From 02c2ae99f7658cec92c52afb9075e19e3d2ee50d Mon Sep 17 00:00:00 2001 From: juanifioren Date: Mon, 12 Jan 2015 19:13:48 -0300 Subject: [PATCH] Refactoring. Add views for client creation. --- openid_provider/lib/endpoints/authorize.py | 33 ++++-- openid_provider/lib/utils/decorators.py | 18 +++ openid_provider/lib/utils/params.py | 2 + openid_provider/migrations/0001_initial.py | 108 ++++++++++++++++++ openid_provider/models.py | 21 +++- .../templates/openid_provider/authorize.html | 6 +- .../templates/openid_provider/base.html | 4 + .../templates/openid_provider/clients.html | 33 ++++++ .../templates/registration/login.html | 39 +++++++ .../templates/registration/logout.html | 19 +++ openid_provider/urls.py | 14 ++- openid_provider/views/__init__.py | 0 openid_provider/views/clients.py | 14 +++ .../{views.py => views/endpoints.py} | 10 +- 14 files changed, 300 insertions(+), 21 deletions(-) create mode 100644 openid_provider/lib/utils/decorators.py create mode 100644 openid_provider/migrations/0001_initial.py create mode 100644 openid_provider/templates/openid_provider/clients.html create mode 100644 openid_provider/templates/registration/login.html create mode 100644 openid_provider/templates/registration/logout.html create mode 100644 openid_provider/views/__init__.py create mode 100644 openid_provider/views/clients.py rename openid_provider/{views.py => views/endpoints.py} (88%) diff --git a/openid_provider/lib/endpoints/authorize.py b/openid_provider/lib/endpoints/authorize.py index 03c9a68..f12ac97 100644 --- a/openid_provider/lib/endpoints/authorize.py +++ b/openid_provider/lib/endpoints/authorize.py @@ -41,7 +41,7 @@ class AuthorizeEndpoint(object): self.params.client_id = self.query_dict.get('client_id', '') self.params.redirect_uri = self.query_dict.get('redirect_uri', '') self.params.response_type = self.query_dict.get('response_type', '') - self.params.scope = self.query_dict.get('scope', '') + self.params.scope = self.query_dict.get('scope', '').split() self.params.state = self.query_dict.get('state', '') def _extract_implicit_params(self): @@ -57,8 +57,11 @@ class AuthorizeEndpoint(object): if not self.params.redirect_uri: raise RedirectUriError() - if not ('openid' in self.params.scope.split()): - raise AuthorizeError(self.params.redirect_uri, 'invalid_scope', self.grant_type) + if not ('openid' in self.params.scope): + raise AuthorizeError( + self.params.redirect_uri, + 'invalid_scope', + self.grant_type) try: self.client = Client.objects.get(client_id=self.params.client_id) @@ -66,8 +69,13 @@ class AuthorizeEndpoint(object): if not (self.params.redirect_uri in self.client.redirect_uris): raise RedirectUriError() - if not (self.grant_type) or not (self.params.response_type == self.client.response_type): - raise AuthorizeError(self.params.redirect_uri, 'unsupported_response_type', self.grant_type) + if not (self.grant_type) or \ + not (self.params.response_type == self.client.response_type): + + raise AuthorizeError( + self.params.redirect_uri, + 'unsupported_response_type', + self.grant_type) except Client.DoesNotExist: raise ClientIdError() @@ -75,7 +83,10 @@ class AuthorizeEndpoint(object): def create_response_uri(self, allow): if not allow: - raise AuthorizeError(self.params.redirect_uri, 'access_denied', self.grant_type) + raise AuthorizeError( + self.params.redirect_uri, + 'access_denied', + self.grant_type) try: self.validate_params() @@ -110,7 +121,7 @@ class AuthorizeEndpoint(object): id_token = encode_id_token(id_token_dic, self.client.client_secret) - # TODO: Check if response_type is 'id_token token' and + # TODO: Check if response_type is 'id_token token' then # add access_token to the fragment. uri = self.params.redirect_uri + \ '#token_type={0}&id_token={1}&expires_in={2}'.format( @@ -118,9 +129,13 @@ class AuthorizeEndpoint(object): id_token, 60*10) except: - raise AuthorizeError(self.params.redirect_uri, 'server_error', self.grant_type) + raise AuthorizeError( + self.params.redirect_uri, + 'server_error', + self.grant_type) # Add state if present. - uri = uri + ('&state={0}'.format(self.params.state) if self.params.state else '') + uri = uri + ('&state={0}'.format(self.params.state) + if self.params.state else '') return uri \ No newline at end of file diff --git a/openid_provider/lib/utils/decorators.py b/openid_provider/lib/utils/decorators.py new file mode 100644 index 0000000..fb91ecb --- /dev/null +++ b/openid_provider/lib/utils/decorators.py @@ -0,0 +1,18 @@ +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.contrib.auth.decorators import user_passes_test + + +def staff_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None): + """ + Decorator for views that checks that the user is logged in and is staff, + redirecting to the log-in page if necessary. + """ + actual_decorator = user_passes_test( + lambda u: u.is_authenticated() and u.is_staff, + login_url=login_url, + redirect_field_name=redirect_field_name + ) + if function: + return actual_decorator(function) + + return actual_decorator \ No newline at end of file diff --git a/openid_provider/lib/utils/params.py b/openid_provider/lib/utils/params.py index 927d507..94f8661 100644 --- a/openid_provider/lib/utils/params.py +++ b/openid_provider/lib/utils/params.py @@ -1,2 +1,4 @@ + + class Params(object): pass \ No newline at end of file diff --git a/openid_provider/migrations/0001_initial.py b/openid_provider/migrations/0001_initial.py new file mode 100644 index 0000000..67d9e56 --- /dev/null +++ b/openid_provider/migrations/0001_initial.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Client', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(default=b'', max_length=100)), + ('client_id', models.CharField(unique=True, max_length=255)), + ('client_secret', models.CharField(unique=True, max_length=255)), + ('client_type', models.CharField(max_length=20, choices=[(b'confidential', b'Confidential'), (b'public', b'Public')])), + ('response_type', models.CharField(max_length=30, choices=[(b'code', b'code (Authorization Code Flow)'), (b'id_token', b'id_token (Implicit Flow)'), (b'id_token token', b'id_token token (Implicit Flow)')])), + ('_scope', models.TextField(default=b'')), + ('_redirect_uris', models.TextField(default=b'')), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='Code', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('code', models.CharField(unique=True, max_length=255)), + ('expires_at', models.DateTimeField()), + ('_scope', models.TextField(default=b'')), + ('client', models.ForeignKey(to='openid_provider.Client')), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='Token', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('access_token', models.CharField(unique=True, max_length=255)), + ('expires_at', models.DateTimeField()), + ('_scope', models.TextField(default=b'')), + ('_id_token', models.TextField()), + ('client', models.ForeignKey(to='openid_provider.Client')), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='UserInfo', + fields=[ + ('user', models.OneToOneField(primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), + ('given_name', models.CharField(default=b'', max_length=255)), + ('family_name', models.CharField(default=b'', max_length=255)), + ('middle_name', models.CharField(default=b'', max_length=255)), + ('nickname', models.CharField(default=b'', max_length=255)), + ('preferred_username', models.CharField(default=b'', max_length=255)), + ('profile', models.URLField(default=b'')), + ('picture', models.URLField(default=b'')), + ('website', models.URLField(default=b'')), + ('email_verified', models.BooleanField(default=False)), + ('gender', models.CharField(default=b'', max_length=100)), + ('birthdate', models.DateField()), + ('zoneinfo', models.CharField(default=b'', max_length=100)), + ('locale', models.CharField(default=b'', max_length=100)), + ('phone_number', models.CharField(default=b'', max_length=255)), + ('phone_number_verified', models.BooleanField(default=False)), + ('address_formatted', models.CharField(default=b'', max_length=255)), + ('address_street_address', models.CharField(default=b'', max_length=255)), + ('address_locality', models.CharField(default=b'', max_length=255)), + ('address_region', models.CharField(default=b'', max_length=255)), + ('address_postal_code', models.CharField(default=b'', max_length=255)), + ('address_country', models.CharField(default=b'', max_length=255)), + ('updated_at', models.DateTimeField()), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='token', + name='user', + field=models.ForeignKey(to=settings.AUTH_USER_MODEL), + preserve_default=True, + ), + migrations.AddField( + model_name='code', + name='user', + field=models.ForeignKey(to=settings.AUTH_USER_MODEL), + preserve_default=True, + ), + migrations.AddField( + model_name='client', + name='user', + field=models.ForeignKey(to=settings.AUTH_USER_MODEL), + preserve_default=True, + ), + ] diff --git a/openid_provider/models.py b/openid_provider/models.py index a45d7ea..c4769b2 100644 --- a/openid_provider/models.py +++ b/openid_provider/models.py @@ -54,7 +54,15 @@ class Code(models.Model): client = models.ForeignKey(Client) code = models.CharField(max_length=255, unique=True) expires_at = models.DateTimeField() - scope = models.TextField() # TODO: add getter and setter for this. + + _scope = models.TextField(default='') + def scope(): + def fget(self): + return self._scope.split() + def fset(self, value): + self._scope = ' '.join(value) + return locals() + scope = property(**scope()) def has_expired(self): return timezone.now() >= self.expires_at @@ -64,9 +72,16 @@ class Token(models.Model): user = models.ForeignKey(User) client = models.ForeignKey(Client) access_token = models.CharField(max_length=255, unique=True) - refresh_token = models.CharField(max_length=255, unique=True) expires_at = models.DateTimeField() - scope = models.TextField() # TODO: add getter and setter for this. + + _scope = models.TextField(default='') + def scope(): + def fget(self): + return self._scope.split() + def fset(self, value): + self._scope = ' '.join(value) + return locals() + scope = property(**scope()) _id_token = models.TextField() def id_token(): diff --git a/openid_provider/templates/openid_provider/authorize.html b/openid_provider/templates/openid_provider/authorize.html index 115dc90..a6f3363 100644 --- a/openid_provider/templates/openid_provider/authorize.html +++ b/openid_provider/templates/openid_provider/authorize.html @@ -11,18 +11,18 @@

Request for Permission

-

Client {{ client.name }} would like to access this information of you ...

+

Client {{ client.name }} would like to access this information of you ...

{% csrf_token %} - +
    - {% for scope in params.scope.split %} + {% for scope in params.scope %} {% if scope != 'openid' %}
  • {{ scope | capfirst }}
  • {% endif %} diff --git a/openid_provider/templates/openid_provider/base.html b/openid_provider/templates/openid_provider/base.html index cfdd022..55184e7 100644 --- a/openid_provider/templates/openid_provider/base.html +++ b/openid_provider/templates/openid_provider/base.html @@ -9,9 +9,13 @@ diff --git a/openid_provider/templates/openid_provider/clients.html b/openid_provider/templates/openid_provider/clients.html new file mode 100644 index 0000000..7b53a06 --- /dev/null +++ b/openid_provider/templates/openid_provider/clients.html @@ -0,0 +1,33 @@ +{% extends "openid_provider/base.html" %} + +{% load i18n %} + +{% block content %} + +
    +
    +
    + +
    +
    +
    +
    +
    +

    Clients

    +
    +
    +
    + {% for client in object_list %} + {{ client.name }} + {% empty %} +
    No clients yet.
    + {% endfor %} +
    +
    +
    +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/openid_provider/templates/registration/login.html b/openid_provider/templates/registration/login.html new file mode 100644 index 0000000..171f0ab --- /dev/null +++ b/openid_provider/templates/registration/login.html @@ -0,0 +1,39 @@ +{% extends "openid_provider/base.html" %} + +{% load i18n %} + +{% block content %} + +
    +
    +
    +
    +

    Please sign in

    +
    + {% if form.errors %} + + {% endif %} + + {% csrf_token %} +
    +
    + +
    +
    + +
    +
    + +
    + + +
    + +
    +
    +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/openid_provider/templates/registration/logout.html b/openid_provider/templates/registration/logout.html new file mode 100644 index 0000000..bd9de8a --- /dev/null +++ b/openid_provider/templates/registration/logout.html @@ -0,0 +1,19 @@ +{% extends "openid_provider/base.html" %} + +{% load i18n %} + +{% block content %} + +
    +
    +
    +
    +

    Logged out

    +
    +

    See you next time.

    +
    +
    +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/openid_provider/urls.py b/openid_provider/urls.py index 99b8274..9d8f58e 100644 --- a/openid_provider/urls.py +++ b/openid_provider/urls.py @@ -1,12 +1,18 @@ from django.conf.urls import patterns, include, url from django.views.decorators.csrf import csrf_exempt -from . import views +from openid_provider.views import clients +from openid_provider.views import endpoints urlpatterns = patterns('', - url(r'^authorize/$', views.AuthorizeView.as_view(), name='authorize'), - url(r'^token/$', csrf_exempt(views.TokenView.as_view()), name='token'), - url(r'^userinfo/$', csrf_exempt(views.userinfo), name='userinfo'), + url(r'^authorize/$', endpoints.AuthorizeView.as_view(), name='authorize'), + url(r'^token/$', csrf_exempt(endpoints.TokenView.as_view()), name='token'), + url(r'^userinfo/$', csrf_exempt(endpoints.userinfo), name='userinfo'), + + url(r'^login/$', 'django.contrib.auth.views.login', name='login'), + url(r'^logout/$', 'django.contrib.auth.views.logout', name='logout'), + + url(r'^clients/$', clients.ClientListView.as_view(), name='clients'), ) \ No newline at end of file diff --git a/openid_provider/views/__init__.py b/openid_provider/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openid_provider/views/clients.py b/openid_provider/views/clients.py new file mode 100644 index 0000000..ce3f2e2 --- /dev/null +++ b/openid_provider/views/clients.py @@ -0,0 +1,14 @@ +from django.utils.decorators import method_decorator +from django.views.generic.list import ListView +from openid_provider.lib.utils.decorators import staff_required +from openid_provider.models import Client + + +class ClientListView(ListView): + + model = Client + template_name = "openid_provider/clients.html" + + @method_decorator(staff_required) + def dispatch(self, *args, **kwargs): + return super(ClientListView, self).dispatch(*args, **kwargs) \ No newline at end of file diff --git a/openid_provider/views.py b/openid_provider/views/endpoints.py similarity index 88% rename from openid_provider/views.py rename to openid_provider/views/endpoints.py index 6f23f8c..1627d52 100644 --- a/openid_provider/views.py +++ b/openid_provider/views/endpoints.py @@ -21,6 +21,10 @@ class AuthorizeView(View): authorize.validate_params() if request.user.is_authenticated(): + + # This is for passing scopes into the form. + authorize.params.scope_str = ' '.join(authorize.params.scope) + data = { 'params': authorize.params, 'client': authorize.client, @@ -29,7 +33,7 @@ class AuthorizeView(View): return render(request, 'openid_provider/authorize.html', data) else: next = urllib.quote(request.get_full_path()) - login_url = settings.LOGIN_URL + '?next={0}'.format(next) + login_url = settings.LOGIN_URL + '?next=' + next return HttpResponseRedirect(login_url) @@ -42,7 +46,9 @@ class AuthorizeView(View): return render(request, 'openid_provider/error.html', data) except (AuthorizeError) as error: - uri = error.create_uri(authorize.params.redirect_uri, authorize.params.state) + uri = error.create_uri( + authorize.params.redirect_uri, + authorize.params.state) return HttpResponseRedirect(uri)