summaryrefslogtreecommitdiffstats
path: root/pharos-dashboard/account
diff options
context:
space:
mode:
authormaxbr <maxbr@mi.fu-berlin.de>2016-08-19 17:15:28 +0200
committermaxbr <maxbr@mi.fu-berlin.de>2016-08-19 17:15:28 +0200
commit114a6fb65e14f15487bc8db33cedae011fc42cac (patch)
treeb0e2db7a0876ade38e7780468b367a30b48b6036 /pharos-dashboard/account
parent217e6dd3c193b5f576ade7581775993c8ed82294 (diff)
Use Jira Oauth for user authentication
JIRA: RELENG-12 Users can use their jira accounts for the dashboard. This also allows the dasboard to open jira tickets for bookings. Signed-off-by: maxbr <maxbr@mi.fu-berlin.de>
Diffstat (limited to 'pharos-dashboard/account')
-rw-r--r--pharos-dashboard/account/forms.py15
-rw-r--r--pharos-dashboard/account/jira_util.py56
-rw-r--r--pharos-dashboard/account/migrations/0002_auto_20160816_1511.py27
-rw-r--r--pharos-dashboard/account/migrations/0003_auto_20160819_1024.py25
-rw-r--r--pharos-dashboard/account/migrations/0004_auto_20160819_1055.py20
-rw-r--r--pharos-dashboard/account/models.py6
-rw-r--r--pharos-dashboard/account/rsa.pem17
-rw-r--r--pharos-dashboard/account/rsa.pub6
-rw-r--r--pharos-dashboard/account/urls.py7
-rw-r--r--pharos-dashboard/account/views.py153
10 files changed, 257 insertions, 75 deletions
diff --git a/pharos-dashboard/account/forms.py b/pharos-dashboard/account/forms.py
index 7893867..14f11cd 100644
--- a/pharos-dashboard/account/forms.py
+++ b/pharos-dashboard/account/forms.py
@@ -1,17 +1,14 @@
import django.forms as forms
import pytz as pytz
-from registration.forms import RegistrationForm as BaseRegistrationForm
+from account.models import UserProfile
-class AccountSettingsForm(forms.Form):
- fields = ['first_name', 'last_name', 'email', 'company', 'ssh_public_key', 'pgp_public_key',
- 'timezone']
+class AccountSettingsForm(forms.ModelForm):
+ class Meta:
+ model = UserProfile
+ fields = ['company', 'ssh_public_key', 'pgp_public_key', 'timezone']
- first_name = forms.CharField(max_length=30)
- last_name = forms.CharField(max_length=30)
- email = forms.EmailField()
- company = forms.CharField(max_length=30)
ssh_public_key = forms.CharField(max_length=2048, widget=forms.Textarea)
pgp_public_key = forms.CharField(max_length=2048, widget=forms.Textarea)
- timezone = forms.ChoiceField(choices=[(x, x) for x in pytz.common_timezones], initial='UTC') \ No newline at end of file
+ timezone = forms.ChoiceField(choices=[(x, x) for x in pytz.common_timezones], initial='UTC')
diff --git a/pharos-dashboard/account/jira_util.py b/pharos-dashboard/account/jira_util.py
new file mode 100644
index 0000000..bd07ff3
--- /dev/null
+++ b/pharos-dashboard/account/jira_util.py
@@ -0,0 +1,56 @@
+import base64
+import os
+
+import oauth2 as oauth
+from jira import JIRA
+from tlslite.utils import keyfactory
+
+from pharos_dashboard 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/account/migrations/0002_auto_20160816_1511.py b/pharos-dashboard/account/migrations/0002_auto_20160816_1511.py
new file mode 100644
index 0000000..3fcd989
--- /dev/null
+++ b/pharos-dashboard/account/migrations/0002_auto_20160816_1511.py
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-16 15:11
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='userprofile',
+ name='oauth_secret',
+ field=models.CharField(default='', max_length=1024),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='userprofile',
+ name='oauth_token',
+ field=models.CharField(default='', max_length=1024),
+ preserve_default=False,
+ ),
+ ]
diff --git a/pharos-dashboard/account/migrations/0003_auto_20160819_1024.py b/pharos-dashboard/account/migrations/0003_auto_20160819_1024.py
new file mode 100644
index 0000000..b648844
--- /dev/null
+++ b/pharos-dashboard/account/migrations/0003_auto_20160819_1024.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-19 10:24
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0002_auto_20160816_1511'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='userprofile',
+ old_name='pgpkey',
+ new_name='pgp_pupblic_key',
+ ),
+ migrations.RenameField(
+ model_name='userprofile',
+ old_name='sshkey',
+ new_name='ssh_public_key',
+ ),
+ ]
diff --git a/pharos-dashboard/account/migrations/0004_auto_20160819_1055.py b/pharos-dashboard/account/migrations/0004_auto_20160819_1055.py
new file mode 100644
index 0000000..51af0aa
--- /dev/null
+++ b/pharos-dashboard/account/migrations/0004_auto_20160819_1055.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-19 10:55
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0003_auto_20160819_1024'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='userprofile',
+ old_name='pgp_pupblic_key',
+ new_name='pgp_public_key',
+ ),
+ ]
diff --git a/pharos-dashboard/account/models.py b/pharos-dashboard/account/models.py
index 5181c71..fbabf6c 100644
--- a/pharos-dashboard/account/models.py
+++ b/pharos-dashboard/account/models.py
@@ -8,9 +8,11 @@ from dashboard.models import Resource
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
timezone = models.CharField(max_length=100, blank=False, default='UTC')
- sshkey = models.CharField(max_length=2048, blank=False)
- pgpkey = models.CharField(max_length=2048, blank=False)
+ ssh_public_key = models.CharField(max_length=2048, blank=False)
+ pgp_public_key = models.CharField(max_length=2048, blank=False)
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/account/rsa.pem b/pharos-dashboard/account/rsa.pem
new file mode 100644
index 0000000..dbd4eed
--- /dev/null
+++ b/pharos-dashboard/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/account/rsa.pub b/pharos-dashboard/account/rsa.pub
new file mode 100644
index 0000000..cc50e45
--- /dev/null
+++ b/pharos-dashboard/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/account/urls.py b/pharos-dashboard/account/urls.py
index 5d68135..b837814 100644
--- a/pharos-dashboard/account/urls.py
+++ b/pharos-dashboard/account/urls.py
@@ -14,13 +14,12 @@ Including another URLconf
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
-from django.contrib.auth import views as auth_views
from account.views import *
urlpatterns = [
- url(r'^login/$', auth_views.login, name='login'),
- url(r'^logout/$', auth_views.logout, name='logout'),
- url(r'^register/', RegistrationView.as_view(), name='registration'),
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')
]
diff --git a/pharos-dashboard/account/views.py b/pharos-dashboard/account/views.py
index 3432867..7d2c9bd 100644
--- a/pharos-dashboard/account/views.py
+++ b/pharos-dashboard/account/views.py
@@ -1,78 +1,111 @@
+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.core.exceptions import ObjectDoesNotExist
+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 FormView
-from registration.backends.simple.views import RegistrationView as BaseRegistrationView
+from django.views.generic import RedirectView
+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 pharos_dashboard 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 '/'
-class RegistrationView(BaseRegistrationView):
- template_name = 'registration/registration_form.html'
+ def get_object(self, queryset=None):
+ return self.request.user.userprofile
- def get_context_data(self, **kwargs):
- context = super(RegistrationView, self).get_context_data(**kwargs)
- context.update({'title': "Registration"})
- return context
- def register(self, form):
- new_user = super(RegistrationView, self).register(form)
- UserProfile.objects.create(user=new_user)
- messages.add_message(self.request, messages.INFO, 'Please complete your user profile.')
- return new_user
+class JiraLoginView(RedirectView):
+ def get_redirect_url(self, *args, **kwargs):
+ client = oauth.Client(consumer)
+ client.set_signature_method(SignatureMethod_RSA_SHA1())
- def get_success_url(self, user):
- return reverse('account:settings')
+ # 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']
+ return url
-@method_decorator(login_required, name='dispatch')
-class AccountSettingsView(FormView):
- form_class = AccountSettingsForm
- template_name = 'registration/registration_form.html'
- success_url = '/'
- def dispatch(self, request, *args, **kwargs):
+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:
- request.user.userprofile
- except ObjectDoesNotExist:
- UserProfile.objects.create(user=request.user)
- messages.add_message(self.request, messages.INFO,
- 'Please complete your user profile to proceed.')
- return super(AccountSettingsView, self).dispatch(request, *args, **kwargs)
-
- def get_context_data(self, **kwargs):
- context = super(AccountSettingsView, self).get_context_data(**kwargs)
- context.update({'title': "Settings"})
- return context
-
- def get_initial(self):
- user = self.request.user
- initial = super(AccountSettingsView, self).get_initial()
- initial['first_name'] = user.first_name
- initial['last_name'] = user.last_name
- initial['email'] = user.email
- initial['company'] = user.userprofile.company
- initial['ssh_public_key'] = user.userprofile.sshkey
- initial['pgp_public_key'] = user.userprofile.pgpkey
- initial['timezone'] = user.userprofile.timezone
- return initial
-
- def form_valid(self, form):
- user = self.request.user
- user.first_name = form.cleaned_data['first_name']
- user.last_name = form.cleaned_data['last_name']
- user.email = form.cleaned_data['email']
- user.userprofile.company = form.cleaned_data['company']
- user.userprofile.sshkey = form.cleaned_data['ssh_public_key']
- user.userprofile.pgpkey = form.cleaned_data['pgp_public_key']
- user.userprofile.timezone = form.cleaned_data['timezone']
+ 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()
- if not user.is_active:
- user.is_active = True
+ user.set_password(access_token['oauth_token_secret'])
user.save()
- messages.add_message(self.request, messages.INFO,
- 'Settings saved')
- return super(AccountSettingsView, self).form_valid(form)
+ user = authenticate(username=username, password=access_token['oauth_token_secret'])
+ login(self.request, user)
+ # redirect user to settings page to complete profile
+ return url