Skip to content

Commit

Permalink
Merge pull request #35 from Kagiso-Future-Media/sso
Browse files Browse the repository at this point in the history
Single sign on
  • Loading branch information
lee-kagiso committed Dec 3, 2015
2 parents 0bfca8e + 55aa22e commit df8d4ef
Show file tree
Hide file tree
Showing 21 changed files with 468 additions and 328 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Kagiso Auth
Django client for our Authentication API
(https://github.com/Kagiso-Future-Media/auth)

[ ![Codeship Status for Kagiso-Future-Media/django_auth](https://codeship.com/projects/f5876350-c731-0132-3b15-4a390261e3f5/status?branch=master)](https://codeship.com/projects/74869)
[![codecov.io](https://codecov.io/github/Kagiso-Future-Media/django_auth/coverage.svg?token=LrFwE9TaXk&branch=master)](https://codecov.io/github/Kagiso-Future-Media/django_auth?branch=master)
Expand Down Expand Up @@ -38,7 +40,7 @@ SIGN_UP_EMAIL_TEMPLATE = 'Name of your mailchimp template'
PASSWORD_RESET_EMAIL_TEMPLATE = 'Name of your mailchimp template'
AUTHOMATIC_CONFIG = {} # see http://peterhudec.github.io/authomatic/reference/config.html if you want social auth
```
Note that the above settings may use lambdas that receive the request as the sole argument if you wish to
Note that the above settings may use lambdas that receive the request as the sole argument if you wish to
make your settings depend on the request.

If you want to use the generic auth UI for sign ups and password resets etc,
Expand Down
27 changes: 10 additions & 17 deletions kagiso_auth/auth_api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,25 @@
import requests

from . import settings
from .exceptions import CASNetworkError, CASTimeout
from .exceptions import AuthAPINetworkError, AuthAPITimeout


logger = logging.getLogger('django')


class AuthApiClient:

BASE_URL = settings.CAS_BASE_URL
BASE_URL = settings.AUTH_API_BASE_URL
TIMEOUT_IN_SECONDS = 6
AUTH_API_TOKEN = settings.AUTH_API_TOKEN

def __init__(self, cas_credentials=None):
if cas_credentials is None:
self._cas_token = settings.CAS_TOKEN
self._cas_source_id = settings.CAS_SOURCE_ID
else:
self._cas_token = cas_credentials['cas_token']
self._cas_source_id = cas_credentials['cas_source_id']

def call(self, endpoint, method='GET', payload=None):
@classmethod
def call(cls, endpoint, method='GET', payload=None):
auth_headers = {
'AUTHORIZATION': 'Token {0}'.format(self._cas_token),
'SOURCE-ID': self._cas_source_id,
'AUTHORIZATION': 'Token {0}'.format(cls.AUTH_API_TOKEN),
}
url = '{base_url}/{endpoint}/.json'.format(
base_url=self.BASE_URL,
base_url=cls.BASE_URL,
endpoint=endpoint
)

Expand All @@ -39,12 +32,12 @@ def call(self, endpoint, method='GET', payload=None):
url,
headers=auth_headers,
json=payload,
timeout=self.TIMEOUT_IN_SECONDS
timeout=cls.TIMEOUT_IN_SECONDS
)
except requests.exceptions.ConnectionError as e:
raise CASNetworkError from e
raise AuthAPINetworkError from e
except requests.exceptions.Timeout as e:
raise CASTimeout from e
raise AuthAPITimeout from e

logger.debug('method={0}'.format(method))
logger.debug('url={0}'.format(url))
Expand Down
28 changes: 6 additions & 22 deletions kagiso_auth/backends.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from django.contrib.auth.backends import ModelBackend
from django.db.models.signals import pre_save

from . import http
from .auth_api_client import AuthApiClient
from .exceptions import CASUnexpectedStatusCode, EmailNotConfirmedError
from .models import KagisoUser, save_user_to_cas
from .exceptions import AuthAPIUnexpectedStatusCode, EmailNotConfirmedError
from .models import KagisoUser


class KagisoBackend(ModelBackend):
Expand All @@ -17,8 +16,6 @@ class KagisoBackend(ModelBackend):
# Django AllAuth does this:
# credentials = {'email': '[email protected], 'password': 'open'}
def authenticate(self, email=None, username=None, password=None, **kwargs):
cas_credentials = kwargs.get('cas_credentials')

email = username if not email else email

payload = {
Expand All @@ -34,29 +31,16 @@ def authenticate(self, email=None, username=None, password=None, **kwargs):
if strategy:
payload['strategy'] = strategy

auth_api_client = AuthApiClient(cas_credentials)
auth_api_client = AuthApiClient()
status, data = auth_api_client.call('sessions', 'POST', payload)

if status == http.HTTP_200_OK:
local_user = KagisoUser.objects.filter(id=data['id']).first()
if local_user:
local_user.override_cas_credentials(cas_credentials)
else:
try:
# Do not on save sync to CAS, as we just got the user's
# data from CAS, and nothing has changed in the interim
pre_save.disconnect(save_user_to_cas, sender=KagisoUser)
local_user = KagisoUser()
local_user.set_password(password)
local_user.build_from_cas_data(data)
local_user.save()
finally:
pre_save.connect(save_user_to_cas, sender=KagisoUser)
user = KagisoUser.sync_user_data_locally(data)
elif status == http.HTTP_404_NOT_FOUND:
return None
elif status == http.HTTP_422_UNPROCESSABLE_ENTITY:
raise EmailNotConfirmedError()
else:
raise CASUnexpectedStatusCode(status, data)
raise AuthAPIUnexpectedStatusCode(status, data)

return local_user
return user
10 changes: 5 additions & 5 deletions kagiso_auth/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
class CASError(Exception):
class AuthAPIError(Exception):
pass


class CASNetworkError(CASError):
class AuthAPINetworkError(AuthAPIError):
pass


class CASTimeout(CASError):
class AuthAPITimeout(AuthAPIError):
pass


class CASUnexpectedStatusCode(CASError):
class AuthAPIUnexpectedStatusCode(AuthAPIError):

def __init__(self, status_code, json):
message = 'Status: {0}.\nJson: {1}'.format(status_code, json)
Expand All @@ -19,5 +19,5 @@ def __init__(self, status_code, json):
super().__init__(message)


class EmailNotConfirmedError(CASError):
class EmailNotConfirmedError(AuthAPIError):
pass
10 changes: 2 additions & 8 deletions kagiso_auth/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ class SignUpForm(forms.Form):

# --- Instance variables ---
social_sign_in = False
site_id = None # Wagtail only

@classmethod
def create(cls, post_data=None, oauth_data=None):
Expand All @@ -119,9 +118,8 @@ def clean(self):
validate_passwords_match(self)
return self.cleaned_data

def save(self, cas_credentials=None):
def save(self, app_name):
user = KagisoUser()
user.override_cas_credentials(cas_credentials)
user.email = self.cleaned_data['email']

if self.social_sign_in:
Expand All @@ -138,11 +136,7 @@ def save(self, cas_credentials=None):
'birth_date': str(self.cleaned_data['birth_date']),
'alerts': self.cleaned_data['alerts'],
}

# If we are using Wagtail keep track of which site it it
# to support multitenancy
if self.site_id:
user.profile['site_id'] = self.site_id
user.created_via = app_name

user.save()
return user
Expand Down
31 changes: 31 additions & 0 deletions kagiso_auth/migrations/0007_auto_20151130_1043.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations


class Migration(migrations.Migration):

dependencies = [
('kagiso_auth', '0006_auto_20150514_1045'),
]

operations = [
migrations.RenameField(
model_name='kagisouser',
old_name='date_joined',
new_name='created',
),
migrations.AddField(
model_name='kagisouser',
name='created_via',
field=models.CharField(null=True, max_length=100, blank=True),
preserve_default=True,
),
migrations.AddField(
model_name='kagisouser',
name='last_sign_in_via',
field=models.CharField(null=True, max_length=100, blank=True),
preserve_default=True,
),
]
Loading

0 comments on commit df8d4ef

Please sign in to comment.