Refactoring. Add views for client creation.

This commit is contained in:
juanifioren 2015-01-12 19:13:48 -03:00
parent f10d307fa4
commit 02c2ae99f7
14 changed files with 300 additions and 21 deletions

View file

@ -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

View file

@ -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

View file

@ -1,2 +1,4 @@
class Params(object):
pass

View file

@ -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,
),
]

View file

@ -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():

View file

@ -11,18 +11,18 @@
<h3 class="panel-title">Request for Permission</h3>
</div>
<div class="panel-body">
<p>Client {{ client.name }} would like to access this information of you ...</p>
<p>Client <strong>{{ client.name }}</strong> would like to access this information of you ...</p>
<form method="post" action="{% url 'openid_provider:authorize' %}">
{% csrf_token %}
<input name="client_id" type="hidden" value="{{ params.client_id }}" />
<input name="redirect_uri" type="hidden" value="{{ params.redirect_uri }}" />
<input name="response_type" type="hidden" value="{{ params.response_type }}" />
<input name="scope" type="hidden" value="{{ params.scope }}" />
<input name="scope" type="hidden" value="{{ params.scope_str }}" />
<input name="state" type="hidden" value="{{ params.state }}" />
<ul class="list-group">
{% for scope in params.scope.split %}
{% for scope in params.scope %}
{% if scope != 'openid' %}
<li class="list-group-item">{{ scope | capfirst }}</li>
{% endif %}

View file

@ -9,9 +9,13 @@
<link href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.1/spacelab/bootstrap.min.css" rel="stylesheet">
<style type="text/css">
body {
background-color: #fbfbfb;
padding-top: 90px;
padding-bottom: 30px;
}
.btn {
font-weight: bold;
}
</style>
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->

View file

@ -0,0 +1,33 @@
{% extends "openid_provider/base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-md-3 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-body">
<a href="#" class="btn btn-success btn-block">New Client</a>
</div>
</div>
</div>
<div class="col-md-7">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Clients</h3>
</div>
<div class="panel-body">
<div class="list-group">
{% for client in object_list %}
<a href="#{{ client.client_id }}" class="list-group-item">{{ client.name }}</a>
{% empty %}
<div class="list-group-item">No clients yet.</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,39 @@
{% extends "openid_provider/base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default">
<div class="panel-body">
<h2>Please sign in</h2>
<hr>
{% if form.errors %}
<div class="alert alert-danger" role="alert">Username and password mismatch.</div>
{% endif %}
<form accept-charset="UTF-8" role="form" method="post" action="{% url 'openid_provider:login' %}">
{% csrf_token %}
<fieldset>
<div class="form-group">
<input class="form-control" placeholder="Username" name="username" type="text">
</div>
<div class="form-group">
<input class="form-control" placeholder="Password" name="password" type="password" value="">
</div>
<div class="checkbox">
<label>
<input name="remember" type="checkbox" value="Remember Me"> Remember Me
</label>
</div>
<input type="hidden" name="next" value="{{ next }}" />
<input class="btn btn-success btn-block" type="submit" value="OK">
</fieldset>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,19 @@
{% extends "openid_provider/base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default">
<div class="panel-body">
<h2>Logged out</h2>
<hr>
<p>See you next time.</p>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -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'),
)

View file

View file

@ -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)

View file

@ -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)