Initial commit.

This commit is contained in:
juanifioren 2014-12-19 12:27:43 -03:00
commit 9def141582
17 changed files with 966 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# Byte-compiled python files
__pycache__/
*.py[cod]

21
LICENSE Normal file
View file

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

3
MANIFEST.in Normal file
View file

@ -0,0 +1,3 @@
include LICENSE
include README.rst
recursive-include openid_provider/templates *

100
README.rst Normal file
View file

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

View file

View file

View file

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

View file

View file

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

View file

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

View file

115
openid_provider/models.py Normal file
View file

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

View file

@ -0,0 +1,47 @@
{% extends "openid_provider/base.html" %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-heading">
<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>
<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="state" type="hidden" value="{{ params.state }}" />
<ul class="list-group">
{% for scope in params.scope.split %}
{% if scope != 'openid' %}
<li class="list-group-item">{{ scope | capfirst }}</li>
{% endif %}
{% endfor %}
</ul>
<div class="btn-group btn-group-justified" role="group" aria-label="Justified button group">
<div class="btn-group" role="group">
<input type="submit" class="btn btn-danger" value="Decline" />
</div>
<div class="btn-group" role="group">
<input name="allow" type="submit" class="btn btn-success" value="Authorize" />
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,55 @@
{% load staticfiles %}
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>OpenID Provider</title>
<link href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.1/flatly/bootstrap.min.css" rel="stylesheet">
<style type="text/css">
body {
padding-top: 90px;
padding-bottom: 30px;
}
</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:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<!-- Fixed navbar -->
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">OpenID Provider</a>
</div>
{% if user.is_authenticated %}
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">{{ user.email }}</a></li>
</ul>
</div><!--/.nav-collapse -->
{% endif %}
</div>
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
</body>
</html>

12
openid_provider/urls.py Normal file
View file

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

90
openid_provider/views.py Normal file
View file

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

33
setup.py Normal file
View file

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