Permalink
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 27, 2012
View
@@ -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
@@ -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
@@ -1 +1 @@
-__version__ = '0.6.0'
+__version__ = '0.9.0'
View
@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-
View
@@ -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
@@ -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
@@ -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'),
+ )
@@ -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'),
-)
Oops, something went wrong.

0 comments on commit 8faa216

Please sign in to comment.