Browse files

pretty huge changes, mostly backwards incompatible

first step towards a 1.0 release
  • Loading branch information...
1 parent 83e5abd commit 8faa2166c367541fd7450b42e888ba7881bda5b0 @ojii ojii committed Nov 28, 2012
View
4 runtests.py
@@ -28,7 +28,9 @@ def run_tests():
ROOT_URLCONF = ROOT_URLCONF,
DATABASES = DATABASES,
TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner',
- SIMPLE_SSO_SERVER = '/server/',
+ SSO_PRIVATE_KEY = 'private',
+ SSO_PUBLIC_KEY = 'public',
+ SSO_SERVER = 'http://localhost/server/',
)
# Run the test suite, including the extra validation tests.
View
17 setup.py
@@ -25,7 +25,20 @@
url='http://github.com/ojii/django-simple-sso',
license='BSD',
packages=find_packages(),
- install_requires=['Django>=1.3', 'django-load', 'requests', 'south'],
+ install_requires=[
+ 'itsdangerous'
+ ],
+ extras_require = {
+ 'server': [
+ 'django',
+ 'webservices[server]',
+ 'south',
+ ],
+ 'client': [
+ 'requests',
+ 'webservices[server]',
+ ],
+ },
include_package_data=True,
- zip_safe=False
+ zip_safe=False,
)
View
2 simple_sso/__init__.py
@@ -1 +1 @@
-__version__ = '0.6.0'
+__version__ = '0.9.0'
View
1 simple_sso/client.py
@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-
View
194 simple_sso/server.py
@@ -1,194 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.conf.urls.defaults import patterns, url
-from django.contrib import admin
-from django.contrib.admin.options import ModelAdmin
-from django.core.urlresolvers import reverse
-from django.http import (HttpResponse, HttpResponseForbidden,
- HttpResponseBadRequest, HttpResponseRedirect)
-from django.utils import simplejson
-from django.views.generic.base import View
-from simple_sso.signatures import build_signature
-from simple_sso.sso_server.forms import (RequestTokenRequestForm, AuthorizeForm,
- VerificationForm)
-from simple_sso.sso_server.models import Token, Client
-from simple_sso.utils import SIMPLE_KEYS
-from urlparse import urljoin
-import datetime
-import urllib
-
-
-class RequestTokenView(View):
- """
- Request Token Request view called by the client application to obtain a
- one-time Request Token.
- """
- form_class = RequestTokenRequestForm
- server = None
-
- def get(self, request):
- self.form = self.form_class(self.request.GET)
- if self.form.is_valid():
- return self.form_valid()
- else:
- return self.form_invalid()
-
- def get_token(self):
- return Token.objects.create_for_client(self.form.client)
-
- def form_valid(self):
- token = self.get_token()
- params = [('request_token', token.request_token)]
- signature = build_signature(params, token.client.secret)
- params.append(('signature', signature))
- data = urllib.urlencode(params)
- return HttpResponse(data)
-
- def form_invalid(self):
- if self.form.invalid_signature:
- return HttpResponseForbidden()
- return HttpResponseBadRequest()
-
-
-class AuthorizeView(View):
- """
- The client get's redirected to this view with the `request_token` obtained
- by the Request Token Request by the client application beforehand.
-
- This view checks if the user is logged in on the server application and if
- that user has the necessary rights.
-
- If the user is not logged in, the user is prompted to log in.
- """
- form_class = AuthorizeForm
- server = None
-
- def get(self, request):
- self.form = self.form_class(request.GET)
- if self.form.is_valid():
- self.token = self.form.cleaned_data['token']
- if self.check_token_timeout():
- return self.form_valid()
- else:
- return self.token_timeout()
- else:
- return self.form_invalid()
-
- def form_valid(self):
- if self.request.user.is_authenticated():
- return self.form_valid_authenticated()
- else:
- return self.form_valid_unauthenticated()
-
- def check_token_timeout(self):
- delta = datetime.datetime.now() - self.token.created
- if delta > self.server.token_timeout:
- return False
- else:
- return True
-
- def token_timeout(self):
- self.token.delete()
- return HttpResponseForbidden()
-
- def form_valid_authenticated(self):
- if self.has_access():
- return self.success()
- else:
- return self.access_denied()
-
- def has_access(self):
- return True
-
- def success(self):
- url = urljoin(self.token.client.root_url, 'authenticate') + '/'
- params = [('request_token', self.token.request_token), ('auth_token', self.token.auth_token)]
- signature = build_signature(params, self.token.client.secret)
- params.append(('signature', signature))
- self.token.user = self.request.user
- self.token.save()
- return HttpResponseRedirect('%s?%s' % (url, urllib.urlencode(params)))
-
- def access_denied(self):
- return HttpResponseForbidden()
-
- def form_valid_unauthenticated(self):
- params = urllib.urlencode([('next', '%s?%s' % (self.request.path, urllib.urlencode(self.request.GET)))])
- return HttpResponseRedirect('%s?%s' % (reverse('django.contrib.auth.views.login'), params))
-
- def form_invalid(self):
- if self.form.invalid_signature:
- return HttpResponseForbidden()
- return HttpResponseBadRequest()
-
-
-class VerifyView(View):
- """
- View called by the client application to verify the Auth Token passed by
- the client request as GET parameter with the server application
- """
- form_class = VerificationForm
- server = None
-
- def get(self, request):
- self.form = self.form_class(request.GET)
- if self.form.is_valid():
- return self.form_valid()
- else:
- return self.form_invalid()
-
- def construct_user(self):
- data = {}
- for key in SIMPLE_KEYS:
- data[key] = getattr(self.token.user, key)
- data['permissions'] = []
- for perm in self.token.user.user_permissions.select_related('content_type').all():
- data['permissions'].append({
- 'content_type': perm.content_type.natural_key(),
- 'codename': perm.codename,
- })
- return data
-
- def get_user_json(self):
- """
- Returns the JSON string representation of the user object for a client.
- """
- data = self.construct_user()
- return simplejson.dumps(data)
-
- def form_valid(self):
- self.token = self.form.cleaned_data['token']
- self.user = self.get_user_json()
- params = [('user', self.user)]
- signature = build_signature(params, self.token.client.secret)
- params.append(('signature', signature))
- data = urllib.urlencode(params)
- self.token.delete()
- return HttpResponse(data)
-
- def form_invalid(self):
- if self.form.invalid_signature:
- return HttpResponseForbidden()
- return HttpResponseBadRequest()
-
-
-class SimpleSSOServer(object):
- request_token_view = RequestTokenView
- authorize_view = AuthorizeView
- verify_view = VerifyView
- token_timeout = datetime.timedelta(minutes=30)
- client_admin = ModelAdmin
-
- def __init__(self, **kwargs):
- for key, value in kwargs.items():
- setattr(self, key, value)
- self.register_admin()
-
- def register_admin(self):
- admin.site.register(Client, self.client_admin)
-
- def get_urls(self):
- return patterns('simple_sso.sso_server.views',
- url(r'^request-token/$', self.request_token_view.as_view(server=self), name='simple-sso-request-token'),
- url(r'^authorize/$', self.authorize_view.as_view(server=self), name='simple-sso-authorize'),
- url(r'^verify/$', self.verify_view.as_view(server=self), name='simple-sso-verify'),
- )
View
28 simple_sso/signatures.py
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-import hashlib
-import hmac
-import urllib
-
-
-def build_signature(parameters, secret):
- """
- Builds the signature for the parameters and the body given.
-
- Parameters is a list of tuples.
- """
- message = urllib.urlencode(sorted(parameters))
- return hmac.new(secret.encode('ascii'), message.encode('ascii'), hashlib.sha256).hexdigest()
-
-def verify_signature(parameters, signature, secret):
- """
- Verifies the signature
-
- Parameters is a list of tuples.
- """
- result = 0
- built_signature = build_signature(parameters, secret)
- if len(signature) != len(built_signature):
- return False
- for x, y in zip(built_signature, signature):
- result |= ord(x) ^ ord(y)
- return result == 0
View
103 simple_sso/sso_client/client.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+import urllib
+import urlparse
+from django.conf.urls import patterns, url
+from django.contrib.auth import login
+from django.contrib.auth.backends import ModelBackend
+from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+from django.views.generic import View
+from itsdangerous import URLSafeTimedSerializer
+from webservices.sync import SyncConsumer
+
+
+class LoginView(View):
+ client = None
+
+ def get(self, request):
+ next = self.get_next()
+ scheme = 'https' if request.is_secure() else 'http'
+ query = urllib.urlencode([('next', next)])
+ netloc = request.get_host()
+ path = reverse('simple-sso-authenticate')
+ redirect_to = urlparse.urlunparse((scheme, netloc, path, '', query, ''))
+ request_token = self.client.get_request_token(redirect_to)
+ host = urlparse.urljoin(self.client.server_url, 'authorize/')
+ url = '%s?%s' % (host, urllib.urlencode([('token', request_token)]))
+ return HttpResponseRedirect(url)
+
+ def get_next(self):
+ """
+ Given a request, returns the URL where a user should be redirected to
+ after login. Defaults to '/'
+ """
+ next = self.request.GET.get('next', None)
+ if not next:
+ return '/'
+ netloc = urlparse.urlparse(next)[1]
+ # Heavier security check -- don't allow redirection to a different
+ # host.
+ # Taken from django.contrib.auth.views.login
+ if netloc and netloc != self.request.get_host():
+ return '/'
+ return next
+
+
+class AuthenticateView(LoginView):
+ client = None
+
+ def get(self, request):
+ raw_access_token = request.GET['access_token']
+ access_token = URLSafeTimedSerializer(self.client.private_key).loads(raw_access_token)
+ user = self.client.get_user(access_token)
+ user.backend = self.client.backend
+ login(request, user)
+ next = self.get_next()
+ return HttpResponseRedirect(next)
+
+
+class Client(object):
+ login_view = LoginView
+ authenticate_view = AuthenticateView
+ backend = "%s.%s" % (ModelBackend.__module__, ModelBackend.__name__)
+
+ def __init__(self, server_url, public_key, private_key):
+ self.server_url = server_url
+ self.public_key = public_key
+ self.private_key = private_key
+ self.consumer = SyncConsumer(self.server_url, self.public_key, self.private_key)
+
+ @classmethod
+ def from_dsn(cls, dsn):
+ parse_result = urlparse.urlparse(dsn)
+ public_key = parse_result.username
+ private_key = parse_result.password
+ netloc = parse_result.hostname
+ if parse_result.port:
+ netloc += ':%s' % parse_result.port
+ server_url = urlparse.urlunparse((parse_result.scheme, netloc, parse_result.path, parse_result.params, parse_result.query, parse_result.fragment))
+ return cls(server_url, public_key, private_key)
+
+ def get_request_token(self, redirect_to):
+ return self.consumer.consume('/request-token/', {'redirect_to': redirect_to})['request_token']
+
+ def get_user(self, access_token):
+ user_data = self.consumer.consume('/verify/', {'access_token': access_token})
+ user = self.build_user(user_data)
+ return user
+
+ def build_user(self, user_data):
+ try:
+ user = User.objects.get(username=user_data['username'])
+ except User.DoesNotExist:
+ user = User(**user_data)
+ user.set_unusable_password()
+ user.save()
+ return user
+
+ def get_urls(self):
+ return patterns('',
+ url(r'^$', self.login_view.as_view(client=self), name='simple-sso-login'),
+ url(r'^authenticate/$', self.authenticate_view.as_view(client=self), name='simple-sso-authenticate'),
+ )
View
7 simple_sso/sso_client/urls.py
@@ -1,7 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.conf.urls.defaults import patterns, url
-
-urlpatterns = patterns('simple_sso.sso_client.views',
- url(r'^$', 'login_view', name='simple-sso-login'),
- url(r'^authenticate/$', 'authenticate_view', name='simple-sso-authenticate'),
-)
View
41 simple_sso/sso_client/utils.py
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.contrib.auth.models import User, Permission
-from django.contrib.contenttypes.models import ContentType
-from django.utils import simplejson
-from simple_sso.utils import SIMPLE_KEYS
-
-def load_json_user(json):
- """
- Given a JSON string, returns a Django User instance.
- """
- data = simplejson.loads(json)
- try:
- user = User.objects.get(username=data['username'])
- except User.DoesNotExist:
- user = User()
-
- for key in SIMPLE_KEYS:
- setattr(user, key, data[key])
- user.set_unusable_password()
- user.save()
-
- ctype_cache = {}
-
- permissions = []
-
- for perm in data['permissions']:
- ctype = ctype_cache.get(perm['content_type'], None)
- if not ctype:
- try:
- ctype = ContentType.objects.get_by_natural_key(perm['content_type'])
- except ContentType.DoesNotExist:
- continue
- ctype_cache[perm['content_type']] = ctype
- try:
- permission = Permission.objects.get(content_type=ctype, codename=perm['codename'])
- except Permission.DoesNotExist:
- continue
- permissions.append(permission)
-
- user.user_permissions = permissions
- return user
View
109 simple_sso/sso_client/views.py
@@ -1,109 +0,0 @@
-# -*- coding: utf-8 -*-
-from django.conf import settings
-from django.contrib.auth import login
-from django.contrib.auth.backends import ModelBackend
-from django.http import HttpResponseBadRequest, QueryDict, HttpResponseRedirect
-from simple_sso.signatures import build_signature, verify_signature
-from simple_sso.sso_client.utils import load_json_user
-from urlparse import urljoin, urlparse
-import requests
-import urllib
-
-BACKEND = ModelBackend()
-
-def get_request_token():
- """
- Requests a Request Token from the SSO Server. Returns False if the request
- failed.
- """
- params = [('key', settings.SIMPLE_SSO_KEY)]
- signature = build_signature(params, settings.SIMPLE_SSO_SECRET)
- params.append(('signature', signature))
- url = urljoin(settings.SIMPLE_SSO_SERVER, 'request-token') + '/'
- response = requests.get(url, params=dict(params))
- if response.status_code != 200:
- return False
- data = QueryDict(response.content)
- if 'signature' not in data:
- return False
- if 'request_token' not in data:
- return False
- params = [(key, value) for key,value in data.items() if key != 'signature']
- if not verify_signature(params, data['signature'], settings.SIMPLE_SSO_SECRET):
- return False
- return data['request_token']
-
-def verify_auth_token(data):
- """
- Verifies a Auth Token in a QueryDict. Returns a
- django.contrib.auth.models.User instance if successful or False.
- """
- if 'auth_token' not in data:
- return False
- if 'request_token' not in data:
- return False
- auth_token = data['auth_token']
- params = [('auth_token', auth_token), ('key', settings.SIMPLE_SSO_KEY)]
- signature = build_signature(params, settings.SIMPLE_SSO_SECRET)
- params.append(('signature', signature))
- url = urljoin(settings.SIMPLE_SSO_SERVER, 'verify') + '/'
- response = requests.get(url, params=dict(params))
- if response.status_code != 200:
- return False
- data = QueryDict(response.content)
- if 'signature' not in data:
- return False
- if 'user' not in data:
- return False
- params = [(key, value) for key,value in data.items() if key != 'signature']
- if not verify_signature(params, data['signature'], settings.SIMPLE_SSO_SECRET):
- return False
- return load_json_user(data['user'])
-
-def get_next(request):
- """
- Given a request, returns the URL where a user should be redirected to
- after login. Defaults to '/'
- """
- next = request.GET.get('next', None)
- if not next:
- return '/'
- netloc = urlparse(next)[1]
-
- # Heavier security check -- don't allow redirection to a different
- # host.
- # Taken from django.contrib.auth.views.login
- if netloc and netloc != request.get_host():
- return '/'
- return next
-
-def login_view(request):
- """
- Login view.
-
- Requests a Request Token and then redirects the User to the the SSO Server.
- """
- next = get_next(request)
- request.session['simple-sso-next'] = next
- request_token = get_request_token()
- if not request_token:
- return HttpResponseBadRequest()
- params = [('request_token', request_token), ('key', settings.SIMPLE_SSO_KEY)]
- signature = build_signature(params, settings.SIMPLE_SSO_SECRET)
- params.append(('signature', signature))
- query_string = urllib.urlencode(params)
- url = urljoin(settings.SIMPLE_SSO_SERVER, 'authorize') + '/'
- return HttpResponseRedirect('%s?%s' % (url, query_string))
-
-def authenticate_view(request):
- """
- Authentication view.
-
- Verifies the user token and logs the user in.
- """
- user = verify_auth_token(request.GET)
- if not user:
- return HttpResponseBadRequest()
- user.backend = "%s.%s" % (BACKEND.__module__, BACKEND.__class__.__name__)
- login(request, user)
- return HttpResponseRedirect(request.session.get('simple-sso-next', '/'))
View
72 simple_sso/sso_server/forms.py
@@ -1,72 +0,0 @@
-# -*- coding: utf-8 -*-
-from django import forms
-from simple_sso.signatures import verify_signature
-from simple_sso.sso_server.models import Token, Client
-
-
-class BaseForm(forms.Form):
- """
- Base Simple SSO form that verifies the key and signature.
- """
- signature = forms.CharField(max_length=64, min_length=64)
- key = forms.CharField(max_length=64)
-
- def __init__(self, *args, **kwargs):
- self.invalid_signature = False
- self.client = None
- return super(BaseForm, self).__init__(*args, **kwargs)
-
- def clean(self):
- data = super(BaseForm, self).clean()
- parameters = [(key, value) for key, value in data.items() if key != 'signature']
- client_key = data['key']
- try:
- self.client = Client.objects.get(key=client_key)
- except Client.DoesNotExist:
- raise forms.ValidationError('Invalid client key')
- secret = self.client.secret
- signature = data['signature']
- if not verify_signature(parameters, signature, secret):
- self.invalid_signature = True
- raise forms.ValidationError('Invalid signature')
- return data
-
-
-class RequestTokenRequestForm(BaseForm):
- """
- Form used to request a Request Token
- """
-
-
-class AuthorizeForm(BaseForm):
- """
- Authorize form used to retrieve a Auth Token using a Request Token
- """
- request_token = forms.CharField(max_length=64, min_length=64)
-
- def clean(self):
- data = super(AuthorizeForm, self).clean()
- request_token = data['request_token']
- try:
- token = Token.objects.get(request_token=request_token, client=self.client, user__isnull=True)
- except Token.DoesNotExist:
- raise forms.ValidationError('Invalid request token')
- data['token'] = token
- return data
-
-
-class VerificationForm(BaseForm):
- """
- Form used to verify a Auth Token
- """
- auth_token = forms.CharField(max_length=64, min_length=64)
-
- def clean(self):
- data = super(VerificationForm, self).clean()
- auth_token = data['auth_token']
- try:
- token = Token.objects.get(auth_token=auth_token, user__isnull=False, client=self.client)
- except Token.DoesNotExist:
- raise forms.ValidationError('Invalid auth token')
- data['token'] = token
- return data
View
49 simple_sso/sso_server/migrations/0001_initial.py
@@ -1,37 +1,38 @@
-# encoding: utf-8
+# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
+
class Migration(SchemaMigration):
def forwards(self, orm):
-
- # Adding model 'Client'
- db.create_table('sso_server_client', (
+ # Adding model 'Consumer'
+ db.create_table('sso_server_consumer', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('secret', self.gf('django.db.models.fields.CharField')(unique=True, max_length=64)),
- ('key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=64)),
- ('root_url', self.gf('django.db.models.fields.URLField')(max_length=200)),
+ ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)),
+ ('private_key', self.gf('django.db.models.fields.CharField')(default='zCZK89Dk5gFNVxt6Po3FYooDRUgIr8lkPqq8vDUJjBIIeRQKP1kAlsfGGY9kpuCW', unique=True, max_length=64)),
+ ('public_key', self.gf('django.db.models.fields.CharField')(default='BL7olRs8N2nN1a4fuhuAECASZG3ERgMH4GmdqI8dkUg79Ay80FBxERICTAoMWmg1', unique=True, max_length=64)),
))
- db.send_create_signal('sso_server', ['Client'])
+ db.send_create_signal('sso_server', ['Consumer'])
# Adding model 'Token'
db.create_table('sso_server_token', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('client', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['sso_server.Client'])),
- ('request_token', self.gf('django.db.models.fields.CharField')(unique=True, max_length=64)),
- ('auth_token', self.gf('django.db.models.fields.CharField')(unique=True, max_length=64)),
+ ('consumer', self.gf('django.db.models.fields.related.ForeignKey')(related_name='tokens', to=orm['sso_server.Consumer'])),
+ ('request_token', self.gf('django.db.models.fields.CharField')(default='FOcGALzcHN4D1Fl4RjsOnUUOSIK71QhHhWhLY9EpsrZ5xIJIBHvfZRGCM9KPMkZI', unique=True, max_length=64)),
+ ('access_token', self.gf('django.db.models.fields.CharField')(default='6iOTXc4LLS3fPIJl8wXmfsYPMm6wuwsvUQf7UTVnMX6TeFBpIM4apGOdKiHz8qUt', unique=True, max_length=64)),
+ ('timestamp', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
+ ('redirect_to', self.gf('django.db.models.fields.CharField')(max_length=255)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)),
))
db.send_create_signal('sso_server', ['Token'])
def backwards(self, orm):
-
- # Deleting model 'Client'
- db.delete_table('sso_server_client')
+ # Deleting model 'Consumer'
+ db.delete_table('sso_server_consumer')
# Deleting model 'Token'
db.delete_table('sso_server_token')
@@ -74,21 +75,23 @@ def backwards(self, orm):
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
- 'sso_server.client': {
- 'Meta': {'object_name': 'Client'},
+ 'sso_server.consumer': {
+ 'Meta': {'object_name': 'Consumer'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
- 'root_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
- 'secret': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
+ 'private_key': ('django.db.models.fields.CharField', [], {'default': "'yPw6AHFmcPFosghzRA2v14JjHjhgEoLlzmOI2luovaZPechIRBDJrRbhQOyJAyaB'", 'unique': 'True', 'max_length': '64'}),
+ 'public_key': ('django.db.models.fields.CharField', [], {'default': "'AK35mZLc8n1yKnNMRDqgyrOo31NoSCT1ZqnmZE9UeJZQEjmi8mSmjQmZdqOnLX4o'", 'unique': 'True', 'max_length': '64'})
},
'sso_server.token': {
'Meta': {'object_name': 'Token'},
- 'auth_token': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
- 'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sso_server.Client']"}),
+ 'access_token': ('django.db.models.fields.CharField', [], {'default': "'L8sKiHFRPEL6bN3R1QK2KHDDZ2F2MfyZcJrSr8GkYk64AFa9t0ZTpCw7zbUQiOmF'", 'unique': 'True', 'max_length': '64'}),
+ 'consumer': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tokens'", 'to': "orm['sso_server.Consumer']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'request_token': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+ 'redirect_to': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'request_token': ('django.db.models.fields.CharField', [], {'default': "'Es5jnmA77SZcy6dqdbSYGBg62iyPKisEkbd7w2RQsAxPAd8qYnewfPTGEgNGI71J'", 'unique': 'True', 'max_length': '64'}),
+ 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
}
}
- complete_apps = ['sso_server']
+ complete_apps = ['sso_server']
View
76 simple_sso/sso_server/migrations/0002_auto__add_field_token_created.py
@@ -1,76 +0,0 @@
-# encoding: utf-8
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-class Migration(SchemaMigration):
-
- def forwards(self, orm):
-
- # Adding field 'Token.created'
- db.add_column('sso_server_token', 'created', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now), keep_default=False)
-
-
- def backwards(self, orm):
-
- # Deleting field 'Token.created'
- db.delete_column('sso_server_token', 'created')
-
-
- models = {
- 'auth.group': {
- 'Meta': {'object_name': 'Group'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
- 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
- },
- 'auth.permission': {
- 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
- 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
- },
- 'auth.user': {
- 'Meta': {'object_name': 'User'},
- 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
- 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
- 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
- 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
- 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
- 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
- },
- 'contenttypes.contenttype': {
- 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
- 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
- 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
- },
- 'sso_server.client': {
- 'Meta': {'object_name': 'Client'},
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'key': ('django.db.models.fields.CharField', [], {'default': "'O8ggD53KCK1fllehgC5ow3OCZIqt7iHexh7Orshag934S29SRky3o2MtZKXy0DKi'", 'unique': 'True', 'max_length': '64'}),
- 'root_url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
- 'secret': ('django.db.models.fields.CharField', [], {'default': "'n5vypeYiFYAc0uGgUU6znyAJ4eSvBbhw2ahrF803yIeOUcQBZs3uYrBqXhpg5rwx'", 'unique': 'True', 'max_length': '64'})
- },
- 'sso_server.token': {
- 'Meta': {'object_name': 'Token'},
- 'auth_token': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
- 'client': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sso_server.Client']"}),
- 'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'request_token': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
- 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
- }
- }
-
- complete_apps = ['sso_server']
View
65 simple_sso/sso_server/models.py
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
from django.db import models
-from django.db.models.query_utils import Q
from simple_sso.utils import gen_secret_key
import datetime
@@ -11,55 +10,45 @@ def gen_client_key(field):
"""
def _genkey():
key = gen_secret_key(64)
- while Client.objects.filter(**{field: key}).exists():
+ while Consumer.objects.filter(**{field: key}).exists():
key = gen_secret_key(64)
return key
return _genkey
-
-class TokenManager(models.Manager):
- def create_for_client(self, client):
- """
- Create a new token for a client object.
- """
- request_token = gen_secret_key(64)
- auth_token = gen_secret_key(64)
- # check unique constraints
- while self.filter(Q(request_token=request_token) | Q(auth_token=auth_token)).exists():
- request_token = gen_secret_key(64)
- auth_token = gen_secret_key(64)
- return self.create(
- client=client,
- request_token=request_token,
- auth_token=auth_token,
- )
-
-
-class Client(models.Model):
+def gen_token_field(field):
"""
- A client. If secret/key change, the client website has to be updated too!
+ Helper function to give default values to Client.secret and Client.key
"""
- secret = models.CharField(max_length=64, unique=True, default=gen_client_key('secret'))
- key = models.CharField(max_length=64, unique=True, default=gen_client_key('key'))
- root_url = models.URLField(verify_exists=False)
+ def _genkey():
+ key = gen_secret_key(64)
+ while Token.objects.filter(**{field: key}).exists():
+ key = gen_secret_key(64)
+ return key
+ return _genkey
+
+
+class Consumer(models.Model):
+ name = models.CharField(max_length=100, unique=True)
+ private_key = models.CharField(max_length=64, unique=True, default=gen_client_key('private_key'))
+ public_key = models.CharField(max_length=64, unique=True, default=gen_client_key('public_key'))
def __unicode__(self):
- return self.root_url
+ return self.name
def rotate_keys(self):
- self.secret = gen_client_key('secret')()
- self.key = gen_client_key('key')()
+ self.secret = gen_client_key('private_key')()
+ self.key = gen_client_key('public_key')()
self.save()
class Token(models.Model):
- """
- An auth token used to authenticate a user.
- """
- client = models.ForeignKey(Client)
- request_token = models.CharField(max_length=64, unique=True)
- auth_token = models.CharField(max_length=64, unique=True)
+ consumer = models.ForeignKey(Consumer, related_name='tokens')
+ request_token = models.CharField(unique=True, max_length=64, default=gen_token_field('request_token'))
+ access_token = models.CharField(unique=True, max_length=64, default=gen_token_field('access_token'))
+ timestamp = models.DateTimeField(default=datetime.datetime.now)
+ redirect_to = models.CharField(max_length=255)
user = models.ForeignKey('auth.User', null=True)
- created = models.DateTimeField(default=datetime.datetime.now)
-
- objects = TokenManager()
+
+ def refresh(self):
+ self.timestamp = datetime.datetime.now()
+ self.save()
View
164 simple_sso/sso_server/server.py
@@ -0,0 +1,164 @@
+# -*- coding: utf-8 -*-
+import urlparse
+from django.conf.urls import patterns, url
+from django.contrib import admin
+from django.contrib.admin.options import ModelAdmin
+from django.core.urlresolvers import reverse
+from django.http import (HttpResponseForbidden, HttpResponseBadRequest, HttpResponseRedirect, QueryDict)
+from django.views.generic.base import View
+from itsdangerous import URLSafeTimedSerializer
+from simple_sso.sso_server.models import Token, Consumer
+import datetime
+import urllib
+from webservices.models import Provider
+from webservices.sync import provider_for_django
+
+
+class BaseProvider(Provider):
+ max_age = 5
+
+ def __init__(self, server):
+ self.server = server
+
+ def get_private_key(self, public_key):
+ try:
+ self.consumer = Consumer.objects.get(public_key=public_key)
+ except Consumer.DoesNotExist:
+ return None
+ return self.consumer.private_key
+
+
+class RequestTokenProvider(BaseProvider):
+ def provide(self, data):
+ redirect_to = data['redirect_to']
+ token = Token.objects.create(consumer=self.consumer, redirect_to=redirect_to)
+ return {'request_token': token.request_token}
+
+
+class AuthorizeView(View):
+ """
+ The client get's redirected to this view with the `request_token` obtained
+ by the Request Token Request by the client application beforehand.
+
+ This view checks if the user is logged in on the server application and if
+ that user has the necessary rights.
+
+ If the user is not logged in, the user is prompted to log in.
+ """
+ server = None
+
+ def get(self, request):
+ request_token = request.GET.get('token', None)
+ if not request_token:
+ return self.missing_token_argument()
+ try:
+ self.token = Token.objects.select_related('consumer').get(request_token=request_token)
+ except Token.DoesNotExist:
+ return self.token_not_found()
+ if not self.check_token_timeout():
+ return self.token_timeout()
+ self.token.refresh()
+ if request.user.is_authenticated():
+ return self.handle_authenticated_user()
+ else:
+ return self.handle_unauthenticated_user()
+
+ def missing_token_argument(self):
+ return HttpResponseBadRequest('Token missing')
+
+ def token_not_found(self):
+ return HttpResponseForbidden('Token not found')
+
+ def token_timeout(self):
+ return HttpResponseForbidden('Token timed out')
+
+ def check_token_timeout(self):
+ delta = datetime.datetime.now() - self.token.timestamp
+ if delta > self.server.token_timeout:
+ self.token.delete()
+ return False
+ else:
+ return True
+
+ def handle_authenticated_user(self):
+ if self.server.has_access(self.request.user, self.token.consumer):
+ return self.success()
+ else:
+ return self.access_denied()
+
+ def handle_unauthenticated_user(self):
+ next = '%s?%s' % (self.request.path, urllib.urlencode([('token', self.token.request_token)]))
+ url = '%s?%s' % (reverse(self.server.auth_view_name), urllib.urlencode([('next', next)]))
+ return HttpResponseRedirect(url)
+
+ def access_denied(self):
+ return HttpResponseForbidden("Access denied")
+
+ def success(self):
+ self.token.user = self.request.user
+ self.token.save()
+ serializer = URLSafeTimedSerializer(self.token.consumer.private_key)
+ parse_result = urlparse.urlparse(self.token.redirect_to)
+ query_dict = QueryDict(parse_result.query, mutable=True)
+ query_dict['access_token'] = serializer.dumps(self.token.access_token)
+ url = urlparse.urlunparse((parse_result.scheme, parse_result.netloc, parse_result.path, '', query_dict.urlencode(), ''))
+ return HttpResponseRedirect(url)
+
+
+class VerificationProvider(BaseProvider, AuthorizeView):
+ def provide(self, data):
+ token = data['access_token']
+ try:
+ self.token = Token.objects.select_related('user').get(access_token=token, consumer=self.consumer)
+ except Token.DoesNotExist:
+ return self.token_not_found()
+ if not self.check_token_timeout():
+ return self.token_timeout()
+ if not self.token.user:
+ return self.token_not_bound()
+ return self.server.get_user_data(self.token.user, self.consumer)
+
+ def token_not_bound(self):
+ return HttpResponseForbidden("Invalid token")
+
+
+class ConsumerAdmin(ModelAdmin):
+ readonly_fields = ['public_key', 'private_key']
+
+
+class Server(object):
+ request_token_provider = RequestTokenProvider
+ authorize_view = AuthorizeView
+ verification_provider = VerificationProvider
+ token_timeout = datetime.timedelta(minutes=5)
+ client_admin = ConsumerAdmin
+ auth_view_name = 'django.contrib.auth.views.login'
+
+ def __init__(self, **kwargs):
+ for key, value in kwargs.items():
+ setattr(self, key, value)
+ self.register_admin()
+
+ def register_admin(self):
+ admin.site.register(Consumer, self.client_admin)
+
+ def has_access(self, user, consumer):
+ return True
+
+ def get_user_data(self, user, consumer):
+ return {
+ 'username': user.username,
+ 'email': user.email,
+ 'first_name': user.first_name,
+ 'last_name': user.last_name,
+ 'is_staff': False,
+ 'is_superuser': False,
+ }
+
+ def get_urls(self):
+ return patterns('',
+ url(r'^request-token/$', provider_for_django(self.request_token_provider(server=self)), name='simple-sso-request-token'),
+ url(r'^authorize/$', self.authorize_view.as_view(server=self), name='simple-sso-authorize'),
+ url(r'^verify/$', provider_for_django(self.verification_provider(server=self)), name='simple-sso-verify'),
+ )
+
View
15 simple_sso/test_urls.py
@@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-
-from django.conf.urls.defaults import patterns, url, include
+from django.conf import settings
+from django.conf.urls import patterns, url, include
from django.http import HttpResponse
-from simple_sso.server import SimpleSSOServer
+from simple_sso.sso_client.client import Client
+from simple_sso.sso_server.server import Server
-test_server = SimpleSSOServer()
+test_server = Server()
+test_client = Client(settings.SSO_SERVER, settings.SSO_PUBLIC_KEY, settings.SSO_PRIVATE_KEY)
urlpatterns = patterns('',
- url('^server/', include(test_server.get_urls())),
- url('^client/', include('simple_sso.sso_client.urls')),
+ url(r'^server/', include(test_server.get_urls())),
+ url(r'^client/', include(test_client.get_urls())),
url(r'^login/$', 'django.contrib.auth.views.login', {'template_name': 'admin/login.html'}),
url('^$', lambda request: HttpResponse('home'), name='root')
-)
+)
View
244 simple_sso/tests.py
@@ -1,23 +1,18 @@
# -*- coding: utf-8 -*-
+from django.conf import settings
from django.contrib.auth import get_user
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
-from django.http import (HttpResponseRedirect, HttpResponse,
- HttpResponseForbidden, HttpResponseBadRequest)
-from django.test.client import Client as TestClient, RequestFactory
+from django.http import HttpResponseRedirect, HttpResponse
+from django.test.client import Client
from django.test.testcases import TestCase
-from django.utils import simplejson
-from simple_sso.server import AuthorizeView
-from simple_sso.signatures import build_signature
-from simple_sso.sso_client.utils import load_json_user
-from simple_sso.sso_server.models import Client, Token
-from simple_sso.test_urls import test_server
+from simple_sso.sso_server.models import Token, Consumer
+from simple_sso.test_urls import test_client
from simple_sso.test_utils.context_managers import (SettingsOverride,
UserLoginContext)
-from simple_sso.utils import SIMPLE_KEYS, gen_secret_key
-import datetime
+from simple_sso.utils import gen_secret_key
import urlparse
-
+from webservices.sync import DjangoTestingConsumer
class SimpleSSOTests(TestCase):
@@ -26,175 +21,92 @@ class SimpleSSOTests(TestCase):
def setUp(self):
import requests
def get(url, params={}, headers={}, cookies=None, auth=None, **kwargs):
- testclient = TestClient()
- return testclient.get(url, params)
+ return Client().get(url, params)
requests.get = get
- self.client.logout()
+ test_client.consumer = DjangoTestingConsumer(Client(), test_client.server_url, test_client.public_key, test_client.private_key)
+
+ def _get_consumer(self):
+ return Consumer.objects.create(name='test', private_key=settings.SSO_PRIVATE_KEY, public_key=settings.SSO_PUBLIC_KEY)
def test_walkthrough(self):
# create a user and a client
+ client = Client()
USERNAME = PASSWORD = 'myuser'
server_user = User.objects.create_user(USERNAME, 'my@user.com', PASSWORD)
- client = Client.objects.create(root_url='/client/')
- with SettingsOverride(SIMPLE_SSO_KEY=client.key, SIMPLE_SSO_SECRET=client.secret):
- # verify theres no tokens yet
- self.assertEqual(Token.objects.count(), 0)
- response = self.client.get(reverse('simple-sso-login'))
- # there should be a token now
- self.assertEqual(Token.objects.count(), 1)
- # this should be a HttpResponseRedirect
- self.assertEqual(response.status_code, HttpResponseRedirect.status_code)
- # check that it's the URL we expect
- url = urlparse.urlparse(response['Location'])
- path = url.path
- self.assertEqual(path, reverse('simple-sso-authorize'))
- # follow that redirect
- response = self.client.get(response['Location'])
- # now we should have another redirect to the login
- self.assertEqual(response.status_code, HttpResponseRedirect.status_code)
- # check that the URL is correct
- url = urlparse.urlparse(response['Location'])
- path = url.path
- self.assertEqual(path, reverse('django.contrib.auth.views.login'))
- # follow that redirect
- login_url = response['Location']
- response = self.client.get(login_url)
- # now we should have a 200
- self.assertEqual(response.status_code, HttpResponse.status_code)
- # and log in using the username/password from above
- response = self.client.post(login_url, {'username': USERNAME, 'password': PASSWORD})
- # now we should have a redirect back to the authorize view
- self.assertEqual(response.status_code, HttpResponseRedirect.status_code)
- # check that it's the URL we expect
- url = urlparse.urlparse(response['Location'])
- path = url.path
- self.assertEqual(path, reverse('simple-sso-authorize'))
- # follow that redirect
- response = self.client.get(response['Location'])
- # this should again be a redirect
- self.assertEqual(response.status_code, HttpResponseRedirect.status_code)
- # this time back to the client app, confirm that!
- url = urlparse.urlparse(response['Location'])
- path = url.path
- self.assertEqual(path, reverse('simple-sso-authenticate'))
- # follow it again
- response = self.client.get(response['Location'])
- # again a redirect! This time to /
- url = urlparse.urlparse(response['Location'])
- path = url.path
- self.assertEqual(path, reverse('root'))
- # if we follow to root now, we should be logged in
- response = self.client.get(response['Location'])
- client_user = get_user(self.client)
- self.assertEqual(client_user.password, '!')
- self.assertNotEqual(server_user.password, '!')
- for key in SIMPLE_KEYS:
- self.assertEqual(getattr(client_user, key), getattr(server_user, key))
+ consumer = self._get_consumer()
+ # verify theres no tokens yet
+ self.assertEqual(Token.objects.count(), 0)
+ response = client.get(reverse('simple-sso-login'))
+ # there should be a token now
+ self.assertEqual(Token.objects.count(), 1)
+ # this should be a HttpResponseRedirect
+ self.assertEqual(response.status_code, HttpResponseRedirect.status_code)
+ # check that it's the URL we expect
+ url = urlparse.urlparse(response['Location'])
+ path = url.path
+ self.assertEqual(path, reverse('simple-sso-authorize'))
+ # follow that redirect
+ response = client.get(response['Location'])
+ # now we should have another redirect to the login
+ self.assertEqual(response.status_code, HttpResponseRedirect.status_code, response.content)
+ # check that the URL is correct
+ url = urlparse.urlparse(response['Location'])
+ path = url.path
+ self.assertEqual(path, reverse('django.contrib.auth.views.login'))
+ # follow that redirect
+ login_url = response['Location']
+ response = client.get(login_url)
+ # now we should have a 200
+ self.assertEqual(response.status_code, HttpResponse.status_code)
+ # and log in using the username/password from above
+ response = client.post(login_url, {'username': USERNAME, 'password': PASSWORD})
+ # now we should have a redirect back to the authorize view
+ self.assertEqual(response.status_code, HttpResponseRedirect.status_code)
+ # check that it's the URL we expect
+ url = urlparse.urlparse(response['Location'])
+ path = url.path
+ self.assertEqual(path, reverse('simple-sso-authorize'))
+ # follow that redirect
+ response = client.get(response['Location'])
+ # this should again be a redirect
+ self.assertEqual(response.status_code, HttpResponseRedirect.status_code)
+ # this time back to the client app, confirm that!
+ url = urlparse.urlparse(response['Location'])
+ path = url.path
+ self.assertEqual(path, reverse('simple-sso-authenticate'))
+ # follow it again
+ response = client.get(response['Location'])
+ # again a redirect! This time to /
+ url = urlparse.urlparse(response['Location'])
+ path = url.path
+ self.assertEqual(path, reverse('root'))
+ # if we follow to root now, we should be logged in
+ response = client.get(response['Location'])
+ client_user = get_user(client)
+ self.assertEqual(client_user.password, '!')
+ self.assertNotEqual(server_user.password, '!')
+ for key in ['username', 'email', 'first_name', 'last_name']:
+ self.assertEqual(getattr(client_user, key), getattr(server_user, key))
def test_user_already_logged_in(self):
+ client = Client()
# create a user and a client
USERNAME = PASSWORD = 'myuser'
server_user = User.objects.create_user(USERNAME, 'my@user.com', PASSWORD)
- client = Client.objects.create(root_url='/client/')
- with SettingsOverride(SIMPLE_SSO_KEY=client.key, SIMPLE_SSO_SECRET=client.secret):
- with UserLoginContext(self, server_user):
- # try logging in and auto-follow all 302s
- self.client.get(reverse('simple-sso-login'), follow=True)
- # check the user
- client_user = get_user(self.client)
- self.assertEqual(client_user.password, '!')
- self.assertNotEqual(server_user.password, '!')
- for key in SIMPLE_KEYS:
- self.assertEqual(getattr(client_user, key), getattr(server_user, key))
+ consumer = self._get_consumer()
+ with UserLoginContext(self, server_user):
+ # try logging in and auto-follow all 302s
+ client.get(reverse('simple-sso-login'), follow=True)
+ # check the user
+ client_user = get_user(client)
+ self.assertEqual(client_user.password, '!')
+ self.assertNotEqual(server_user.password, '!')
+ for key in ['username', 'email', 'first_name', 'last_name']:
+ self.assertEqual(getattr(client_user, key), getattr(server_user, key))
def test_custom_keygen(self):
# WARNING: The following test uses a key generator function that is
# highly insecure and should never under any circumstances be used in
# a production enivornment
with SettingsOverride(SIMPLE_SSO_KEYGENERATOR=lambda length: 'test'):
self.assertEqual(gen_secret_key(40), 'test')
-
- def test_load_json_user(self):
- userdata = {
- 'username': 'mytestuser',
- 'password': 'testpassword',
- 'first_name': 'mytestuser',
- 'last_name': 'mytestuser',
- 'email': 'mytestuser@example.com',
- 'is_staff': True,
- 'is_superuser': False,
- 'permissions': []
- }
- jsondata = simplejson.dumps(userdata)
- user = load_json_user(jsondata)
- for key in SIMPLE_KEYS:
- self.assertEqual(getattr(user, key), userdata[key])
- self.assertFalse(user.check_password('testpassword'))
-
- def test_request_token_view_invalid_signature(self):
- client = Client.objects.create(root_url='/client/')
- data = {'key': client.key, 'signature': 'x' * 64}
- response = self.client.get(reverse('simple-sso-request-token'), data)
- self.assertEqual(response.status_code, HttpResponseForbidden.status_code)
-
- def test_request_token_view_bad_request(self):
- data = {'key': 'y' * 64, 'signature': 'x' * 64}
- response = self.client.get(reverse('simple-sso-request-token'), data)
- self.assertEqual(response.status_code, HttpResponseBadRequest.status_code)
-
- def test_authorize_view_token_timeout(self):
- client = Client.objects.create(root_url='/client/')
- token = Token.objects.create_for_client(client)
- token.created = datetime.datetime.now() - test_server.token_timeout - datetime.timedelta(hours=1)
- token.save()
- data = {
- 'key': client.key,
- 'request_token': token.request_token,
- }
- data['signature'] = build_signature(data.items(), client.secret)
- response = self.client.get(reverse('simple-sso-authorize'), data)
- self.assertEqual(response.status_code, HttpResponseForbidden.status_code)
-
- def test_authorize_view_invalid_request_token(self):
- client = Client.objects.create(root_url='/client/')
- data = {
- 'key': client.key,
- 'request_token': 'x' * 64,
- }
- data['signature'] = build_signature(data.items(), client.secret)
- response = self.client.get(reverse('simple-sso-authorize'), data)
- self.assertEqual(response.status_code, HttpResponseBadRequest.status_code)
-
- def test_authorize_view_invalid_signature(self):
- client = Client.objects.create(root_url='/client/')
- token = Token.objects.create_for_client(client)
- token.created = datetime.datetime.now() - test_server.token_timeout - datetime.timedelta(hours=1)
- token.save()
- data = {
- 'key': client.key,
- 'request_token': token.request_token,
- 'signature': 'x' * 64
- }
- response = self.client.get(reverse('simple-sso-authorize'), data)
- self.assertEqual(response.status_code, HttpResponseForbidden.status_code)
-
- def test_authorize_view_no_access(self):
- class NoAccessAuthorizeView(AuthorizeView):
- server = test_server
-
- def has_access(self):
- return False
- client = Client.objects.create(root_url='/client/')
- token = Token.objects.create_for_client(client)
- data = {
- 'key': client.key,
- 'request_token': token.request_token,
- }
- data['signature'] = build_signature(data.items(), client.secret)
- request = RequestFactory().get(reverse('simple-sso-authorize'), data)
- USERNAME = PASSWORD = 'myuser'
- server_user = User.objects.create_user(USERNAME, 'my@user.com', PASSWORD)
- request.user = server_user
- view = NoAccessAuthorizeView.as_view()
- response = view(request)
- self.assertEqual(response.status_code, HttpResponseForbidden.status_code)
View
9 simple_sso/utils.py
@@ -6,15 +6,6 @@
KEY_CHARACTERS = string.letters + string.digits
-SIMPLE_KEYS = [
- 'username',
- 'first_name',
- 'last_name',
- 'email',
- 'is_staff',
- 'is_superuser',
-]
-
def default_gen_secret_key(length=40):
return ''.join([random.choice(KEY_CHARACTERS) for _ in range(length)])

0 comments on commit 8faa216

Please sign in to comment.