From 737db08fa6685c167d95942ec713bfe29da7e751 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 28 Jan 2015 14:31:16 -0300 Subject: [PATCH 01/16] test.. --- openid_provider/lib/endpoints/authorize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openid_provider/lib/endpoints/authorize.py b/openid_provider/lib/endpoints/authorize.py index f12ac97..c6796b3 100644 --- a/openid_provider/lib/endpoints/authorize.py +++ b/openid_provider/lib/endpoints/authorize.py @@ -97,7 +97,7 @@ class AuthorizeEndpoint(object): code.user = self.request.user code.client = self.client code.code = uuid.uuid4().hex - code.expires_at = timezone.now() + timedelta(seconds=60*10) # TODO: Add this into settings. + code.expires_at = timezone.now() + timedelta(seconds=60*10) # TODO: Add this into settings. code.scope = self.params.scope code.save() From 7cada8a55ab60405bb30c2ee08dc1f023082c70b Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 28 Jan 2015 14:51:25 -0300 Subject: [PATCH 02/16] PEP8 compliant --- openid_provider/lib/endpoints/authorize.py | 38 ++++++++-------- openid_provider/lib/endpoints/token.py | 26 ++++++----- openid_provider/lib/endpoints/userinfo.py | 12 ++--- openid_provider/lib/errors.py | 52 ++++++++++++++++++---- openid_provider/lib/scopes.py | 9 ++-- openid_provider/lib/utils/params.py | 4 +- openid_provider/lib/utils/token.py | 26 ++++++----- openid_provider/models.py | 12 ++++- openid_provider/urls.py | 8 ++-- openid_provider/views.py | 5 ++- 10 files changed, 124 insertions(+), 68 deletions(-) diff --git a/openid_provider/lib/endpoints/authorize.py b/openid_provider/lib/endpoints/authorize.py index c6796b3..2a9dbd5 100644 --- a/openid_provider/lib/endpoints/authorize.py +++ b/openid_provider/lib/endpoints/authorize.py @@ -1,10 +1,13 @@ +import uuid + from datetime import timedelta + from django.utils import timezone + from openid_provider.lib.errors import * from openid_provider.lib.utils.params import * from openid_provider.lib.utils.token import * from openid_provider.models import * -import uuid class AuthorizeEndpoint(object): @@ -17,8 +20,7 @@ class AuthorizeEndpoint(object): # Because in this endpoint we handle both GET # and POST request. - self.query_dict = (self.request.POST if self.request.method == 'POST' - else self.request.GET) + self.query_dict = (self.request.POST if self.request.method == 'POST' else self.request.GET) self._extract_params() @@ -32,12 +34,12 @@ class AuthorizeEndpoint(object): self.grant_type = None def _extract_params(self): - ''' + """ Get all the params used by the Authorization Code Flow (and also for the Implicit). See: http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest - ''' + """ 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', '') @@ -45,11 +47,11 @@ class AuthorizeEndpoint(object): self.params.state = self.query_dict.get('state', '') def _extract_implicit_params(self): - ''' + """ Get specific params used by the Implicit Flow. See: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest - ''' + """ self.params.nonce = self.query_dict.get('nonce', '') def validate_params(self): @@ -69,8 +71,7 @@ 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): + if not self.grant_type or not (self.params.response_type == self.client.response_type): raise AuthorizeError( self.params.redirect_uri, @@ -91,7 +92,7 @@ class AuthorizeEndpoint(object): try: self.validate_params() - if (self.grant_type == 'authorization_code'): + if self.grant_type == 'authorization_code': code = Code() code.user = self.request.user @@ -103,11 +104,11 @@ class AuthorizeEndpoint(object): uri = self.params.redirect_uri + '?code={0}'.format(code.code) - else: # Implicit Flow + else: # Implicit Flow id_token_dic = create_id_token_dic( self.request.user, - 'http://localhost:8000', # TODO: Add this into settings. + 'http://localhost:8000', # TODO: Add this into settings. self.client.client_id) token = create_token( @@ -123,11 +124,11 @@ class AuthorizeEndpoint(object): # 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( - 'bearer', - id_token, - 60*10) + uri = self.params.redirect_uri + '#token_type={0}&id_token={1}&expires_in={2}'.format( + 'bearer', + id_token, + 60*10 + ) except: raise AuthorizeError( self.params.redirect_uri, @@ -135,7 +136,6 @@ class AuthorizeEndpoint(object): 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/endpoints/token.py b/openid_provider/lib/endpoints/token.py index 59661e6..bea96b0 100644 --- a/openid_provider/lib/endpoints/token.py +++ b/openid_provider/lib/endpoints/token.py @@ -1,9 +1,11 @@ +import urllib + from django.http import JsonResponse + from openid_provider.lib.errors import * from openid_provider.lib.utils.params import * from openid_provider.lib.utils.token import * from openid_provider.models import * -import urllib class TokenEndpoint(object): @@ -53,15 +55,17 @@ class TokenEndpoint(object): def create_response_dic(self): id_token_dic = create_id_token_dic( - self.code.user, - 'http://localhost:8000', # TODO: Add this into settings. - self.client.client_id) + self.code.user, + 'http://localhost:8000', # TODO: Add this into settings. + self.client.client_id + ) token = create_token( - user=self.code.user, - client=self.code.client, - id_token_dic=id_token_dic, - scope=self.code.scope) + user=self.code.user, + client=self.code.client, + id_token_dic=id_token_dic, + scope=self.code.scope + ) # Store the token. token.save() @@ -74,7 +78,7 @@ class TokenEndpoint(object): dic = { 'access_token': token.access_token, 'token_type': 'bearer', - 'expires_in': 60*60, # TODO: Add this into settings. + 'expires_in': 60*60, # TODO: Add this into settings. 'id_token': id_token, } @@ -82,9 +86,9 @@ class TokenEndpoint(object): @classmethod def response(self, dic, status=200): - ''' + """ Create and return a response object. - ''' + """ response = JsonResponse(dic, status=status) response['Cache-Control'] = 'no-store' response['Pragma'] = 'no-cache' diff --git a/openid_provider/lib/endpoints/userinfo.py b/openid_provider/lib/endpoints/userinfo.py index f9377c4..84b56ab 100644 --- a/openid_provider/lib/endpoints/userinfo.py +++ b/openid_provider/lib/endpoints/userinfo.py @@ -1,9 +1,11 @@ +import re + from django.http import HttpResponse, JsonResponse + from openid_provider.lib.errors import * from openid_provider.lib.scopes import * from openid_provider.lib.utils.params import * from openid_provider.models import * -import re class UserInfoEndpoint(object): @@ -21,12 +23,12 @@ class UserInfoEndpoint(object): self.params.access_token = self._get_access_token() def _get_access_token(self): - ''' + """ Get the access token using Authorization Request Header Field method. See: http://tools.ietf.org/html/rfc6750#section-2.1 Return a string. - ''' + """ auth_header = self.request.META.get('HTTP_AUTHORIZATION', '') if re.compile('^Bearer\s{1}.+$').match(auth_header): @@ -45,12 +47,12 @@ class UserInfoEndpoint(object): raise UserInfoError('invalid_token') def create_response_dic(self): - ''' + """ Create a diccionary with all the requested claims about the End-User. See: http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse Return a diccionary. - ''' + """ dic = { 'sub': self.token.id_token.get('sub'), } diff --git a/openid_provider/lib/errors.py b/openid_provider/lib/errors.py index 08d5627..51ba9f3 100644 --- a/openid_provider/lib/errors.py +++ b/openid_provider/lib/errors.py @@ -6,33 +6,52 @@ class RedirectUriError(Exception): error = 'Redirect URI Error' description = 'The request fails due to a missing, invalid, or mismatching redirection URI (redirect_uri).' + class ClientIdError(Exception): error = 'Client ID Error' description = 'The client identifier (client_id) is missing or invalid.' + 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', + + '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', + + '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', } @@ -65,17 +84,26 @@ class AuthorizeError(Exception): 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', + + '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', + + 'invalid_scope': 'The requested scope is invalid, unknown, malformed, or exceeds the scope granted by the ' + 'resource owner', } def __init__(self, error): @@ -92,14 +120,20 @@ class TokenError(Exception): return dic -class UserInfoError(Exception): +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), + '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): diff --git a/openid_provider/lib/scopes.py b/openid_provider/lib/scopes.py index 0472d8d..530d0b7 100644 --- a/openid_provider/lib/scopes.py +++ b/openid_provider/lib/scopes.py @@ -1,4 +1,5 @@ from django.utils.translation import ugettext as _ + from openid_provider.models import UserInfo @@ -32,10 +33,10 @@ class StandardClaims(object): 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__: @@ -47,9 +48,9 @@ class StandardClaims(object): 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(): diff --git a/openid_provider/lib/utils/params.py b/openid_provider/lib/utils/params.py index ca399ef..961424a 100644 --- a/openid_provider/lib/utils/params.py +++ b/openid_provider/lib/utils/params.py @@ -1,7 +1,7 @@ class Params(object): - ''' + """ The purpose of this class is for accesing params via dot notation. - ''' + """ pass \ No newline at end of file diff --git a/openid_provider/lib/utils/token.py b/openid_provider/lib/utils/token.py index 401627c..6adf7ed 100644 --- a/openid_provider/lib/utils/token.py +++ b/openid_provider/lib/utils/token.py @@ -1,19 +1,21 @@ -from datetime import timedelta -from django.utils import timezone -import jwt -from openid_provider.models import * import time +import jwt import uuid +from datetime import timedelta + +from django.utils import timezone +from openid_provider.models import * + def create_id_token_dic(user, iss, aud): - ''' + """ Receives a user object, iss (issuer) and aud (audience). Then creates the id_token dic. See: http://openid.net/specs/openid-connect-core-1_0.html#IDToken Return a dic. - ''' + """ expires_in = 60*10 now = timezone.now() @@ -34,22 +36,24 @@ def create_id_token_dic(user, iss, aud): return dic + def encode_id_token(id_token_dic, client_secret): - ''' + """ Represent the ID Token as a JSON Web Token (JWT). Return a hash. - ''' + """ id_token_hash = jwt.encode(id_token_dic, client_secret) return id_token_hash + def create_token(user, client, id_token_dic, scope): - ''' + """ Create and populate a Token object. Return a Token object. - ''' + """ token = Token() token.user = user token.client = client @@ -58,7 +62,7 @@ def create_token(user, client, id_token_dic, scope): token.id_token = id_token_dic token.refresh_token = uuid.uuid4().hex - token.expires_at = timezone.now() + timedelta(seconds=60*60) # TODO: Add this into settings. + token.expires_at = timezone.now() + timedelta(seconds=60*60) # TODO: Add this into settings. token.scope = scope return token \ No newline at end of file diff --git a/openid_provider/models.py b/openid_provider/models.py index c146f1c..ff0070c 100644 --- a/openid_provider/models.py +++ b/openid_provider/models.py @@ -1,7 +1,8 @@ -from django.contrib.auth.models import User +import json + from django.db import models from django.utils import timezone -import json +from django.contrib.auth.models import User class Client(models.Model): @@ -18,6 +19,7 @@ class Client(models.Model): response_type = models.CharField(max_length=30, choices=RESPONSE_TYPE_CHOICES) _redirect_uris = models.TextField(default='') + def redirect_uris(): def fget(self): return self._redirect_uris.splitlines() @@ -30,6 +32,7 @@ class Client(models.Model): def default_redirect_uri(self): return self.redirect_uris[0] if self.redirect_uris else '' + class Code(models.Model): user = models.ForeignKey(User) @@ -38,6 +41,7 @@ class Code(models.Model): expires_at = models.DateTimeField() _scope = models.TextField(default='') + def scope(): def fget(self): return self._scope.split() @@ -49,6 +53,7 @@ class Code(models.Model): def has_expired(self): return timezone.now() >= self.expires_at + class Token(models.Model): user = models.ForeignKey(User) @@ -57,6 +62,7 @@ class Token(models.Model): expires_at = models.DateTimeField() _scope = models.TextField(default='') + def scope(): def fget(self): return self._scope.split() @@ -66,6 +72,7 @@ class Token(models.Model): scope = property(**scope()) _id_token = models.TextField() + def id_token(): def fget(self): return json.loads(self._id_token) @@ -74,6 +81,7 @@ class Token(models.Model): return locals() id_token = property(**id_token()) + class UserInfo(models.Model): user = models.OneToOneField(User, primary_key=True) diff --git a/openid_provider/urls.py b/openid_provider/urls.py index c937e23..3b0183f 100644 --- a/openid_provider/urls.py +++ b/openid_provider/urls.py @@ -1,12 +1,12 @@ from django.conf.urls import patterns, include, url from django.views.decorators.csrf import csrf_exempt + from openid_provider.views import * urlpatterns = patterns('', - - url(r'^authorize/$', AuthorizeView.as_view(), name='authorize'), - url(r'^token/$', csrf_exempt(TokenView.as_view()), name='token'), - url(r'^userinfo/$', csrf_exempt(userinfo), name='userinfo'), + url(r'^authorize/$', AuthorizeView.as_view(), name='authorize'), + url(r'^token/$', csrf_exempt(TokenView.as_view()), name='token'), + url(r'^userinfo/$', csrf_exempt(userinfo), name='userinfo'), ) \ No newline at end of file diff --git a/openid_provider/views.py b/openid_provider/views.py index d76c5ba..2cc1228 100644 --- a/openid_provider/views.py +++ b/openid_provider/views.py @@ -1,3 +1,5 @@ +import urllib + from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.views import redirect_to_login @@ -6,7 +8,7 @@ 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 openid_provider.lib.errors import * from openid_provider.lib.endpoints.authorize import * from openid_provider.lib.endpoints.token import * @@ -69,6 +71,7 @@ class AuthorizeView(View): return HttpResponseRedirect(uri) + class TokenView(View): def post(self, request, *args, **kwargs): From 49ce86997e4d23d4f0c2d28d86c15daa66f31c63 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 28 Jan 2015 15:41:05 -0300 Subject: [PATCH 03/16] workaround..JsonResponse import error on django < 1.7 --- openid_provider/lib/endpoints/token.py | 13 +++++++++---- openid_provider/lib/endpoints/userinfo.py | 6 +++++- openid_provider/views.py | 3 ++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/openid_provider/lib/endpoints/token.py b/openid_provider/lib/endpoints/token.py index bea96b0..1ae224c 100644 --- a/openid_provider/lib/endpoints/token.py +++ b/openid_provider/lib/endpoints/token.py @@ -1,10 +1,15 @@ import urllib -from django.http import JsonResponse +try: # JsonResponse is only available in Django > 1.7 + from django.http import JsonResponse +except ImportError: + from ..utils.http import JsonResponse + +from ..utils.http import JsonResponse +from ..errors import * +from ..utils.params import * +from ..utils.token import * -from openid_provider.lib.errors import * -from openid_provider.lib.utils.params import * -from openid_provider.lib.utils.token import * from openid_provider.models import * diff --git a/openid_provider/lib/endpoints/userinfo.py b/openid_provider/lib/endpoints/userinfo.py index 84b56ab..4e4c7be 100644 --- a/openid_provider/lib/endpoints/userinfo.py +++ b/openid_provider/lib/endpoints/userinfo.py @@ -1,6 +1,10 @@ import re +from django.http import HttpResponse -from django.http import HttpResponse, JsonResponse +try: # JsonResponse is only available in Django > 1.7 + from django.http import JsonResponse +except ImportError: + from ..utils.http import JsonResponse from openid_provider.lib.errors import * from openid_provider.lib.scopes import * diff --git a/openid_provider/views.py b/openid_provider/views.py index 2cc1228..68acc23 100644 --- a/openid_provider/views.py +++ b/openid_provider/views.py @@ -4,7 +4,8 @@ from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.views import redirect_to_login from django.core.urlresolvers import reverse -from django.http import HttpResponse, HttpResponseRedirect, JsonResponse +from django.http import HttpResponse, HttpResponseRedirect + from django.shortcuts import render from django.views.decorators.http import require_http_methods from django.views.generic import View From 711de36ae7cf5f3404c9f54baddf783697a7e57e Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 28 Jan 2015 17:01:40 -0300 Subject: [PATCH 04/16] optimizing imports --- openid_provider/lib/endpoints/authorize.py | 7 ++++--- openid_provider/lib/endpoints/userinfo.py | 6 +++--- openid_provider/views.py | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/openid_provider/lib/endpoints/authorize.py b/openid_provider/lib/endpoints/authorize.py index 2a9dbd5..49ade1d 100644 --- a/openid_provider/lib/endpoints/authorize.py +++ b/openid_provider/lib/endpoints/authorize.py @@ -4,9 +4,10 @@ from datetime import timedelta from django.utils import timezone -from openid_provider.lib.errors import * -from openid_provider.lib.utils.params import * -from openid_provider.lib.utils.token import * +from ..errors import * +from ..utils.params import * +from ..utils.token import * + from openid_provider.models import * diff --git a/openid_provider/lib/endpoints/userinfo.py b/openid_provider/lib/endpoints/userinfo.py index 4e4c7be..4a24368 100644 --- a/openid_provider/lib/endpoints/userinfo.py +++ b/openid_provider/lib/endpoints/userinfo.py @@ -6,9 +6,9 @@ try: # JsonResponse is only available in Django > 1.7 except ImportError: from ..utils.http import JsonResponse -from openid_provider.lib.errors import * -from openid_provider.lib.scopes import * -from openid_provider.lib.utils.params import * +from ..errors import * +from ..scopes import * +from ..utils.params import * from openid_provider.models import * diff --git a/openid_provider/views.py b/openid_provider/views.py index 68acc23..8311321 100644 --- a/openid_provider/views.py +++ b/openid_provider/views.py @@ -10,10 +10,10 @@ from django.shortcuts import render from django.views.decorators.http import require_http_methods from django.views.generic import View -from openid_provider.lib.errors import * -from openid_provider.lib.endpoints.authorize import * -from openid_provider.lib.endpoints.token import * -from openid_provider.lib.endpoints.userinfo import * +from .lib.errors import * +from .lib.endpoints.authorize import * +from .lib.endpoints.token import * +from .lib.endpoints.userinfo import * class AuthorizeView(View): From 92754e555ffcd346792549b5fa07eb0d81bccd32 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 29 Jan 2015 10:45:27 -0300 Subject: [PATCH 05/16] adding JsonResponse of django 1.7 to lib utils --- openid_provider/lib/utils/http.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 openid_provider/lib/utils/http.py diff --git a/openid_provider/lib/utils/http.py b/openid_provider/lib/utils/http.py new file mode 100644 index 0000000..f5e05be --- /dev/null +++ b/openid_provider/lib/utils/http.py @@ -0,0 +1,26 @@ +import json +from django.core.serializers.json import DjangoJSONEncoder +from django.http import HttpResponse + + +# CLass JsonResponse see: https://github.com/django/django/blob/master/django/http/response.py#L456 + +class JsonResponse(HttpResponse): + """ + An HTTP response class that consumes data to be serialized to JSON. + :param data: Data to be dumped into json. By default only ``dict`` objects + are allowed to be passed due to a security flaw before EcmaScript 5. See + the ``safe`` parameter for more information. + :param encoder: Should be an json encoder class. Defaults to + ``django.core.serializers.json.DjangoJSONEncoder``. + :param safe: Controls if only ``dict`` objects may be serialized. Defaults + to ``True``. + """ + + +def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, **kwargs): + if safe and not isinstance(data, dict): + raise TypeError('In order to allow non-dict objects to be serialized set the safe parameter to False') + kwargs.setdefault('content_type', 'application/json') + data = json.dumps(data, cls=encoder) + super(JsonResponse, self).__init__(content=data, **kwargs) From cfbfbfc74a35b3aee69a7674eef9007fa5608cac Mon Sep 17 00:00:00 2001 From: "niccolasmendoza@gmail.com" Date: Mon, 8 Jun 2015 12:32:55 -0300 Subject: [PATCH 06/16] removing unnecesary imports --- oidc_provider/lib/endpoints/authorize.py | 6 ------ openid_provider/lib/utils/http.py | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index 667de5a..b119830 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -1,13 +1,7 @@ -from datetime import timedelta -import uuid - -from django.utils import timezone - from oidc_provider.lib.errors import * from oidc_provider.lib.utils.params import * from oidc_provider.lib.utils.token import * from oidc_provider.models import * -from oidc_provider import settings class AuthorizeEndpoint(object): diff --git a/openid_provider/lib/utils/http.py b/openid_provider/lib/utils/http.py index f5e05be..9b6a710 100644 --- a/openid_provider/lib/utils/http.py +++ b/openid_provider/lib/utils/http.py @@ -1,4 +1,5 @@ import json + from django.core.serializers.json import DjangoJSONEncoder from django.http import HttpResponse From c385609b359649d6f36416eb13b670f9be328b57 Mon Sep 17 00:00:00 2001 From: "niccolasmendoza@gmail.com" Date: Mon, 8 Jun 2015 13:01:09 -0300 Subject: [PATCH 07/16] adding test settings, templates.. --- .../tests/templates/accounts/login.html | 24 +++++++++ .../tests/templates/accounts/logout.html | 12 +++++ oidc_provider/tests/templates/base.html | 50 +++++++++++++++++++ oidc_provider/tests/templates/home.html | 11 ++++ .../templates/oidc_provider/authorize.html | 30 +++++++++++ .../tests/templates/oidc_provider/error.html | 14 ++++++ oidc_provider/tests/test_settings.py | 40 +++++++++++++++ oidc_provider/tests/test_urls.py | 15 ++++++ 8 files changed, 196 insertions(+) create mode 100644 oidc_provider/tests/templates/accounts/login.html create mode 100644 oidc_provider/tests/templates/accounts/logout.html create mode 100644 oidc_provider/tests/templates/base.html create mode 100644 oidc_provider/tests/templates/home.html create mode 100644 oidc_provider/tests/templates/oidc_provider/authorize.html create mode 100644 oidc_provider/tests/templates/oidc_provider/error.html create mode 100644 oidc_provider/tests/test_settings.py create mode 100644 oidc_provider/tests/test_urls.py diff --git a/oidc_provider/tests/templates/accounts/login.html b/oidc_provider/tests/templates/accounts/login.html new file mode 100644 index 0000000..6c24774 --- /dev/null +++ b/oidc_provider/tests/templates/accounts/login.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} + +{% block content %} + +
+
+
+ {% csrf_token %} + + {% if form.errors %} + + {% endif %} +
+ +
+
+ +
+ +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/oidc_provider/tests/templates/accounts/logout.html b/oidc_provider/tests/templates/accounts/logout.html new file mode 100644 index 0000000..25aa0f8 --- /dev/null +++ b/oidc_provider/tests/templates/accounts/logout.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block content %} + +
+
+

Bye!

+

Thanks for spending some quality time with the web site today.

+
+
+ +{% endblock %} \ No newline at end of file diff --git a/oidc_provider/tests/templates/base.html b/oidc_provider/tests/templates/base.html new file mode 100644 index 0000000..6d38b8b --- /dev/null +++ b/oidc_provider/tests/templates/base.html @@ -0,0 +1,50 @@ + + + + + + + OpenID Provider + + + + + + + + + + +
+
+ +

django-oidc-provider

+
+ + {% block content %}{% endblock %} + + + +
+ + + + + + + \ No newline at end of file diff --git a/oidc_provider/tests/templates/home.html b/oidc_provider/tests/templates/home.html new file mode 100644 index 0000000..c79d818 --- /dev/null +++ b/oidc_provider/tests/templates/home.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} + +{% block content %} + +
+

Welcome!

+

Django OIDC Provider can help you providing out of the box all the endpoints, data and logic needed to add OpenID Connect capabilities to your Django projects.

+

View on Github

+
+ +{% endblock %} \ No newline at end of file diff --git a/oidc_provider/tests/templates/oidc_provider/authorize.html b/oidc_provider/tests/templates/oidc_provider/authorize.html new file mode 100644 index 0000000..ccc7065 --- /dev/null +++ b/oidc_provider/tests/templates/oidc_provider/authorize.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} + +{% block content %} + +
+
+

Request for Permission

+
+
+

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

+ +
+ + {% csrf_token %} + + {{ hidden_inputs }} + +
    + {% for scope in params.scope %} +
  • {{ scope | capfirst }}
  • + {% endfor %} +
+ + + +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/oidc_provider/tests/templates/oidc_provider/error.html b/oidc_provider/tests/templates/oidc_provider/error.html new file mode 100644 index 0000000..b6e75dd --- /dev/null +++ b/oidc_provider/tests/templates/oidc_provider/error.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block content %} + +
+
+

{{ error }}

+
+
+ {{ description }} +
+
+ +{% endblock %} \ No newline at end of file diff --git a/oidc_provider/tests/test_settings.py b/oidc_provider/tests/test_settings.py new file mode 100644 index 0000000..e15ca5c --- /dev/null +++ b/oidc_provider/tests/test_settings.py @@ -0,0 +1,40 @@ +from datetime import timedelta + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:', + } +} + +SITE_ID = 1 + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', +) + + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.admin', + + 'oidc_provider', +) + +SECRET_KEY = 'secret-for-test-secret-secret' + +ROOT_URLCONF = 'oidc_provider.tests.test_urls' + +TEMPLATE_DIRS = ( + "oidc_provider/tests/templates", +) + +# OIDC Provider settings. + +SITE_URL = 'http://localhost:8000' \ No newline at end of file diff --git a/oidc_provider/tests/test_urls.py b/oidc_provider/tests/test_urls.py new file mode 100644 index 0000000..abfb2c4 --- /dev/null +++ b/oidc_provider/tests/test_urls.py @@ -0,0 +1,15 @@ +from django.contrib.auth import views as auth_views +from django.conf.urls import patterns, include, url +from django.contrib import admin +from django.views.generic import TemplateView + + +urlpatterns = patterns('', + url(r'^$', TemplateView.as_view(template_name='home.html'), name='home'), + url(r'^accounts/login/$', auth_views.login, {'template_name': 'accounts/login.html'}, name='login'), + url(r'^accounts/logout/$', auth_views.logout, {'template_name': 'accounts/logout.html'}, name='logout'), + + url(r'^openid/', include('oidc_provider.urls', namespace='oidc_provider')), + + url(r'^admin/', include(admin.site.urls)), +) From 27137e322a616a73ddfa7705416fd42b3b292713 Mon Sep 17 00:00:00 2001 From: "niccolasmendoza@gmail.com" Date: Mon, 8 Jun 2015 14:12:05 -0300 Subject: [PATCH 08/16] adding pyjwt to tests_require --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index 142995c..93f7549 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,10 @@ setup( 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], + tests_require=[ + 'pyjwt==1.1.0' + ], + install_requires=[ 'pyjwt==1.1.0', ], From fdc7830e6964bde7e2d55677f3da549398dc234e Mon Sep 17 00:00:00 2001 From: "niccolasmendoza@gmail.com" Date: Mon, 8 Jun 2015 14:26:00 -0300 Subject: [PATCH 09/16] integratin CI with travis --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4042848 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: python + +python: + - 2.7 + - 3.3 + +env: + - DJANGO=Django==1.7.9 + - DJANGO=Django==1.8 + +install: + - pip install -q $DJANGO + +script: + - PYTHONPATH=$PYTHONPATH:$PWD django-admin.py test oidc_provider --settings=oidc_provider.tests.test_settings \ No newline at end of file From c4771b347df3549aeda15a9bd9972bfe3b2abc33 Mon Sep 17 00:00:00 2001 From: "niccolasmendoza@gmail.com" Date: Mon, 8 Jun 2015 14:34:37 -0300 Subject: [PATCH 10/16] adding coverage config --- .coveragerc | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..c904932 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,8 @@ +[run] +omit = + tests/* + example_project/* + .tox/* + setup.py + *.egg/* + */__main__.py \ No newline at end of file From 30d8ce79d596e7502d2f717d0ad67a3d254ee076 Mon Sep 17 00:00:00 2001 From: "niccolasmendoza@gmail.com" Date: Mon, 8 Jun 2015 14:37:31 -0300 Subject: [PATCH 11/16] .. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4042848..8804f59 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ env: - DJANGO=Django==1.8 install: - - pip install -q $DJANGO + - pip install $DJANGO script: - PYTHONPATH=$PYTHONPATH:$PWD django-admin.py test oidc_provider --settings=oidc_provider.tests.test_settings \ No newline at end of file From a71fc88a61d11062231e140bb95b8befb297c228 Mon Sep 17 00:00:00 2001 From: "niccolasmendoza@gmail.com" Date: Mon, 8 Jun 2015 14:46:22 -0300 Subject: [PATCH 12/16] updating travis config.. --- .travis.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8804f59..afbf42e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,12 @@ language: python - python: - - 2.7 - - 3.3 - + - "2.6" + - "2.7" + - "3.2" env: - - DJANGO=Django==1.7.9 - - DJANGO=Django==1.8 - + - DJANGO=1.7.9 + - DJANGO=1.8.2 install: - - pip install $DJANGO - + - pip install -q Django==$DJANGO --use-mirrors script: - PYTHONPATH=$PYTHONPATH:$PWD django-admin.py test oidc_provider --settings=oidc_provider.tests.test_settings \ No newline at end of file From 59339ba59d52aaebd38130786ebd1fc081dee777 Mon Sep 17 00:00:00 2001 From: "niccolasmendoza@gmail.com" Date: Mon, 8 Jun 2015 14:50:17 -0300 Subject: [PATCH 13/16] updating travis config.. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index afbf42e..e4a50ec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,12 @@ language: python python: - - "2.6" - "2.7" - "3.2" env: - - DJANGO=1.7.9 + - DJANGO=1.7.8 - DJANGO=1.8.2 install: - pip install -q Django==$DJANGO --use-mirrors + - pip install pyjwt==1.1.0 --use-mirrors script: - PYTHONPATH=$PYTHONPATH:$PWD django-admin.py test oidc_provider --settings=oidc_provider.tests.test_settings \ No newline at end of file From de54a7d6d59689ffa414f9bb890c9ff53bc01fb6 Mon Sep 17 00:00:00 2001 From: "niccolasmendoza@gmail.com" Date: Mon, 8 Jun 2015 14:53:17 -0300 Subject: [PATCH 14/16] no compatibility with python 3.2 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e4a50ec..9e6b1fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - "2.7" - - "3.2" env: - DJANGO=1.7.8 - DJANGO=1.8.2 From 521ff2d7dc3b0cb9eef61427c8c1d1abec3e8af3 Mon Sep 17 00:00:00 2001 From: "niccolasmendoza@gmail.com" Date: Mon, 8 Jun 2015 14:57:23 -0300 Subject: [PATCH 15/16] updating readme.. --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index cc1baa4..7e0467c 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,10 @@ Django OIDC Provider #################### +.. image:: https://api.travis-ci.org/django-py/django-openid-provider.png?branch=master + :alt: Build Status + :target: http://travis-ci.org/django-py/django-openid-provider + Django OIDC Provider can help you providing out of the box all the endpoints, data and logic needed to add OpenID Connect capabilities to your Django projects. From 521708f718d2f03e14e886c8014af33a87dc371e Mon Sep 17 00:00:00 2001 From: "niccolasmendoza@gmail.com" Date: Mon, 8 Jun 2015 16:36:49 -0300 Subject: [PATCH 16/16] adding logger for tests --- oidc_provider/lib/endpoints/authorize.py | 8 ++++ oidc_provider/lib/endpoints/token.py | 15 ++++++- oidc_provider/tests/test_settings.py | 52 +++++++++++++++++++++++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index b119830..2ea3665 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -1,8 +1,12 @@ +import logging + from oidc_provider.lib.errors import * from oidc_provider.lib.utils.params import * from oidc_provider.lib.utils.token import * from oidc_provider.models import * +logger = logging.getLogger(__name__) + class AuthorizeEndpoint(object): @@ -128,6 +132,10 @@ class AuthorizeEndpoint(object): if self.params.response_type == 'id_token token': uri += '&access_token={0}'.format(token.access_token) except: + logger.error('Authorization server error, grant_type: %s' %self.grant_type, extra={ + 'redirect_uri': self.redirect_uri, + 'state': self.params.state + }) raise AuthorizeError( self.params.redirect_uri, 'server_error', diff --git a/oidc_provider/lib/endpoints/token.py b/oidc_provider/lib/endpoints/token.py index 9c5f9dd..ef05ec4 100644 --- a/oidc_provider/lib/endpoints/token.py +++ b/oidc_provider/lib/endpoints/token.py @@ -1,3 +1,4 @@ +import logging import urllib from django.http import JsonResponse @@ -8,6 +9,7 @@ from oidc_provider.lib.utils.token import * from oidc_provider.models import * from oidc_provider import settings +logger = logging.getLogger(__name__) class TokenEndpoint(object): @@ -16,6 +18,11 @@ class TokenEndpoint(object): self.params = Params() self._extract_params() + logger.debug('Request %s', self.request) + logger.debug('TokenEndPoint request.POST --> : %s', self.request.POST) + logger.debug('TokenEndpoint request.GET --> : %s', self.request.GET) + logger.debug('TokenEndPoint extract_params --> : %s', self.params.__dict__) + def _extract_params(self): query_dict = self.request.POST @@ -29,21 +36,25 @@ class TokenEndpoint(object): def validate_params(self): if not (self.params.grant_type == 'authorization_code'): + logger.error('Unsupported grant type: --> : %s', self.params.grant_type) 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): + logger.error('Invalid client, client secret -->: %s', self.params.client_secret) raise TokenError('invalid_client') if not (self.params.redirect_uri in self.client.redirect_uris): + logger.error('Invalid client, redirect_uri --> : %s', self.params.redirect_uri) raise TokenError('invalid_client') self.code = Code.objects.get(code=self.params.code) if not (self.code.client == self.client) \ or self.code.has_expired(): + logger.error('Invalid grant, code client --> %s', self.code.client) raise TokenError('invalid_grant') except Client.DoesNotExist: @@ -77,7 +88,7 @@ class TokenEndpoint(object): 'expires_in': settings.get('OIDC_TOKEN_EXPIRE'), 'id_token': id_token, } - + logger.debug('Response dictionary --> : %s', dic) return dic @classmethod @@ -89,4 +100,6 @@ class TokenEndpoint(object): response['Cache-Control'] = 'no-store' response['Pragma'] = 'no-cache' + logger.debug('JSON Response --> : %s', response.__dict__) + return response diff --git a/oidc_provider/tests/test_settings.py b/oidc_provider/tests/test_settings.py index e15ca5c..0e158e1 100644 --- a/oidc_provider/tests/test_settings.py +++ b/oidc_provider/tests/test_settings.py @@ -1,5 +1,8 @@ +import os from datetime import timedelta +DEBUG = False + DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', @@ -16,6 +19,53 @@ MIDDLEWARE_CLASSES = ( ) +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + + 'formatters': { + 'simple': { + 'format': '%(asctime)s %(process)d [%(levelname)s] %(name)s Line: %(lineno)s id: %(process)d : %(message)s' + } + }, + + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse', + } + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'filters': ['require_debug_false'], + 'formatter': 'simple', + }, + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler', + 'formatter': 'simple', + }, + "debug_file_handler": { + "class": "logging.handlers.RotatingFileHandler", + "level": "DEBUG", + "formatter": "simple", + 'filename': 'debug.log', + 'formatter': 'simple', + "maxBytes": 10485760, + "backupCount": 20, + "encoding": "utf8" + } + }, + + 'loggers': { + 'oidc_provider': { + 'handlers': ['console', 'debug_file_handler'], + 'level': os.getenv('DJANGO_LOG_LEVEL', 'DEBUG'), + }, + }, +} + INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', @@ -27,7 +77,7 @@ INSTALLED_APPS = ( 'oidc_provider', ) -SECRET_KEY = 'secret-for-test-secret-secret' +SECRET_KEY = 'secret-for-test-secret-top-secret' ROOT_URLCONF = 'oidc_provider.tests.test_urls'