-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Pull in custom backend and pipeline steps from analytics-dashboard
This includes pared-down unit tests from analytics-dashboard.
- Loading branch information
Renzo Lucioni
committed
Feb 19, 2015
1 parent
41430dd
commit 7e5b2fd
Showing
19 changed files
with
1,125 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
language: python | ||
python: | ||
- "2.7" | ||
install: | ||
- pip install -qr requirements.txt | ||
- pip install coveralls | ||
script: ./test.sh | ||
after_success: | ||
coveralls |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Renzo Lucioni <[email protected]> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
How To Contribute | ||
================= | ||
|
||
Contributions are very welcome. | ||
|
||
Please read `How To Contribute <https://github.com/edx/edx-platform/blob/master/CONTRIBUTING.rst>`_ for details. | ||
|
||
Even though it was written with ``edx-platform`` in mind, the guidelines | ||
should be followed for Open edX code in general. |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include README.rst |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
auth-backends |Travis|_ |Coveralls|_ | ||
============= | ||
.. |Travis| image:: https://travis-ci.org/edx/auth-backends.svg?branch=master | ||
.. _Travis: https://travis-ci.org/edx/auth-backends | ||
|
||
.. |Coveralls| image:: https://img.shields.io/coveralls/edx/auth-backends.svg | ||
.. _Coveralls: https://coveralls.io/r/edx/auth-backends?branch=master | ||
|
||
This repo houses custom authentication backends and pipeline steps used by edX | ||
projects such as the `edx-analytics-dashboard <https://github.com/edx/edx-analytics-dashboard>`_ | ||
and `edx-ecommerce <https://github.com/edx/edx-ecommerce>`_. | ||
|
||
This project is new and under active development. | ||
|
||
Overview | ||
-------- | ||
|
||
Included backends: | ||
|
||
=============== ============================================ | ||
Backend Purpose | ||
--------------- -------------------------------------------- | ||
Open ID Connect Authenticate with the LMS, an OIDC provider. | ||
=============== ============================================ | ||
|
||
This package requires Django 1.7. Required Django settings: | ||
|
||
============================================ ============================================ | ||
Setting Default | ||
-------------------------------------------- -------------------------------------------- | ||
SOCIAL_AUTH_EDX_OIDC_KEY None | ||
SOCIAL_AUTH_EDX_OIDC_SECRET None | ||
SOCIAL_AUTH_EDX_OIDC_ID_TOKEN_DECRYPTION_KEY None | ||
SOCIAL_AUTH_EDX_OIDC_URL_ROOT None | ||
EXTRA_SCOPE [] | ||
COURSE_PERMISSIONS_CLAIMS [] | ||
============================================ ============================================ | ||
|
||
Set these to the correct values for your OAuth2/OpenID Connect provider. ``SOCIAL_AUTH_EDX_OIDC_ID_TOKEN_DECRYPTION_KEY`` | ||
should be the same as ``SOCIAL_AUTH_EDX_OIDC_SECRET``. Set ``EXTRA_SCOPE`` equal to a list of scope strings to request | ||
additional information from the edX OAuth2 provider at the moment of authentication (e.g., provide course permissions bits | ||
to get a full list of courses). | ||
|
||
Testing | ||
------- | ||
|
||
Execute ``test.sh`` to run the test suite. | ||
|
||
License | ||
------- | ||
|
||
The code in this repository is licensed under the AGPL unless otherwise noted. | ||
|
||
Please see ``LICENSE.txt`` for details. | ||
|
||
How To Contribute | ||
----------------- | ||
|
||
Contributions are very welcome! | ||
|
||
Please read `How To Contribute <https://github.com/edx/edx-platform/blob/master/CONTRIBUTING.rst>`_ for details. | ||
|
||
Even though it was written with `edx-platform <https://github.com/edx/edx-platform>`_ in mind, | ||
the guidelines should be followed for Open edX code in general. | ||
|
||
Reporting Security Issues | ||
------------------------- | ||
|
||
Please do not report security issues in public. Please email [email protected]. | ||
|
||
Mailing List and IRC Channel | ||
---------------------------- | ||
|
||
You can discuss this code on the `edx-code Google Group`__ or in the | ||
``#edx-code`` IRC channel on Freenode. | ||
|
||
__ https://groups.google.com/forum/#!forum/edx-code |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
"""Django authentication backends. | ||
For more information visit https://docs.djangoproject.com/en/dev/topics/auth/customizing/. | ||
""" | ||
import json | ||
|
||
from django.conf import settings | ||
import django.dispatch | ||
from social.backends.open_id import OpenIdConnectAuth | ||
|
||
|
||
# pylint: disable=abstract-method | ||
class EdXOpenIdConnect(OpenIdConnectAuth): | ||
name = 'edx-oidc' | ||
|
||
ACCESS_TOKEN_METHOD = 'POST' | ||
REDIRECT_STATE = False | ||
ID_KEY = 'preferred_username' | ||
|
||
DEFAULT_SCOPE = ['openid', 'profile', 'email'] + getattr(settings, 'EXTRA_SCOPE', []) | ||
ID_TOKEN_ISSUER = getattr(settings, 'SOCIAL_AUTH_EDX_OIDC_URL_ROOT', None) | ||
AUTHORIZATION_URL = '{0}/authorize/'.format(ID_TOKEN_ISSUER) | ||
ACCESS_TOKEN_URL = '{0}/access_token/'.format(ID_TOKEN_ISSUER) | ||
USER_INFO_URL = '{0}/user_info/'.format(ID_TOKEN_ISSUER) | ||
|
||
PROFILE_TO_DETAILS_KEY_MAP = { | ||
'preferred_username': u'username', | ||
'email': u'email', | ||
'name': u'full_name', | ||
'given_name': u'first_name', | ||
'family_name': u'last_name', | ||
'locale': u'language', | ||
} | ||
|
||
auth_complete_signal = django.dispatch.Signal(providing_args=["user", "id_token"]) | ||
|
||
def user_data(self, _access_token, *_args, **_kwargs): | ||
# Include decoded id_token fields in user data. | ||
return self.id_token | ||
|
||
def auth_complete_params(self, state=None): | ||
params = super(EdXOpenIdConnect, self).auth_complete_params(state) | ||
|
||
# TODO: Due a limitation in the OIDC provider in the LMS, the list of all course permissions | ||
# is computed during the authentication process. As an optimization, we explicitly request | ||
# the list here, avoiding further roundtrips. This is no longer necessary once the limitation | ||
# is resolved and instead the course permissions can be requested on a need to have basis, | ||
# reducing overhead significantly. | ||
claim_names = getattr(settings, 'COURSE_PERMISSIONS_CLAIMS', []) | ||
courses_claims_request = {name: {'essential': True} for name in claim_names} | ||
params['claims'] = json.dumps({'id_token': courses_claims_request}) | ||
|
||
return params | ||
|
||
def auth_complete(self, *args, **kwargs): | ||
# WARNING: During testing, the user model class is `social.tests.models` and not the one | ||
# specified for the application. | ||
user = super(EdXOpenIdConnect, self).auth_complete(*args, **kwargs) | ||
self.auth_complete_signal.send(sender=self.__class__, user=user, id_token=self.id_token) | ||
return user | ||
|
||
def get_user_claims(self, access_token, claims=None): | ||
"""Returns a dictionary with the values for each claim requested.""" | ||
|
||
data = self.get_json( | ||
self.USER_INFO_URL, | ||
headers={'Authorization': 'Bearer {0}'.format(access_token)} | ||
) | ||
|
||
if claims: | ||
claims_names = set(claims) | ||
data = {k: v for (k, v) in data.iteritems() if k in claims_names} | ||
|
||
return data | ||
|
||
def get_user_details(self, response): | ||
details = self._map_user_details(response) | ||
|
||
# Limits the scope of languages we can use | ||
locale = response.get('locale') | ||
if locale: | ||
details[u'language'] = _to_language(response['locale']) | ||
|
||
# Set superuser bit if the provider determines the user is an administrator | ||
details[u'is_superuser'] = details[u'is_staff'] = response.get('administrator', False) | ||
|
||
return details | ||
|
||
def _map_user_details(self, response): | ||
"""Maps key/values from the response to key/values in the user model. | ||
Does not transfer any key/value that is empty or not present in the reponse. | ||
""" | ||
dest = {} | ||
for source_key, dest_key in self.PROFILE_TO_DETAILS_KEY_MAP.items(): | ||
value = response.get(source_key) | ||
if value is not None: | ||
dest[dest_key] = value | ||
|
||
return dest | ||
|
||
|
||
def _to_language(locale): | ||
"""Convert locale name to language code if necessary. | ||
OpenID Connect locale needs to be converted to Django's language | ||
code. In general however, the differences between the locale names | ||
and language code are not very clear among different systems. | ||
For more information, refer to: | ||
http://openid.net/specs/openid-connect-basic-1_0.html#StandardClaims | ||
https://docs.djangoproject.com/en/1.6/topics/i18n/#term-translation-string | ||
""" | ||
return locale.replace('_', '-').lower() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
"""Python-social-auth pipeline functions. | ||
For more info visit http://python-social-auth.readthedocs.org/en/latest/pipeline.html. | ||
""" | ||
from django.contrib.auth import get_user_model | ||
|
||
|
||
User = get_user_model() | ||
|
||
|
||
# pylint: disable=unused-argument | ||
# The function parameters must be named exactly as they are below. | ||
# Do not change them to appease Pylint. | ||
def get_user_if_exists(strategy, details, user=None, *args, **kwargs): | ||
"""Return a User with the given username iff the User exists.""" | ||
if user: | ||
return {'is_new': False} | ||
try: | ||
username = details.get('username') | ||
|
||
# Return the user if it exists | ||
return { | ||
'is_new': False, | ||
'user': User.objects.get(username=username) | ||
} | ||
except User.DoesNotExist: | ||
# Fall to the default return value | ||
pass | ||
|
||
# Nothing to return since we don't have a user | ||
return {} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from django.conf import settings | ||
from social.tests.backends.oauth import OAuth2Test | ||
from social.tests.backends.open_id import OpenIdConnectTestMixin | ||
|
||
|
||
class EdXOpenIdConnectTests(OpenIdConnectTestMixin, OAuth2Test): | ||
backend_path = 'auth_backends.backends.EdXOpenIdConnect' | ||
issuer = getattr(settings, 'SOCIAL_AUTH_EDX_OIDC_URL_ROOT', None) | ||
expected_username = 'test_user' | ||
|
||
def get_id_token(self, *args, **kwargs): | ||
data = super(EdXOpenIdConnectTests, self).get_id_token(*args, **kwargs) | ||
|
||
# Set the field used to derive the username of the logged user. | ||
data['preferred_username'] = self.expected_username | ||
|
||
return data | ||
|
||
def test_login(self): | ||
user = self.do_login() | ||
self.assertIsNotNone(user) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from django.contrib.auth import get_user_model | ||
from django.test import TestCase | ||
from django_dynamic_fixture import G | ||
|
||
from auth_backends.pipeline import get_user_if_exists | ||
|
||
|
||
class PipelineTests(TestCase): | ||
def setUp(self): | ||
self.user = get_user_model() | ||
|
||
def test_get_user_if_exists(self): | ||
username = 'edx' | ||
details = {'username': username} | ||
|
||
# If no user exists, return an empty dict | ||
actual = get_user_if_exists(None, details) | ||
self.assertDictEqual(actual, {}) | ||
|
||
# If user exists, return dict with user and any additional information | ||
user = G(self.user, username=username) | ||
actual = get_user_if_exists(None, details) | ||
self.assertDictEqual(actual, {'is_new': False, 'user': user}) | ||
|
||
# If user passed to function, just return the additional information | ||
actual = get_user_if_exists(None, details, user=user) | ||
self.assertDictEqual(actual, {'is_new': False}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#!/usr/bin/env python | ||
import os | ||
import sys | ||
|
||
if __name__ == "__main__": | ||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings") | ||
|
||
from django.core.management import execute_from_command_line | ||
|
||
execute_from_command_line(sys.argv) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
Django>=1.7 # BSD | ||
|
||
# PSA package on PyPI hasn't been updated to include a fix for a breaking change in PyJWT | ||
# python-social-auth==0.2.1 # BSD License | ||
git+https://github.com/omab/python-social-auth.git@bdf69d67d109acfda1016d4a2a63a1cc0a3aba84#egg=python-social-auth | ||
|
||
# For running tests | ||
django-dynamic-fixture==1.8.1 # MIT | ||
django-nose==1.3 | ||
httpretty==0.6.5 | ||
unittest2==0.5.1 | ||
coverage==3.7.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from setuptools import setup | ||
|
||
|
||
def readme(): | ||
with open('README.rst') as f: | ||
return f.read() | ||
|
||
|
||
setup( | ||
name='edx-auth-backends', | ||
version='0.1', | ||
description='Custom edX authentication backends and pipeline steps', | ||
long_description=readme(), | ||
classifiers=[ | ||
'Development Status :: 4 - Beta', | ||
'License :: OSI Approved :: GNU Affero General Public License v3', | ||
'Programming Language :: Python :: 2.7', | ||
'Topic :: Internet', | ||
'Intended Audience :: Developers', | ||
'Environment :: Web Environment', | ||
], | ||
keywords='authentication edx', | ||
url='https://github.com/edx/auth-backends', | ||
author='edX', | ||
author_email='[email protected]', | ||
license='AGPL', | ||
packages=['auth_backends'], | ||
install_requires=[ | ||
'Django>=1.7', | ||
# PSA package on PyPI hasn't been updated to include a fix for a breaking change in PyJWT. | ||
# For reference on how dependency_links is used here, see http://goo.gl/D5g4Qq. | ||
'python-social-auth<=0.2.2' | ||
], | ||
dependency_links=[ | ||
'git+https://github.com/omab/python-social-auth.git@bdf69d67d109acfda1016d4a2a63a1cc0a3aba84#egg=python-social-auth-0.2.2', | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
#!/bin/sh | ||
|
||
python manage.py test auth_backends --with-coverage --cover-package=auth_backends |
Empty file.
Oops, something went wrong.