Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@
'style-vendor': {
'source_filenames': [
'css/vendor/normalize.css',
'css/vendor/office365.css',
'css/vendor/font-awesome.css',
'css/vendor/html5-input-polyfills/number-polyfill.css',
'js/vendor/CodeMirror/codemirror.css',
Expand Down
16 changes: 15 additions & 1 deletion common/djangoapps/third_party_auth/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def B(*args, **kwargs):

See http://psa.matiasaguirre.net/docs/pipeline.html for more docs.
"""

import pkg_resources
import random
import string # pylint: disable-msg=deprecated-module
from collections import OrderedDict
Expand Down Expand Up @@ -556,7 +556,21 @@ def set_logged_in_cookie(backend=None, user=None, request=None, auth_entry=None,
# Check that the cookie isn't already set.
# This ensures that we allow the user to continue to the next
# pipeline step once he/she has the cookie set by this step.

try:
if not hasattr(request, 'COOKIES'):

version = pkg_resources.get_distribution("python-social-auth").version
version_parts = tuple([int(x) for x in version.split('.') if x.isdigit()])

if float(str(version_parts[0]) + "." + str(version_parts[1])) >= 0.2:
return;

except Exception:
pass

has_cookie = student.helpers.is_logged_in_cookie_set(request)

if not has_cookie:
try:
redirect_url = get_complete_url(backend.name)
Expand Down
36 changes: 30 additions & 6 deletions common/djangoapps/third_party_auth/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
invoke the Django armature.
"""

from social.backends import google, linkedin, facebook
from social.backends import google, linkedin, facebook, azuread

_DEFAULT_ICON_CLASS = 'fa-signin'

Expand All @@ -21,9 +21,11 @@ class BaseProvider(object):
# String. Name of the FontAwesome glyph to use for sign in buttons (or the
# name of a user-supplied custom glyph that is present at runtime).
ICON_CLASS = _DEFAULT_ICON_CLASS
# String. User-facing name of the provider. Must be unique across all
# enabled providers. Will be presented in the UI.
# String. Name of the provider. Must be unique across all enabled providers.
NAME = None
# String. User-facing name of the provider that shows up in the login button text.
# Can contain blanks and will be presented in the UI.
UINAME = None
# Dict of string -> object. Settings that will be merged into Django's
# settings instance. In most cases the value will be None, since real
# values are merged from .json files (foo.auth.json; foo.env.json) onto the
Expand Down Expand Up @@ -115,7 +117,7 @@ class GoogleOauth2(BaseProvider):

BACKEND_CLASS = google.GoogleOAuth2
ICON_CLASS = 'fa-google-plus'
NAME = 'Google'
NAME = UINAME = 'Google'
SETTINGS = {
'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY': None,
'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET': None,
Expand All @@ -135,7 +137,7 @@ class LinkedInOauth2(BaseProvider):

BACKEND_CLASS = linkedin.LinkedinOAuth2
ICON_CLASS = 'fa-linkedin'
NAME = 'LinkedIn'
NAME = UINAME = 'LinkedIn'
SETTINGS = {
'SOCIAL_AUTH_LINKEDIN_OAUTH2_KEY': None,
'SOCIAL_AUTH_LINKEDIN_OAUTH2_SECRET': None,
Expand All @@ -155,7 +157,7 @@ class FacebookOauth2(BaseProvider):

BACKEND_CLASS = facebook.FacebookOAuth2
ICON_CLASS = 'fa-facebook'
NAME = 'Facebook'
NAME = UINAME = 'Facebook'
SETTINGS = {
'SOCIAL_AUTH_FACEBOOK_KEY': None,
'SOCIAL_AUTH_FACEBOOK_SECRET': None,
Expand All @@ -170,6 +172,28 @@ def get_name(cls, provider_details):
return provider_details.get('fullname')


class AzureADOauth2(BaseProvider):
"""Provider for Microsoft Azure Active Directory's Oauth2 auth system."""

BACKEND_CLASS = azuread.AzureADOAuth2
ICON_CLASS = 'img-office365'
NAME = 'AzureAD'
UINAME = 'Office 365'
SETTINGS = {
'SOCIAL_AUTH_AZUREAD_OAUTH2_KEY': None,
'SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET': None,
'SOCIAL_AUTH_AZUREAD_OAUTH2_RESOURCE': None
}

@classmethod
def get_email(cls, provider_details):
return provider_details.get('email')

@classmethod
def get_name(cls, provider_details):
return provider_details.get('fullname')


class Registry(object):
"""Singleton registry of third-party auth providers.

Expand Down
53 changes: 53 additions & 0 deletions common/djangoapps/third_party_auth/tests/specs/test_azuread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Integration tests for AzureAd providers."""

from third_party_auth import provider
from third_party_auth.tests.specs import base


class AzureADOAuth2IntegrationTest(base.Oauth2IntegrationTest):
"""Integration tests for provider.AzureADOAuth2."""

PROVIDER_CLASS = provider.AzureADOauth2
PROVIDER_SETTINGS = {
'SOCIAL_AUTH_AZUREAD_OAUTH2_KEY': 'azure_oauth2_key',
'SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET': 'azure_oauth2_secret',
'SOCIAL_AUTH_AZUREAD_OAUTH2_RESOURCE': 'https://mysite-my.sharepoint.com'
}
TOKEN_RESPONSE_DATA = {
'access_token': 'foobar',
'token_type': 'bearer',
'id_token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83Mjc0MDZhYy03MDY4'
'LTQ4ZmEtOTJiOS1jMmQ2NzIxMWJjNTAvIiwiaWF0IjpudWxsLCJleHAiOm51bGwsImF1ZCI6IjAyOWNjMDEwLWJiNzQtNGQyY'
'i1hMDQwLWY5Y2VkM2ZkMmM3NiIsInN1YiI6InFVOHhrczltSHFuVjZRMzR6aDdTQVpvY2loOUV6cnJJOW1wVlhPSWJWQTgiLC'
'J2ZXIiOiIxLjAiLCJ0aWQiOiI3Mjc0MDZhYy03MDY4LTQ4ZmEtOTJiOS1jMmQ2NzIxMWJjNTAiLCJvaWQiOiI3ZjhlMTk2OS0'
'4YjgxLTQzOGMtOGQ0ZS1hZDZmNTYyYjI4YmIiLCJ1cG4iOiJmb29iYXJAdGVzdC5vbm1pY3Jvc29mdC5jb20iLCJnaXZlbl9u'
'YW1lIjoiZm9vIiwiZmFtaWx5X25hbWUiOiJiYXIiLCJuYW1lIjoiZm9vIGJhciIsInVuaXF1ZV9uYW1lIjoiZm9vYmFyQHRlc'
'3Qub25taWNyb3NvZnQuY29tIiwicHdkX2V4cCI6IjQ3MzMwOTY4IiwicHdkX3VybCI6Imh0dHBzOi8vcG9ydGFsLm1pY3Jvc2'
'9mdG9ubGluZS5jb20vQ2hhbmdlUGFzc3dvcmQuYXNweCJ9.3V50dHXTZOHj9UWtkn2g7BjX5JxNe8skYlK4PdhiLz4',
'expires_in': 3600,
'expires_on': 1423650396,
'not_before': 1423646496
}

USER_RESPONSE_DATA = {
"iss": "https://sts.windows.net/727406ac-7068-48fa-92b9-c2d67211bc50/",
"iat": 'null',
"exp": 'null',
"aud": "029cc010-bb74-4d2b-a040-f9ced3fd2c76",
"sub": "qU8xks9mHqnV6Q34zh7SAZocih9EzrrI9mpVXOIbVA8",
"ver": "1.0",
"tid": "727406ac-7068-48fa-92b9-c2d67211bc50",
"oid": "7f8e1969-8b81-438c-8d4e-ad6f562b28bb",
"upn": "foobar@test.onmicrosoft.com",
"given_name": "foo",
"family_name": "bar",
"name": "foo bar",
"unique_name": "foobar@test.onmicrosoft.com",
"pwd_exp": "47330968",
"pwd_url": "https://portal.microsoftonline.com/ChangePassword.aspx"
}

def get_username(self):
response_data = self.get_response_data()
return response_data.get('upn')

7 changes: 7 additions & 0 deletions common/static/css/vendor/office365.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.img-office365{
background-image: url('../../images/office365.png');
vertical-align: sub;
display: inline-block;
height: 21px;
width: 22px;
}
Binary file added common/static/images/office365.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lms/djangoapps/student_account/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ def account_settings_context(request):

context['auth']['providers'] = [{
'name': state.provider.NAME, # The name of the provider e.g. Facebook
'uiname': state.provider.UINAME, # The ui name of the provider e.g. Office 365
'connected': state.has_account, # Whether the user's edX account is connected with the provider.
# If the user is not connected, they should be directed to this page to authenticate
# with the particular provider.
Expand Down
1 change: 1 addition & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,7 @@
'style-vendor': {
'source_filenames': [
'css/vendor/font-awesome.css',
'css/vendor/office365.css',
'css/vendor/jquery.qtip.min.css',
'css/vendor/responsive-carousel/responsive-carousel.css',
'css/vendor/responsive-carousel/responsive-carousel.slide.css',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,9 @@
fields: _.map(authData.providers, function(provider) {
return {
'view': new AccountSettingsFieldViews.AuthFieldView({
title: provider.name,
title: provider.uiname,
screenReaderTitle: interpolate_text(
gettext("Connect your {accountName} account"), {accountName: provider['name']}
gettext("Connect your {accountName} account"), {accountName: provider['uiname']}
),
valueAttribute: 'auth-' + provider.name.toLowerCase(),
helpMessage: '',
Expand Down
2 changes: 1 addition & 1 deletion lms/templates/dashboard/_dashboard_third_party_error.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<h2 class="sr">${_("Could Not Link Accounts")}</h2>
<div class="copy">
## Translators: this message is displayed when a user tries to link their account with a third-party authentication provider (for example, Google or LinkedIn) with a given edX account, but their third-party account is already associated with another edX account. provider_name is the name of the third-party authentication provider, and platform_name is the name of the edX deployment.
<p>${_("The {provider_name} account you selected is already linked to another {platform_name} account.").format(provider_name='<strong>{duplicate_provider}</strong>'.format(duplicate_provider=duplicate_provider.NAME), platform_name=platform_name)}</p>
<p>${_("The {provider_name} account you selected is already linked to another {platform_name} account.").format(provider_name='<strong>{duplicate_provider}</strong>'.format(duplicate_provider=duplicate_provider.UINAME), platform_name=platform_name)}</p>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion lms/templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ <h2 class="sr">${_('Account Preferences')}</h2>

% for enabled in provider.Registry.enabled():
## Translators: provider_name is the name of an external, third-party user authentication provider (like Google or LinkedIn).
<button type="submit" class="button button-primary button-${enabled.NAME} login-${enabled.NAME}" onclick="thirdPartySignin(event, '${pipeline_url[enabled.NAME]}');"><span class="icon fa ${enabled.ICON_CLASS}"></span>${_('Sign in with {provider_name}').format(provider_name=enabled.NAME)}</button>
<button type="submit" class="button button-primary button-${enabled.NAME} login-${enabled.NAME}" onclick="thirdPartySignin(event, '${pipeline_url[enabled.NAME]}');"><span class="icon fa ${enabled.ICON_CLASS}"></span>${_('Sign in with {provider_name}').format(provider_name=enabled.UINAME)}</button>
% endfor

</div>
Expand Down
2 changes: 1 addition & 1 deletion lms/templates/register.html
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ <h3 class="message-title">${_("The following errors occurred while processing yo

% for enabled in provider.Registry.enabled():
## Translators: provider_name is the name of an external, third-party user authentication service (like Google or LinkedIn).
<button type="submit" class="button button-primary button-${enabled.NAME} register-${enabled.NAME}" onclick="thirdPartySignin(event, '${pipeline_urls[enabled.NAME]}');"><span class="icon fa ${enabled.ICON_CLASS}"></span>${_('Sign up with {provider_name}').format(provider_name=enabled.NAME)}</button>
<button type="submit" class="button button-primary button-${enabled.NAME} register-${enabled.NAME}" onclick="thirdPartySignin(event, '${pipeline_urls[enabled.NAME]}');"><span class="icon fa ${enabled.ICON_CLASS}"></span>${_('Sign up with {provider_name}').format(provider_name=enabled.UINAME)}</button>
% endfor

</div>
Expand Down
2 changes: 1 addition & 1 deletion lms/templates/student_account/login.underscore
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
if ( provider.loginUrl ) { %>
<button type="button" class="button button-primary button-<%- provider.name %> login-provider login-<%- provider.name %>" data-provider-url="<%- provider.loginUrl %>">
<div class="icon fa <%- provider.iconClass %>" aria-hidden="true"></div>
<%- provider.name %>
<%- provider.uiname %>
</button>
<% }
}); %>
Expand Down
2 changes: 1 addition & 1 deletion lms/templates/student_account/register.underscore
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
if ( provider.registerUrl ) { %>
<button type="button" class="button button-primary button-<%- provider.name %> login-provider register-<%- provider.name %>" data-provider-url="<%- provider.registerUrl %>">
<span class="icon fa <%- provider.iconClass %>" aria-hidden="true"></span>
<%- provider.name %>
<%- provider.uiname %>
</button>
<% }
}); %>
Expand Down
2 changes: 1 addition & 1 deletion lms/templates/student_profile/third_party_auth.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<i class="icon fa fa-unlink"></i><span class="copy">${_('Not Linked')}</span>
% endif
</div>
<span class="provider">${state.provider.NAME}</span>
<span class="provider">${state.provider.UINAME}</span>
<span class="control">
<form
action="${pipeline.get_disconnect_url(state.provider.NAME)}"
Expand Down
2 changes: 1 addition & 1 deletion requirements/edx/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pyparsing==2.0.1
python-memcached==1.48
python-openid==2.2.5
python-dateutil==2.1
python-social-auth==0.1.23
-e git+https://github.com/MSOpenTech/python-social-auth-1.git@dev#egg=python-social-auth-1
pytz==2015.2
pysrt==0.4.7
PyYAML==3.10
Expand Down