commit 9def141582af0e36d00e64b67149396dc038f1c3 Author: juanifioren Date: Fri Dec 19 12:27:43 2014 -0300 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a7bb45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Byte-compiled python files + +__pycache__/ +*.py[cod] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cc208c6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Juan Ignacio Fiorentino + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..d78e4b3 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE +include README.rst +recursive-include openid_provider/templates * \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..26001f7 --- /dev/null +++ b/README.rst @@ -0,0 +1,100 @@ +###################### +Django OpenID Provider +###################### + +************ +Installation +************ + +Install the package using pip. + +Add it to your proyect apps. + +.. code:: python + + INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'openid_provider', + # ... + ) + +Add the provider urls to your proyect. + +.. code:: python + + urlpatterns = patterns('', + # ... + url(r'^openid/', include('openid_provider.urls', namespace='openid_provider')), + # ... + ) + +Finally, add a login view and ensure that has the same url defined in `LOGIN_URL` setting. + +See: https://docs.djangoproject.com/en/1.7/ref/settings/#login-url + +******************** +Create User & Client +******************** + +First of all, we need to create a user: ``python manage.py createsuperuser``. + +Then let's create a Client. Start django shell: ``python manage.py shell``. + +.. code:: python + + >>> from openid_provider.models import Client + >>> c = Client(name='Some Client', client_id='123', client_secret='456', client_type='public', grant_type='authorization_code', response_type='code', _redirect_uris='http://example.com/') + >>> from django.contrib.auth.models import User + >>> c.user = User.objects.all()[0] + >>> c.save() + +******************* +/authorize endpoint +******************* + +.. code:: curl + + GET /openid/authorize?client_id=123&redirect_uri=http%3A%2F%2Fexample.com%2F&response_type=code&scope=openid%20profile%20email&state=abcdefgh HTTP/1.1 + Host: localhost:8000 + Cache-Control: no-cache + Content-Type: application/x-www-form-urlencoded + +**** +Code +**** + +After the user accepts and authorizes the client application, the server redirects to: + +.. code:: curl + + http://example.com/?code=5fb3b172913448acadce6b011af1e75e&state=abcdefgh + +We extract the ``code`` param and use it to obtain access token. + +*************** +/token endpoint +*************** + +.. code:: curl + + POST /openid/token/ HTTP/1.1 + Host: localhost:8000 + Cache-Control: no-cache + Content-Type: application/x-www-form-urlencoded + + client_id=123&client_secret=456&redirect_uri=http%253A%252F%252Fexample.com%252F&grant_type=authorization_code&code=[CODE]&state=abcdefgh + +****************** +/userinfo endpoint +****************** + +.. code:: curl + + POST /openid/userinfo/ HTTP/1.1 + Host: localhost:8000 + Authorization: Bearer [ACCESS_TOKEN] diff --git a/openid_provider/__init__.py b/openid_provider/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openid_provider/lib/__init__.py b/openid_provider/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openid_provider/lib/errors.py b/openid_provider/lib/errors.py new file mode 100644 index 0000000..4c98feb --- /dev/null +++ b/openid_provider/lib/errors.py @@ -0,0 +1,110 @@ +import urllib + + +class RedirectUriError(Exception): + + error = None + description = 'The request fails due to a missing, invalid, or mismatching redirection URI (redirect_uri).' + + +class ClientIdError(Exception): + + error = None + description = 'The client identifier (client_id) is missing or invalid.' + +class MissingScopeError(Exception): + + error = 'openid scope' + description = 'The openid scope value is missing.' + + +class AuthorizeError(Exception): + + _errors = { + # Oauth2 errors. + # https://tools.ietf.org/html/rfc6749#section-4.1.2.1 + 'invalid_request': 'The request is otherwise malformed', + 'unauthorized_client': 'The client is not authorized to request an authorization code using this method', + 'access_denied': 'The resource owner or authorization server denied the request', + 'unsupported_response_type': 'The authorization server does not support obtaining an authorization code using this method', + 'invalid_scope': 'The requested scope is invalid, unknown, or malformed', + 'server_error': 'The authorization server encountered an error', + 'temporarily_unavailable': 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server', + # OpenID errors. + # http://openid.net/specs/openid-connect-core-1_0.html#AuthError + 'interaction_required': 'The Authorization Server requires End-User interaction of some form to proceed', + 'login_required': 'The Authorization Server requires End-User authentication', + 'account_selection_required': 'The End-User is required to select a session at the Authorization Server', + 'consent_required': 'The Authorization Server requires End-User consent', + 'invalid_request_uri': 'The request_uri in the Authorization Request returns an error or contains invalid data', + 'invalid_request_object': 'The request parameter contains an invalid Request Object', + 'request_not_supported': 'The provider does not support use of the request parameter', + 'request_uri_not_supported': 'The provider does not support use of the request_uri parameter', + 'registration_not_supported': 'The provider does not support use of the registration parameter', + } + + def __init__(self, redirect_uri, error): + + self.error = error + self.description = self._errors.get(error) + self.redirect_uri = redirect_uri + + def create_uri(self, redirect_uri, state): + + description = urllib.quote(self.description) + + uri = '{0}?error={1}&error_description={2}'.format(redirect_uri, self.error, description) + + # Add state if present. + uri = uri + '&state={0}'.format(state) if state else '' + + return uri + + @property + def response(self): + pass + + +class TokenError(Exception): + + _errors = { + # Oauth2 errors. + # https://tools.ietf.org/html/rfc6749#section-5.2 + 'invalid_request': 'The request is otherwise malformed', + 'invalid_client': 'Client authentication failed (e.g., unknown client, no client authentication included, or unsupported authentication method)', + 'invalid_grant': 'The provided authorization grant or refresh token is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client', + 'unauthorized_client': 'The authenticated client is not authorized to use this authorization grant type', + 'unsupported_grant_type': 'The authorization grant type is not supported by the authorization server', + 'invalid_scope': 'The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the resource owner', + } + + def __init__(self, error): + + self.error = error + self.description = self._errors.get(error) + + def create_dict(self): + + dic = { + 'error': self.error, + 'error_description': self.description, + } + + return dic + +class UserInfoError(Exception): + + _errors = { + # Oauth2 errors. + # https://tools.ietf.org/html/rfc6750#section-3.1 + 'invalid_request': ('The request is otherwise malformed', 400), + 'invalid_token': ('The access token provided is expired, revoked, malformed, or invalid for other reasons', 401), + 'insufficient_scope': ('The request requires higher privileges than provided by the access token', 403), + } + + def __init__(self, code): + + self.code = code + error_tuple = self._errors.get(code, ('', '')) + self.description = error_tuple[0] + self.status = error_tuple[1] diff --git a/openid_provider/lib/grants/__init__.py b/openid_provider/lib/grants/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openid_provider/lib/grants/authorization_code.py b/openid_provider/lib/grants/authorization_code.py new file mode 100644 index 0000000..4e987d3 --- /dev/null +++ b/openid_provider/lib/grants/authorization_code.py @@ -0,0 +1,260 @@ +from datetime import timedelta +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse +from django.utils import timezone +import urllib +import uuid +import json +import jwt +import random +import re +import time +from openid_provider.models import * +from openid_provider.lib.errors import * +from openid_provider.lib.scopes import * + + +class AuthorizeEndpoint(object): + + def __init__(self, request): + + self.request = request + self.extract_params() + + def extract_params(self): + + query_dict = self.request.POST if self.request.method == 'POST' else self.request.GET + + class Params(object): pass + + Params.client_id = query_dict.get('client_id', '') + Params.redirect_uri = query_dict.get('redirect_uri', '') + Params.response_type = query_dict.get('response_type', '') + Params.scope = query_dict.get('scope', '') + Params.state = query_dict.get('state', '') + + self.params = Params + + def validate_params(self): + + if not self.params.redirect_uri: + raise RedirectUriError() + + if not ('openid' in self.params.scope.split()): + raise AuthorizeError(self.params.redirect_uri, 'invalid_scope') + + try: + self.client = Client.objects.get(client_id=self.params.client_id) + + if not (self.params.redirect_uri in self.client.redirect_uris): + raise RedirectUriError() + + if not (self.params.response_type == 'code'): + raise AuthorizeError(self.params.redirect_uri, 'unsupported_response_type') + + except Client.DoesNotExist: + raise ClientIdError() + + def create_response_uri(self, allow): + + if not allow: + raise AuthorizeError(self.params.redirect_uri, 'access_denied') + + try: + self.validate_params() + + code = Code() + code.user = self.request.user + code.client = self.client + code.code = uuid.uuid4().hex + code.expires_at = timezone.now() + timedelta(seconds=60*10) + code.scope = self.params.scope + + code.save() + except: + raise AuthorizeError(self.params.redirect_uri, 'server_error') + + uri = self.params.redirect_uri + '?code={0}'.format(code.code) + + # Add state if present. + uri = uri + ('&state={0}'.format(self.params.state) if self.params.state else '') + + return uri + +class TokenEndpoint(object): + + def __init__(self, request): + + self.request = request + self.extract_params() + + def extract_params(self): + + query_dict = self.request.POST + + class Params(object): pass + + Params.client_id = query_dict.get('client_id', '') + Params.client_secret = query_dict.get('client_secret', '') + Params.redirect_uri = urllib.unquote(query_dict.get('redirect_uri', '')) + Params.grant_type = query_dict.get('grant_type', '') + Params.code = query_dict.get('code', '') + Params.state = query_dict.get('state', '') + + self.params = Params + + def validate_params(self): + + if not (self.params.grant_type == 'authorization_code'): + raise TokenError('unsupported_grant_type') + + try: + self.client = Client.objects.get(client_id=self.params.client_id) + + if not (self.client.client_secret == self.params.client_secret): + raise TokenError('invalid_client') + + if not (self.params.redirect_uri in self.client.redirect_uris): + raise TokenError('invalid_client') + + self.code = Code.objects.get(code=self.params.code) + + if not (self.code.client == self.client) and not self.code.has_expired(): + raise TokenError('invalid_grant') + + except Client.DoesNotExist: + raise TokenError('invalid_client') + + except Code.DoesNotExist: + raise TokenError('invalid_grant') + + def create_response_dic(self): + + expires_in = 60*60 # TODO: Probably add into settings + + token = Token() + token.user = self.code.user + token.client = self.code.client + token.access_token = uuid.uuid4().hex + + id_token_dic = self.generate_id_token_dic() + token.id_token = id_token_dic + + token.refresh_token = uuid.uuid4().hex + token.expires_at = timezone.now() + timedelta(seconds=expires_in) + token.scope = self.code.scope + + token.save() + + self.code.delete() + + id_token = jwt.encode(id_token_dic, self.client.client_secret) + + dic = { + 'access_token': token.access_token, + 'token_type': 'bearer', + 'expires_in': expires_in, + 'id_token': id_token, + # TODO: 'refresh_token': token.refresh_token, + } + + return dic + + def generate_id_token_dic(self): + + expires_in = 60*10 + + now = timezone.now() + + # Convert datetimes into timestamps. + iat_time = time.mktime(now.timetuple()) + exp_time = time.mktime((now + timedelta(seconds=expires_in)).timetuple()) + user_auth_time = time.mktime(self.code.user.last_login.timetuple()) + + dic = { + 'iss': 'https://localhost:8000', # TODO: this should not be hardcoded. + 'sub': self.code.user.id, + 'aud': self.client.client_id, + 'exp': exp_time, + 'iat': iat_time, + 'auth_time': user_auth_time, + } + + return dic + + @classmethod + def response(self, dic, status=200): + + response = JsonResponse(dic, status=status) + response['Cache-Control'] = 'no-store' + response['Pragma'] = 'no-cache' + + return response + +class UserInfoEndpoint(object): + + def __init__(self, request): + + self.request = request + self.extract_params() + + def extract_params(self): + + # TODO: Add other ways of passing access token + # http://tools.ietf.org/html/rfc6750#section-2 + + class Params(object): pass + + Params.access_token = self._get_access_token() + + self.params = Params + + def validate_params(self): + + try: + self.token = Token.objects.get(access_token=self.params.access_token) + + except Token.DoesNotExist: + raise UserInfoError('invalid_token') + + def _get_access_token(self): + + # Using Authorization Request Header Field + # http://tools.ietf.org/html/rfc6750#section-2.1 + + auth_header = self.request.META.get('HTTP_AUTHORIZATION', '') + + if re.compile('^Bearer\s{1}.+$').match(auth_header): + access_token = auth_header.split()[1] + else: + access_token = '' + + return access_token + + def create_response_dic(self): + + dic = { + 'sub': self.token.id_token.get('sub'), + } + + standard_claims = StandardClaims(self.token.user, self.token.scope.split()) + + dic.update(standard_claims.response_dic) + + return dic + + @classmethod + def response(self, dic): + + response = JsonResponse(dic, status=200) + response['Cache-Control'] = 'no-store' + response['Pragma'] = 'no-cache' + + return response + + @classmethod + def error_response(self, code, description, status): + + response = HttpResponse(status=status) + response['WWW-Authenticate'] = 'error="{0}", error_description="{1}"'.format(code, description) + + return response \ No newline at end of file diff --git a/openid_provider/lib/scopes.py b/openid_provider/lib/scopes.py new file mode 100644 index 0000000..dd96760 --- /dev/null +++ b/openid_provider/lib/scopes.py @@ -0,0 +1,116 @@ +from django.utils.translation import ugettext as _ +from openid_provider.models import UserInfo + + +# Standard Claims +# http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims + +class StandardClaims(object): + + __model__ = UserInfo + + def __init__(self, user, scopes): + self.user = user + self.scopes = scopes + + try: + self.model = self.__model__.objects.get(user=self.user) + except self.__model__.DoesNotExist: + self.model = self.__model__() + + @property + def response_dic(self): + + dic = {} + + for scope in self.scopes: + + if scope in self._scopes_registered(): + dic.update(getattr(self, 'scope_' + scope)) + + dic = self._clean_dic(dic) + + return dic + + def _scopes_registered(self): + ''' + Return a list that contains all the scopes registered + in the class. + ''' + scopes = [] + + for name in self.__class__.__dict__: + + if name.startswith('scope_'): + scope = name.split('scope_')[1] + scopes.append(scope) + + return scopes + + def _clean_dic(self, dic): + ''' + Clean recursively all empty or None values inside a dict. + ''' + aux_dic = dic.copy() + for key, value in dic.iteritems(): + + if not value: + del aux_dic[key] + elif type(value) is dict: + aux_dic[key] = clean_dic(value) + + return aux_dic + + @property + def scope_profile(self): + dic = { + 'name': self.model.name, + 'given_name': self.model.given_name, + 'family_name': self.model.family_name, + 'middle_name': self.model.middle_name, + 'nickname': self.model.nickname, + 'preferred_username': self.model.preferred_username, + 'profile': self.model.profile, + 'picture': self.model.picture, + 'website': self.model.website, + 'gender': self.model.gender, + 'birthdate': self.model.birthdate, + 'zoneinfo': self.model.zoneinfo, + 'locale': self.model.locale, + 'updated_at': self.model.updated_at, + } + + return dic + + @property + def scope_email(self): + dic = { + 'email': self.user.email, + 'email_verified': self.model.email_verified, + } + + return dic + + @property + def scope_phone(self): + dic = { + 'phone_number': self.model.phone_number, + 'phone_number_verified': self.model.phone_number_verified, + } + + return dic + + @property + def scope_address(self): + dic = { + 'address': { + 'formatted': self.model.address_formatted, + 'street_address': self.model.address_street_address, + 'locality': self.model.address_locality, + 'region': self.model.address_region, + 'postal_code': self.model.address_postal_code, + 'country': self.model.address_country, + } + } + + return dic diff --git a/openid_provider/migrations/__init__.py b/openid_provider/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/openid_provider/models.py b/openid_provider/models.py new file mode 100644 index 0000000..856e1f5 --- /dev/null +++ b/openid_provider/models.py @@ -0,0 +1,115 @@ +from django.contrib.auth.models import User +from django.db import models +from django.utils import timezone +import json + + +class Client(models.Model): + + CLIENT_TYPE_CHOICES = [ + ('confidential', 'Confidential'), + #('public', 'Public'), + ] + + GRANT_TYPE_CHOICES = [ + ('authorization_code', 'Authorization Code Flow'), + #('implicit', 'Implicit Flow'), + ] + + RESPONSE_TYPE_CHOICES = [ + ('code', 'Authorization Code Flow'), + #('id_token', 'Implicit Flow'), + #('id_token token', 'Implicit Flow'), + ] + + name = models.CharField(max_length=100, default='') + user = models.ForeignKey(User) + client_id = models.CharField(max_length=255, unique=True) + client_secret = models.CharField(max_length=255, unique=True) + client_type = models.CharField(max_length=20, choices=CLIENT_TYPE_CHOICES) + grant_type = models.CharField(max_length=30, choices=GRANT_TYPE_CHOICES) + response_type = models.CharField(max_length=30, choices=RESPONSE_TYPE_CHOICES) + _redirect_uris = models.TextField() + _scope = models.TextField() # TODO: add getter and setter for this. + + @property + def redirect_uris(self): + if self._redirect_uris: + return self._redirect_uris.split() + return [] + + @property + def default_redirect_uri(self): + return self.redirect_uris[0] + + @property + def scope(self): + if self._scopes: + return self._scopes.split() + return [] + +class Code(models.Model): + + user = models.ForeignKey(User) + 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. + + def has_expired(self): + return timezone.now() >= self.expires_at + +class Token(models.Model): + + user = models.ForeignKey(User) + client = models.ForeignKey(Client) + access_token = models.CharField(max_length=255, unique=True) + _id_token = models.TextField() + refresh_token = models.CharField(max_length=255, unique=True) + expires_at = models.DateTimeField() + scope = models.TextField() # TODO: add getter and setter for this. + + def id_token(): + def fget(self): + return json.loads(self._id_token) + def fset(self, value): + self._id_token = json.dumps(value) + return locals() + id_token = property(**id_token()) + +class UserInfo(models.Model): + + user = models.OneToOneField(User, primary_key=True) + + given_name = models.CharField(max_length=255, default='') + family_name = models.CharField(max_length=255, default='') + middle_name = models.CharField(max_length=255, default='') + nickname = models.CharField(max_length=255, default='') + preferred_username = models.CharField(max_length=255, default='') + profile = models.URLField(default='') + picture = models.URLField(default='') + website = models.URLField(default='') + email_verified = models.BooleanField(default=False) + gender = models.CharField(max_length=100, default='') + birthdate = models.DateField() + zoneinfo = models.CharField(max_length=100, default='') + locale = models.CharField(max_length=100, default='') + phone_number = models.CharField(max_length=255, default='') + phone_number_verified = models.BooleanField(default=False) + address_formatted = models.CharField(max_length=255, default='') + address_street_address = models.CharField(max_length=255, default='') + address_locality = models.CharField(max_length=255, default='') + address_region = models.CharField(max_length=255, default='') + address_postal_code = models.CharField(max_length=255, default='') + address_country = models.CharField(max_length=255, default='') + updated_at = models.DateTimeField() + + @property + def name(self): + name = '' + if self.given_name: + name = self.given_name + if self.family_name: + name = name + ' ' + self.family_name + + return name \ No newline at end of file diff --git a/openid_provider/templates/openid_provider/authorize.html b/openid_provider/templates/openid_provider/authorize.html new file mode 100644 index 0000000..115dc90 --- /dev/null +++ b/openid_provider/templates/openid_provider/authorize.html @@ -0,0 +1,47 @@ +{% extends "openid_provider/base.html" %} + +{% load i18n %} + +{% block content %} + +
+
+
+
+

Request for Permission

+
+
+

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

+
+ {% csrf_token %} + + + + + + + +
    + {% for scope in params.scope.split %} + {% if scope != 'openid' %} +
  • {{ scope | capfirst }}
  • + {% endif %} + {% endfor %} +
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/openid_provider/templates/openid_provider/base.html b/openid_provider/templates/openid_provider/base.html new file mode 100644 index 0000000..4b8534d --- /dev/null +++ b/openid_provider/templates/openid_provider/base.html @@ -0,0 +1,55 @@ +{% load staticfiles %} + + + + + + OpenID Provider + + + + + + + + + + + + +
+ {% block content %}{% endblock %} +
+ + + + + + \ No newline at end of file diff --git a/openid_provider/urls.py b/openid_provider/urls.py new file mode 100644 index 0000000..99b8274 --- /dev/null +++ b/openid_provider/urls.py @@ -0,0 +1,12 @@ +from django.conf.urls import patterns, include, url +from django.views.decorators.csrf import csrf_exempt + +from . import views + +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'), + +) \ No newline at end of file diff --git a/openid_provider/views.py b/openid_provider/views.py new file mode 100644 index 0000000..70d16e4 --- /dev/null +++ b/openid_provider/views.py @@ -0,0 +1,90 @@ +from django.conf import settings +from django.core.urlresolvers import reverse +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse +from django.shortcuts import render +from django.views.decorators.http import require_http_methods +from django.views.generic import View +import urllib +from .lib.errors import * +from .lib.grants.authorization_code import * + + +class AuthorizeView(View): + + def get(self, request, *args, **kwargs): + + authorize = AuthorizeEndpoint(request) + + try: + authorize.validate_params() + + if request.user.is_authenticated(): + data = { + 'params': authorize.params, + 'client': authorize.client, + } + + 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) + + return HttpResponseRedirect(login_url) + + except (ClientIdError, RedirectUriError) as error: + return HttpResponse(error.description) + + except (AuthorizeError) as error: + uri = error.create_uri(authorize.params.redirect_uri, authorize.params.state) + + return HttpResponseRedirect(uri) + + def post(self, request, *args, **kwargs): + + authorize = AuthorizeEndpoint(request) + + allow = True if request.POST.get('allow') else False + + try: + uri = authorize.create_response_uri(allow) + + return HttpResponseRedirect(uri) + + except (AuthorizeError) as error: + uri = error.create_uri(authorize.params.redirect_uri, authorize.params.state) + + return HttpResponseRedirect(uri) + +class TokenView(View): + + def post(self, request, *args, **kwargs): + + token = TokenEndpoint(request) + + try: + token.validate_params() + + dic = token.create_response_dic() + + return TokenEndpoint.response(dic) + + except (TokenError) as error: + return TokenEndpoint.response(error.create_dict(), status=400) + +@require_http_methods(['GET', 'POST']) +def userinfo(request): + + userinfo = UserInfoEndpoint(request) + + try: + userinfo.validate_params() + + dic = userinfo.create_response_dic() + + return UserInfoEndpoint.response(dic) + + except (UserInfoError) as error: + return UserInfoEndpoint.error_response( + error.code, + error.description, + error.status) \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b106fcc --- /dev/null +++ b/setup.py @@ -0,0 +1,33 @@ +import os +from setuptools import setup + +with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: + README = readme.read() + +# allow setup.py to be run from any path +os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) + +setup( + name='django-openid-provider', + version='0.1', + packages=['openid_provider'], + include_package_data=True, + license='MIT License', + description='A simple OpenID Connect Provider implementation for Djangonauts.', + long_description=README, + url='http://github.com/juanifioren/django-openid-provider', + author='Juan Ignacio Fiorentino', + author_email='juanifioren@gmail.com', + classifiers=[ + 'Environment :: Web Environment', + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Topic :: Internet :: WWW/HTTP', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + ], +) \ No newline at end of file