diff options
author | 2016-09-26 16:36:11 +0200 | |
---|---|---|
committer | 2016-09-26 16:36:11 +0200 | |
commit | 6cd7c021a40a50e35ad5788d10a6357465c276ad (patch) | |
tree | c0b814e4d0dca4443ab468fd13c750c215ad81d0 /pharos-dashboard/src/account | |
parent | 4ef75ec4b7012de37598ae4a99b0f5580b579bea (diff) |
Restructure dashboard project for docker deploying
Change-Id: I13cad51270504ee4bed8558598a8891af58a26ab
Signed-off-by: maxbr <maxbr@mi.fu-berlin.de>
Diffstat (limited to 'pharos-dashboard/src/account')
-rw-r--r-- | pharos-dashboard/src/account/__init__.py | 10 | ||||
-rw-r--r-- | pharos-dashboard/src/account/admin.py | 15 | ||||
-rw-r--r-- | pharos-dashboard/src/account/apps.py | 15 | ||||
-rw-r--r-- | pharos-dashboard/src/account/forms.py | 22 | ||||
-rw-r--r-- | pharos-dashboard/src/account/jira_util.py | 66 | ||||
-rw-r--r-- | pharos-dashboard/src/account/middleware.py | 32 | ||||
-rw-r--r-- | pharos-dashboard/src/account/migrations/__init__.py | 10 | ||||
-rw-r--r-- | pharos-dashboard/src/account/models.py | 30 | ||||
-rw-r--r-- | pharos-dashboard/src/account/rsa.pem | 17 | ||||
-rw-r--r-- | pharos-dashboard/src/account/rsa.pub | 6 | ||||
-rw-r--r-- | pharos-dashboard/src/account/tests/__init__.py | 10 | ||||
-rw-r--r-- | pharos-dashboard/src/account/tests/test_general.py | 50 | ||||
-rw-r--r-- | pharos-dashboard/src/account/urls.py | 36 | ||||
-rw-r--r-- | pharos-dashboard/src/account/views.py | 132 |
14 files changed, 451 insertions, 0 deletions
diff --git a/pharos-dashboard/src/account/__init__.py b/pharos-dashboard/src/account/__init__.py new file mode 100644 index 0000000..b5914ce --- /dev/null +++ b/pharos-dashboard/src/account/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/pharos-dashboard/src/account/admin.py b/pharos-dashboard/src/account/admin.py new file mode 100644 index 0000000..18b2e1a --- /dev/null +++ b/pharos-dashboard/src/account/admin.py @@ -0,0 +1,15 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.contrib import admin + +from account.models import UserProfile + +admin.site.register(UserProfile)
\ No newline at end of file diff --git a/pharos-dashboard/src/account/apps.py b/pharos-dashboard/src/account/apps.py new file mode 100644 index 0000000..9814648 --- /dev/null +++ b/pharos-dashboard/src/account/apps.py @@ -0,0 +1,15 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + name = 'account' diff --git a/pharos-dashboard/src/account/forms.py b/pharos-dashboard/src/account/forms.py new file mode 100644 index 0000000..7653e2b --- /dev/null +++ b/pharos-dashboard/src/account/forms.py @@ -0,0 +1,22 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +import django.forms as forms +import pytz as pytz + +from account.models import UserProfile + + +class AccountSettingsForm(forms.ModelForm): + class Meta: + model = UserProfile + fields = ['company', 'ssh_public_key', 'pgp_public_key', 'timezone'] + + timezone = forms.ChoiceField(choices=[(x, x) for x in pytz.common_timezones], initial='UTC') diff --git a/pharos-dashboard/src/account/jira_util.py b/pharos-dashboard/src/account/jira_util.py new file mode 100644 index 0000000..c333f8c --- /dev/null +++ b/pharos-dashboard/src/account/jira_util.py @@ -0,0 +1,66 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +import base64 +import os + +import oauth2 as oauth +from jira import JIRA +from tlslite.utils import keyfactory + +from django.conf import settings + + +class SignatureMethod_RSA_SHA1(oauth.SignatureMethod): + name = 'RSA-SHA1' + + def signing_base(self, request, consumer, token): + if not hasattr(request, 'normalized_url') or request.normalized_url is None: + raise ValueError("Base URL for request is not set.") + + sig = ( + oauth.escape(request.method), + oauth.escape(request.normalized_url), + oauth.escape(request.get_normalized_parameters()), + ) + + key = '%s&' % oauth.escape(consumer.secret) + if token: + key += oauth.escape(token.secret) + raw = '&'.join(sig) + return key, raw + + def sign(self, request, consumer, token): + """Builds the base signature string.""" + key, raw = self.signing_base(request, consumer, token) + + module_dir = os.path.dirname(__file__) # get current directory + with open(module_dir + '/rsa.pem', 'r') as f: + data = f.read() + privateKeyString = data.strip() + privatekey = keyfactory.parsePrivateKey(privateKeyString) + raw = str.encode(raw) + signature = privatekey.hashAndSign(raw) + return base64.b64encode(signature) + + +def get_jira(user): + module_dir = os.path.dirname(__file__) # get current directory + with open(module_dir + '/rsa.pem', 'r') as f: + key_cert = f.read() + + oauth_dict = { + 'access_token': user.userprofile.oauth_token, + 'access_token_secret': user.userprofile.oauth_secret, + 'consumer_key': settings.OAUTH_CONSUMER_KEY, + 'key_cert': key_cert + } + + return JIRA(server=settings.JIRA_URL, oauth=oauth_dict)
\ No newline at end of file diff --git a/pharos-dashboard/src/account/middleware.py b/pharos-dashboard/src/account/middleware.py new file mode 100644 index 0000000..0f1dbd8 --- /dev/null +++ b/pharos-dashboard/src/account/middleware.py @@ -0,0 +1,32 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.utils import timezone +from django.utils.deprecation import MiddlewareMixin + +from account.models import UserProfile + + +class TimezoneMiddleware(MiddlewareMixin): + """ + Activate the timezone from request.user.userprofile if user is authenticated, + deactivate the timezone otherwise and use default (UTC) + """ + def process_request(self, request): + if request.user.is_authenticated: + try: + tz = request.user.userprofile.timezone + timezone.activate(tz) + except UserProfile.DoesNotExist: + UserProfile.objects.create(user=request.user) + tz = request.user.userprofile.timezone + timezone.activate(tz) + else: + timezone.deactivate() diff --git a/pharos-dashboard/src/account/migrations/__init__.py b/pharos-dashboard/src/account/migrations/__init__.py new file mode 100644 index 0000000..b5914ce --- /dev/null +++ b/pharos-dashboard/src/account/migrations/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/pharos-dashboard/src/account/models.py b/pharos-dashboard/src/account/models.py new file mode 100644 index 0000000..621f669 --- /dev/null +++ b/pharos-dashboard/src/account/models.py @@ -0,0 +1,30 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.db import models + +from django.contrib.auth.models import User + +from dashboard.models import Resource + +def upload_to(object, filename): + return object.user.username + '/' + filename + +class UserProfile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + timezone = models.CharField(max_length=100, blank=False, default='UTC') + ssh_public_key = models.FileField(upload_to=upload_to, null=True, blank=True) + pgp_public_key = models.FileField(upload_to=upload_to, null=True, blank=True) + company = models.CharField(max_length=200, blank=False) + oauth_token = models.CharField(max_length=1024, blank=False) + oauth_secret = models.CharField(max_length=1024, blank=False) + + class Meta: + db_table = 'user_profile' diff --git a/pharos-dashboard/src/account/rsa.pem b/pharos-dashboard/src/account/rsa.pem new file mode 100644 index 0000000..dbd4eed --- /dev/null +++ b/pharos-dashboard/src/account/rsa.pem @@ -0,0 +1,17 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V +A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d +7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ +hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H +X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm +uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw +rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z +zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn +qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG +WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno +cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ +3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 +AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 +Lw03eHTNQghS0A== +-----END PRIVATE KEY----- + diff --git a/pharos-dashboard/src/account/rsa.pub b/pharos-dashboard/src/account/rsa.pub new file mode 100644 index 0000000..cc50e45 --- /dev/null +++ b/pharos-dashboard/src/account/rsa.pub @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0YjCwIfYoprq/FQO6lb3asXrx +LlJFuCvtinTF5p0GxvQGu5O3gYytUvtC2JlYzypSRjVxwxrsuRcP3e641SdASwfr +mzyvIgP08N4S0IFzEURkV1wp/IpH7kH41EtbmUmrXSwfNZsnQRE5SYSOhh+LcK2w +yQkdgcMv11l4KoBkcwIDAQAB +-----END PUBLIC KEY----- diff --git a/pharos-dashboard/src/account/tests/__init__.py b/pharos-dashboard/src/account/tests/__init__.py new file mode 100644 index 0000000..b5914ce --- /dev/null +++ b/pharos-dashboard/src/account/tests/__init__.py @@ -0,0 +1,10 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + diff --git a/pharos-dashboard/src/account/tests/test_general.py b/pharos-dashboard/src/account/tests/test_general.py new file mode 100644 index 0000000..72e7ea1 --- /dev/null +++ b/pharos-dashboard/src/account/tests/test_general.py @@ -0,0 +1,50 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +from django.contrib.auth.models import User +from django.test import Client +from django.test import TestCase +from django.urls import reverse +from django.utils import timezone + +from account.models import UserProfile + + +class AccountMiddlewareTestCase(TestCase): + def setUp(self): + self.client = Client() + self.user1 = User.objects.create(username='user1') + self.user1.set_password('user1') + self.user1profile = UserProfile.objects.create(user=self.user1) + self.user1.save() + + def test_timezone_middleware(self): + """ + The timezone should be UTC for anonymous users, for authenticated users it should be set + to user.userprofile.timezone + """ + #default + self.assertEqual(timezone.get_current_timezone_name(), 'UTC') + + url = reverse('account:settings') + # anonymous request + self.client.get(url) + self.assertEqual(timezone.get_current_timezone_name(), 'UTC') + + # authenticated user with UTC timezone (userprofile default) + self.client.login(username='user1', password='user1') + self.client.get(url) + self.assertEqual(timezone.get_current_timezone_name(), 'UTC') + + # authenticated user with custom timezone (userprofile default) + self.user1profile.timezone = 'Etc/Greenwich' + self.user1profile.save() + self.client.get(url) + self.assertEqual(timezone.get_current_timezone_name(), 'Etc/Greenwich') diff --git a/pharos-dashboard/src/account/urls.py b/pharos-dashboard/src/account/urls.py new file mode 100644 index 0000000..3962a0c --- /dev/null +++ b/pharos-dashboard/src/account/urls.py @@ -0,0 +1,36 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +"""pharos_dashboard URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.10/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url + +from account.views import * + +urlpatterns = [ + url(r'^settings/', AccountSettingsView.as_view(), name='settings'), + url(r'^authenticated/$', JiraAuthenticatedView.as_view(), name='authenticated'), + url(r'^login/$', JiraLoginView.as_view(), name='login'), + url(r'^logout/$', JiraLogoutView.as_view(), name='logout'), + url(r'^users/$', UserListView.as_view(), name='users'), +] diff --git a/pharos-dashboard/src/account/views.py b/pharos-dashboard/src/account/views.py new file mode 100644 index 0000000..3b4269d --- /dev/null +++ b/pharos-dashboard/src/account/views.py @@ -0,0 +1,132 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + + +import os +import urllib + +import oauth2 as oauth +from django.contrib import messages +from django.contrib.auth import logout, authenticate, login +from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.auth.models import User +from django.urls import reverse +from django.utils.decorators import method_decorator +from django.views.generic import RedirectView +from django.views.generic import TemplateView +from django.views.generic import UpdateView +from jira import JIRA + +from account.forms import AccountSettingsForm +from account.jira_util import SignatureMethod_RSA_SHA1 +from account.models import UserProfile +from django.conf import settings + +consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET) + + +@method_decorator(login_required, name='dispatch') +class AccountSettingsView(UpdateView): + model = UserProfile + form_class = AccountSettingsForm + template_name_suffix = '_update_form' + + def get_success_url(self): + messages.add_message(self.request, messages.INFO, + 'Settings saved') + return '/' + + def get_object(self, queryset=None): + return self.request.user.userprofile + + +class JiraLoginView(RedirectView): + def get_redirect_url(self, *args, **kwargs): + client = oauth.Client(consumer) + client.set_signature_method(SignatureMethod_RSA_SHA1()) + + # Step 1. Get a request token from Jira. + resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST") + if resp['status'] != '200': + raise Exception("Invalid response %s: %s" % (resp['status'], content)) + + # Step 2. Store the request token in a session for later use. + self.request.session['request_token'] = dict(urllib.parse.parse_qsl(content.decode())) + # Step 3. Redirect the user to the authentication URL. + url = settings.OAUTH_AUTHORIZE_URL + '?oauth_token=' + \ + self.request.session['request_token']['oauth_token'] + \ + '&oauth_callback=' + settings.OAUTH_CALLBACK_URL + return url + + +class JiraLogoutView(LoginRequiredMixin, RedirectView): + def get_redirect_url(self, *args, **kwargs): + logout(self.request) + return '/' + + +class JiraAuthenticatedView(RedirectView): + def get_redirect_url(self, *args, **kwargs): + # Step 1. Use the request token in the session to build a new client. + token = oauth.Token(self.request.session['request_token']['oauth_token'], + self.request.session['request_token']['oauth_token_secret']) + client = oauth.Client(consumer, token) + client.set_signature_method(SignatureMethod_RSA_SHA1()) + + # Step 2. Request the authorized access token from Jira. + resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST") + if resp['status'] != '200': + return '/' + + access_token = dict(urllib.parse.parse_qsl(content.decode())) + + module_dir = os.path.dirname(__file__) # get current directory + with open(module_dir + '/rsa.pem', 'r') as f: + key_cert = f.read() + + oauth_dict = { + 'access_token': access_token['oauth_token'], + 'access_token_secret': access_token['oauth_token_secret'], + 'consumer_key': settings.OAUTH_CONSUMER_KEY, + 'key_cert': key_cert + } + + jira = JIRA(server=settings.JIRA_URL, oauth=oauth_dict) + username = jira.current_user() + url = '/' + # Step 3. Lookup the user or create them if they don't exist. + try: + user = User.objects.get(username=username) + except User.DoesNotExist: + # Save our permanent token and secret for later. + user = User.objects.create_user(username=username, + password=access_token['oauth_token_secret']) + profile = UserProfile() + profile.user = user + profile.save() + url = reverse('account:settings') + user.userprofile.oauth_token = access_token['oauth_token'] + user.userprofile.oauth_secret = access_token['oauth_token_secret'] + user.userprofile.save() + user.set_password(access_token['oauth_token_secret']) + user.save() + user = authenticate(username=username, password=access_token['oauth_token_secret']) + login(self.request, user) + # redirect user to settings page to complete profile + return url + +class UserListView(TemplateView): + template_name = "account/user_list.html" + + def get_context_data(self, **kwargs): + users = User.objects.all() + context = super(UserListView, self).get_context_data(**kwargs) + context.update({'title': "Dashboard Users", 'users': users}) + return context |