Skip to content
Browse files

Add openstax accounts authentication policy

  • Loading branch information...
1 parent 1bb8987 commit ed20cae9717a1a9efe6cdfb0e46f7d5f16d2fd9e @karenc committed Feb 25, 2014
View
8 README.rst
@@ -11,3 +11,11 @@ INSTALL
1. ``virtualenv .``
2. ``./bin/python setup.py install``
+
+3. Set up openstax/services (See karenc/openstax-setup)
+
+4. Register this app with openstax/accounts
+
+5. Copy development.ini.example to development.ini and change the values
+
+6. Start the app by ``./bin/pserve development.ini``
View
14 development.ini.example
@@ -0,0 +1,14 @@
+[app:main]
+use = egg:openstax-accounts-pyramid
+
+openstax_accounts.server_url = https://localhost:3000/
+openstax_accounts.application_id = 940128529654aaaa8826654d3da1b992d815cd4bc2563e13dc66e6b18728dedf
+openstax_accounts.application_secret = 226af66711708a044620199daf68a95a201e893c9f9cff2d46a5437520dda21e
+openstax_accounts.application_url = http://localhost:8000/
+openstax_accounts.login_path = /login
+openstax_accounts.callback_path = /callback
+
+[server:main]
+use = egg:waitress#main
+host = 0.0.0.0
+port = 8000
View
0 openstax_accounts_pyramid/__init__.py
No changes.
View
96 openstax_accounts_pyramid/authentication_policy.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+
+import json
+import urlparse
+import uuid
+
+from pyramid.httpexceptions import HTTPFound
+from pyramid.interfaces import IAuthenticationPolicy
+from pyramid.security import Everyone, Authenticated
+import sanction
+from zope.interface import implements
+
+
+def get_user_from_session(request):
+ """Create a helper function for getting the user profile from request.user
+ """
+ return request.session.get('profile')
+
+
+class OpenstaxAccountsAuthenticationPolicy(object):
+ implements(IAuthenticationPolicy)
+
+ def __init__(self, server_url, application_id, application_secret,
+ application_url, login_path, callback_path):
+ resource_url = server_url
+ authorize_url = urlparse.urljoin(server_url, '/oauth/authorize')
+ token_url = urlparse.urljoin(server_url, '/oauth/token')
+ self.redirect_uri = urlparse.urljoin(application_url, '/callback')
+
+ self.sanction_client = sanction.Client(
+ auth_endpoint=authorize_url,
+ token_endpoint=token_url,
+ resource_endpoint=resource_url,
+ client_id=application_id,
+ client_secret=application_secret)
+
+ self.login_path = login_path
+ self.callback_path = callback_path
+
+ def _login(self, request):
+ raise HTTPFound(location=self.sanction_client.auth_uri(redirect_uri=self.redirect_uri))
+
+ def _callback(self, request):
+ code = request.params['code']
+ def parser_remove_null_expires_in(data):
+ data = json.loads(data)
+ if data.get('expires_in', '') is None:
+ data.pop('expires_in')
+ return data
+
+ self.sanction_client.request_token(
+ parser=parser_remove_null_expires_in,
+ code=code,
+ redirect_uri=self.redirect_uri)
+
+ def authenticated_userid(self, request):
+ if request.path == self.login_path:
+ return self._login(request)
+ if request.path == self.callback_path:
+ self._callback(request)
+ me = self.sanction_client.request('/api/v1/me.json')
+ request.session.update({
+ 'profile': me,
+ 'username': me.get('username'),
+ })
+ request.session.changed()
+ return me.get('username')
+ return self.unauthenticated_userid(request)
+
+ def unauthenticated_userid(self, request):
+ return request.session.get('username')
+
+ def effective_principals(self, request):
+ groups = [Everyone]
+ if self.authenticated_userid(request):
+ groups.append(Authenticated)
+ return groups
+
+ def remember(self, request, principal, **kw):
+ pass
+
+ def forget(self, request):
+ request.session.clear()
+
+
+def main(config):
+ config.add_request_method(get_user_from_session, 'user', reify=True)
+ settings = config.registry.settings
+ config.set_authentication_policy(OpenstaxAccountsAuthenticationPolicy(
+ server_url=settings['openstax_accounts.server_url'],
+ application_id=settings['openstax_accounts.application_id'],
+ application_secret=settings['openstax_accounts.application_secret'],
+ application_url=settings['openstax_accounts.application_url'],
+ login_path=settings['openstax_accounts.login_path'],
+ callback_path=settings['openstax_accounts.callback_path'],
+ ))
View
101 openstax_accounts_pyramid/example.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+
+import uuid
+
+from pyramid.authorization import ACLAuthorizationPolicy
+from pyramid.config import Configurator
+from pyramid.httpexceptions import HTTPFound
+from pyramid.response import Response
+from pyramid.security import (Allow, Everyone,
+ Authenticated, forget)
+from pyramid.session import UnencryptedCookieSessionFactoryConfig
+from pyramid.url import route_url
+from pyramid.view import view_config
+
+class Site(object):
+ __name__ = ''
+ __parent__ = None
+ __acl__ = [
+ (Allow, Authenticated, 'protected'),
+ (Allow, Everyone, 'view'),
+ ]
+
+ def __init__(self, request):
+ self.request = request
+
+def menu(request):
+ user = request.user
+ if user:
+ login_status = 'logged in'
+ login_logout_path = request.route_url('logout')
+ login_logout_text = 'Log out'
+ else:
+ login_status = 'not logged in'
+ login_logout_path = request.route_url('login')
+ login_logout_text = 'Log in'
+ return '''
+<ul>
+ <li>You are currently {login_status}.</li>
+ <li><a href="{hello_world_path}">Hello World!</a></li>
+ <li><a href="{profile_path}">Profile</a></li>
+ <li><a href="{login_logout_path}">{login_logout_text}</a></li>
+</ul>'''.format(
+ login_status=login_status,
+ hello_world_path=request.route_url('hello-world'),
+ login_logout_path=login_logout_path,
+ login_logout_text=login_logout_text,
+ profile_path=request.route_url('profile'),
+ )
+
+@view_config(route_name='index')
+def index(request):
+ return Response(menu(request))
+
+@view_config(route_name='hello-world', context=Site, permission='view')
+def hello_world(request):
+ return Response(menu(request) + '<p>Hello world!</p>')
+
+@view_config(route_name='profile', context=Site, permission='protected')
+def profile(request):
+ user = request.user
+ profile = '<ul>' + ''.join([
+ '<li><strong>{}</strong>: {}</li>'.format(k, v)
+ for k, v in user.iteritems()]) + '</ul>'
+ return Response(menu(request) + '<p>Profile</p>' + profile)
+
+@view_config(route_name='callback', context=Site, permission='protected')
+def callback(request):
+ # callback must be protected, so that effective_principals is called
+ # callback must redirect
+ return HTTPFound(location='/')
+
+@view_config(route_name='login', context=Site, permission='protected')
+def login(request):
+ # login must be protected, so that effective_principals is called
+ pass
+
+@view_config(route_name='logout')
+def logout(request):
+ forget(request)
+ raise HTTPFound(location='/')
+
+def main(global_config, **settings):
+ session_factory = UnencryptedCookieSessionFactoryConfig(
+ str(uuid.uuid4()))
+
+ config = Configurator(settings=settings, root_factory=Site,
+ session_factory=session_factory)
+ config.add_route('index', '/')
+ config.add_route('hello-world', '/hello-world')
+ config.add_route('profile', '/profile')
+ config.add_route('callback', '/callback')
+ config.add_route('login', '/login')
+ config.add_route('logout', '/logout')
+ config.scan()
+
+ # use the openstax accounts authentication policy
+ config.include('openstax_accounts_pyramid.authentication_policy.main')
+
+ # authorization policy must be set if an authentication policy is set
+ config.set_authorization_policy(ACLAuthorizationPolicy())
+ return config.make_wsgi_app()
View
10 setup.py
@@ -1,4 +1,4 @@
-from setuptools import setup
+from setuptools import setup, find_packages
setup(
name='openstax-accounts-pyramid',
@@ -8,8 +8,16 @@
author='Karen Chan',
author_email='karen@karen-chan.com',
url='http://github.com/karenc/openstax-accounts-pyramid',
+ packages=find_packages(),
install_requires=(
+ 'PasteDeploy',
'pyramid',
'sanction',
+ 'waitress',
),
+ entry_points={
+ 'paste.app_factory': [
+ 'main = openstax_accounts_pyramid.example:main',
+ ],
+ },
)

0 comments on commit ed20cae

Please sign in to comment.
Something went wrong with that request. Please try again.