diff --git a/PyRIGS/settings.py b/PyRIGS/settings.py index 8eee78c7..03c7c73a 100644 --- a/PyRIGS/settings.py +++ b/PyRIGS/settings.py @@ -79,58 +79,21 @@ AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', ) -# Environ -# DISCOURSE_HOST='http://localhost:4000' -# DISCOURSE_SSO_SECRET='ABCDEFGHIJKLMNOP' - -SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/rigboard' -SOCIAL_AUTH_LOGIN_URL = '/' - SOCIAL_AUTH_PIPELINE = ( - # Get the information we can about the user and return it in a simple - # format to create the user instance later. On some cases the details are - # already part of the auth response from the provider, but sometimes this - # could hit a provider API. - 'social.pipeline.social_auth.social_details', - - # Get the social uid from whichever service we're authing thru. The uid is - # the unique identifier of the given user in the provider. - 'social.pipeline.social_auth.social_uid', - - # Verifies that the current auth process is valid within the current - # project, this is were emails and domains whitelists are applied (if - # defined). - 'social.pipeline.social_auth.auth_allowed', - - # Checks if the current social-account is already associated in the site. - 'social.pipeline.social_auth.social_user', - - # Make up a username for this person, appends a random string at the end if - # there's any collision. - # 'social.pipeline.user.get_username', - - # Send a validation email to the user to verify its email address. - # Disabled by default. - # 'social.pipeline.mail.mail_validation', - - # Associates the current social details with another user account with - # a similar email address. Disabled by default. - 'social.pipeline.social_auth.associate_by_email', - - # Create a user account if we haven't found one yet. - #'social.pipeline.user.create_user', - - # Create the record that associated the social account with this user. - #'social.pipeline.social_auth.associate_user', - - # Populate the extra_data field in the social record with the values - # specified by settings (and the default ones like access_token, etc). - 'social.pipeline.social_auth.load_extra_data', - - # Update the user record with any changed info from the auth service. - 'social.pipeline.user.user_details', + 'social.pipeline.social_auth.social_details', # Load remote details + 'social.pipeline.social_auth.social_uid', # Load remote ID + 'social.pipeline.social_auth.auth_allowed', # Check not blacklisted + 'social.pipeline.social_auth.social_user', # If already associated, login + 'RIGS.discourse.pipeline.new_connection', # Choose a user account, much UI + 'social.pipeline.social_auth.associate_user', # Associate the social auth with the user + 'social.pipeline.user.user_details', # Save any details that changed ) +DISCOURSE_HOST=os.environ.get('DISCOURSE_HOST') if os.environ.get('DISCOURSE_HOST') else 'http://localhost:4000' +DISCOURSE_SSO_SECRET=os.environ.get('DISCOURSE_SSO_SECRET') if os.environ.get('DISCOURSE_SSO_SECRET') else 'ABCDEFGHIJKLMNOP' + +REGISTRATION_OPEN = False # Disable built-in django registration - must register using forum + ROOT_URLCONF = 'PyRIGS.urls' WSGI_APPLICATION = 'PyRIGS.wsgi.application' diff --git a/RIGS/discourse/discourse.py b/RIGS/discourse/discourse.py index dea26039..ae7d8089 100644 --- a/RIGS/discourse/discourse.py +++ b/RIGS/discourse/discourse.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals -import os from social.backends.base import BaseAuth -from social.exceptions import AuthException +from django.conf import settings from .sso import DiscourseSSO @@ -20,8 +19,8 @@ class DiscourseAssociation(object): class DiscourseAuth(BaseAuth): """Discourse authentication backend""" name = 'discourse' - secret = os.environ['DISCOURSE_SSO_SECRET'] - host = os.environ['DISCOURSE_HOST'] + secret = settings.DISCOURSE_SSO_SECRET + host = settings.DISCOURSE_HOST EXTRA_DATA = [ ('username', 'username'), diff --git a/RIGS/discourse/pipeline.py b/RIGS/discourse/pipeline.py new file mode 100644 index 00000000..06fcbc18 --- /dev/null +++ b/RIGS/discourse/pipeline.py @@ -0,0 +1,62 @@ +from django.core.urlresolvers import reverse +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.shortcuts import render_to_response +from django.core.exceptions import ValidationError + +from social.pipeline.partial import partial + +from RIGS.models import Profile +from RIGS import forms + + +class SocialRegisterForm(forms.ProfileRegistrationFormUniqueEmail): + def __init__(self, *args, **kwargs): + super(SocialRegisterForm, self).__init__(*args, **kwargs) + self.fields.pop('password1') + self.fields.pop('password2') + self.fields.pop('captcha') + + self.fields['email'].widget.attrs['readonly'] = True + + def clean_email(self): + initial = getattr(self, 'initial', None) + if(initial['email'] != self.cleaned_data['email']): + raise ValidationError("You cannot change the email") + + return initial['email'] + + +@partial +def new_connection(backend, details, response, user=None, is_new=False, social=None, request=None, *args, **kwargs): + if social is not None: + return + + data = backend.strategy.request_data() + + if data.get('UseCurrentAccount') is not None: + return + + alreadyLoggedIn = user is not None + + context = { + 'details': details, + 'alreadyLoggedIn': alreadyLoggedIn, + 'loggedInUser': user, + } + + if not alreadyLoggedIn: + completeUrl = reverse('social:complete', kwargs={'backend': backend.name}) + context['login_url'] = "{0}?{1}={2}".format(reverse('login'), REDIRECT_FIELD_NAME, completeUrl) + + if data.get('username') is None: + form = SocialRegisterForm(initial=details) + else: + form = SocialRegisterForm(data, initial=details) + + if form.is_valid(): + new_user = Profile.objects.create_user(**form.cleaned_data) + return {'user': new_user} + + context['form'] = form + + return render_to_response('RIGS/social-associate.html', context) diff --git a/RIGS/forms.py b/RIGS/forms.py index e1e95012..6d164fb9 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -3,6 +3,7 @@ from django import forms from django.utils import formats from django.conf import settings from django.core import serializers +from django.core.exceptions import ValidationError from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AuthenticationForm, PasswordResetForm from registration.forms import RegistrationFormUniqueEmail from captcha.fields import ReCaptchaField @@ -27,6 +28,16 @@ class ProfileRegistrationFormUniqueEmail(RegistrationFormUniqueEmail): raise forms.ValidationError("These initials are already in use. Please supply different initials.") return self.cleaned_data['initials'] + def clean_first_name(self): + if self.cleaned_data["first_name"].strip() == '': + raise ValidationError("First name is required.") + return self.cleaned_data["first_name"] + + def clean_last_name(self): + if self.cleaned_data["last_name"].strip() == '': + raise ValidationError("Last name is required.") + return self.cleaned_data["last_name"] + # Login form class PasswordReset(PasswordResetForm): diff --git a/RIGS/static/imgs/forum-logo.gif b/RIGS/static/imgs/forum-logo.gif new file mode 100644 index 00000000..ca41e56c Binary files /dev/null and b/RIGS/static/imgs/forum-logo.gif differ diff --git a/RIGS/templates/RIGS/profile_detail.html b/RIGS/templates/RIGS/profile_detail.html index 11904b18..491bcaff 100644 --- a/RIGS/templates/RIGS/profile_detail.html +++ b/RIGS/templates/RIGS/profile_detail.html @@ -133,6 +133,20 @@ {% endif %} + +

Linked to {{object.social_auth.count}} Forum Account(s)

+ + {% if object.social_auth.count > 0 %} + + + Unlink Forum Account(s) + + {% else %} + + Link Forum Account + + {% endif %} + {% endif %} {% endif %} diff --git a/RIGS/templates/RIGS/social-associate.html b/RIGS/templates/RIGS/social-associate.html new file mode 100644 index 00000000..768b3957 --- /dev/null +++ b/RIGS/templates/RIGS/social-associate.html @@ -0,0 +1,55 @@ +{% extends 'base.html' %} +{% load widget_tweaks %} +{% block title %}Associate{% endblock %} + +{% block content %} +
+ +
+

Rig Information Gathering System

+
+

Welcome {{details.username}}

+

This is the first time you've visited RIGS with your forum account, so we need a few details to get you set up

+
+ + + {% if alreadyLoggedIn %} +

You are logged in to RIGS as {{loggedInUser.username}}

+
+
{% csrf_token %} + + +
+ Logout +
+ {% else %} +

I already have a RIGS account

+ {% if form.errors or supplement_form.errors %} +
+ {{form.errors}} + {{supplement_form.errors}} +
+ {% endif %} + +
+
{% csrf_token %} + {% for field in form %} +
+ +
+ {% render_field field class+="form-control" placeholder=field.label %} +
+
+ {% endfor %} +

+
+ +
+ {% endif %} + + + + + +
+{% endblock %} \ No newline at end of file diff --git a/RIGS/urls.py b/RIGS/urls.py index 15bb0990..97e3eb8f 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -1,6 +1,7 @@ from django.conf.urls import patterns, url from django.contrib.auth.decorators import login_required from RIGS import models, views, rigboard, finance, ical, versioning, forms +from RIGS.discourse import views as discourseViews from django.views.generic import RedirectView from django.views.decorators.clickjacking import xframe_options_exempt @@ -15,6 +16,7 @@ urlpatterns = patterns('', url(r'^closemodal/$', views.CloseModal.as_view(), name='closemodal'), url('^user/login/$', 'RIGS.views.login', name='login'), + url('^user/associate/$', discourseViews.Associate.as_view(), name='associate'), url('^user/login/embed/$', xframe_options_exempt(views.login_embed), name='login_embed'), url(r'^user/password_reset/$', 'django.contrib.auth.views.password_reset', {'password_reset_form': forms.PasswordReset}), @@ -153,6 +155,7 @@ urlpatterns = patterns('', url(r'^user/edit/$', login_required(views.ProfileUpdateSelf.as_view()), name='profile_update_self'), url(r'^user/reset_api_key$', login_required(views.ResetApiKey.as_view(permanent=False)), name='reset_api_key'), + url(r'^user/unlink_forum$', login_required(views.UnlinkForum.as_view(permanent=False)), name='unlink_forum'), # ICS Calendar - API key authentication url(r'^ical/(?P\d+)/(?P\w+)/rigs.ics$', api_key_required(ical.CalendarICS()), name="ics_calendar"), diff --git a/RIGS/views.py b/RIGS/views.py index c0186bed..e3fa0eaa 100644 --- a/RIGS/views.py +++ b/RIGS/views.py @@ -382,3 +382,12 @@ class ResetApiKey(generic.RedirectView): self.request.user.save() return reverse_lazy('profile_detail') + +class UnlinkForum(generic.RedirectView): + def get_redirect_url(self, *args, **kwargs): + for link in self.request.user.social_auth.all(): + link.delete() + + self.request.user.save() + + return reverse_lazy('profile_detail') diff --git a/templates/registration/login.html b/templates/registration/login.html index 5dd85fe7..33338e8c 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -1,4 +1,5 @@ {% extends 'base.html' %} +{% load static from staticfiles %} {% block title %}Login{% endblock %} @@ -6,5 +7,40 @@

Rig Information Gathering System

-{% include 'registration/loginform.html' %} + + +
+
+
+

+ + Login with TEC Forum + +

+
+ +
+
+
+

+ Login with RIGS Credentials +

+
+
+
+ {% include 'registration/loginform.html' %} +
+
+
+
+ + {% endblock %} \ No newline at end of file diff --git a/templates/registration/loginform.html b/templates/registration/loginform.html index ad7e33d3..8dc7b0b5 100644 --- a/templates/registration/loginform.html +++ b/templates/registration/loginform.html @@ -13,7 +13,7 @@ {% render_field form.password class+="form-control" placeholder=form.password.label %}