Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
700e8053
Commit
700e8053
authored
Aug 01, 2017
by
Jesse Shapiro
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Generalize support for user fields from SAP SuccessFactors SSO providers
parent
02c28a38
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
189 additions
and
21 deletions
+189
-21
cms/envs/test.py
+1
-0
common/djangoapps/third_party_auth/saml.py
+80
-12
common/djangoapps/third_party_auth/tests/specs/base.py
+3
-1
common/djangoapps/third_party_auth/tests/specs/test_testshib.py
+105
-8
No files found.
cms/envs/test.py
View file @
700e8053
...
...
@@ -36,6 +36,7 @@ from lms.envs.test import (
MEDIA_URL
,
COMPREHENSIVE_THEME_DIRS
,
JWT_AUTH
,
REGISTRATION_EXTRA_FIELDS
,
)
# mongo connection settings
...
...
common/djangoapps/third_party_auth/saml.py
View file @
700e8053
...
...
@@ -7,6 +7,7 @@ import requests
from
django.contrib.sites.models
import
Site
from
django.http
import
Http404
from
django.utils.functional
import
cached_property
from
django_countries
import
countries
from
social_core.backends.saml
import
OID_EDU_PERSON_ENTITLEMENT
,
SAMLAuth
,
SAMLIdentityProvider
from
social_core.exceptions
import
AuthForbidden
...
...
@@ -134,6 +135,77 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
'odata_client_id'
,
)
# Define the relationships between SAPSF record fields and Open edX logistration fields.
default_field_mapping
=
{
'username'
:
'username'
,
'firstName'
:
'first_name'
,
'lastName'
:
'last_name'
,
'defaultFullName'
:
'fullname'
,
'email'
:
'email'
,
'country'
:
'country'
,
'city'
:
'city'
,
}
# Define a simple mapping to relate SAPSF values to Open edX-compatible values for
# any given field. By default, this only contains the Country field, as SAPSF supplies
# a country name, which has to be translated to a country code.
default_value_mapping
=
{
'country'
:
{
name
:
code
for
code
,
name
in
countries
}
}
# Unfortunately, not everything has a 1:1 name mapping between Open edX and SAPSF, so
# we need some overrides. TODO: Fill in necessary mappings
default_value_mapping
.
update
({
'United States'
:
'US'
,
})
def
get_registration_fields
(
self
,
response
):
"""
Get a dictionary mapping registration field names to default values.
"""
field_mapping
=
self
.
field_mappings
registration_fields
=
{
edx_name
:
response
[
'd'
]
.
get
(
odata_name
,
''
)
for
odata_name
,
edx_name
in
field_mapping
.
items
()}
value_mapping
=
self
.
value_mappings
for
field
,
value
in
registration_fields
.
items
():
if
field
in
value_mapping
and
value
in
value_mapping
[
field
]:
registration_fields
[
field
]
=
value_mapping
[
field
][
value
]
return
registration_fields
@property
def
field_mappings
(
self
):
"""
Get a dictionary mapping the field names returned in an SAP SuccessFactors
user entity to the field names with which those values should be used in
the Open edX registration form.
"""
overrides
=
self
.
conf
.
get
(
'sapsf_field_mappings'
,
{})
base
=
self
.
default_field_mapping
.
copy
()
base
.
update
(
overrides
)
return
base
@property
def
value_mappings
(
self
):
"""
Get a dictionary mapping of field names to override objects which each
map values received from SAP SuccessFactors to values expected in the
Open edX platform registration form.
"""
overrides
=
self
.
conf
.
get
(
'sapsf_value_mappings'
,
{})
base
=
self
.
default_value_mapping
.
copy
()
for
field
,
override
in
overrides
.
items
():
if
field
in
base
:
base
[
field
]
.
update
(
override
)
else
:
base
[
field
]
=
override
[
field
]
return
base
@property
def
timeout
(
self
):
"""
The number of seconds OData API requests should wait for a response before failing.
"""
return
self
.
conf
.
get
(
'odata_api_request_timeout'
,
10
)
@property
def
sapsf_idp_url
(
self
):
return
self
.
conf
[
'sapsf_oauth_root_url'
]
+
'idp'
...
...
@@ -187,7 +259,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
'token_url'
:
self
.
sapsf_token_url
,
'private_key'
:
self
.
sapsf_private_key
,
},
timeout
=
10
,
timeout
=
self
.
timeout
,
)
assertion
.
raise_for_status
()
assertion
=
assertion
.
text
...
...
@@ -199,7 +271,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
'grant_type'
:
'urn:ietf:params:oauth:grant-type:saml2-bearer'
,
'assertion'
:
assertion
,
},
timeout
=
10
,
timeout
=
self
.
timeout
,
)
token
.
raise_for_status
()
token
=
token
.
json
()[
'access_token'
]
...
...
@@ -220,12 +292,14 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
username
=
details
[
'username'
]
try
:
client
=
self
.
get_odata_api_client
(
user_id
=
username
)
fields
=
','
.
join
(
self
.
field_mappings
)
response
=
client
.
get
(
'{root_url}User(userId=
\'
{user_id}
\'
)?$select=
username,firstName,lastName,defaultFullName,email
'
.
format
(
'{root_url}User(userId=
\'
{user_id}
\'
)?$select=
{fields}
'
.
format
(
root_url
=
self
.
odata_api_root_url
,
user_id
=
username
user_id
=
username
,
fields
=
fields
,
),
timeout
=
10
,
timeout
=
self
.
timeout
,
)
response
.
raise_for_status
()
response
=
response
.
json
()
...
...
@@ -237,13 +311,7 @@ class SapSuccessFactorsIdentityProvider(EdXSAMLIdentityProvider):
self
.
odata_company_id
,
)
return
details
return
{
'username'
:
response
[
'd'
][
'username'
],
'first_name'
:
response
[
'd'
][
'firstName'
],
'last_name'
:
response
[
'd'
][
'lastName'
],
'fullname'
:
response
[
'd'
][
'defaultFullName'
],
'email'
:
response
[
'd'
][
'email'
],
}
return
self
.
get_registration_fields
(
response
)
def
get_saml_idp_choices
():
...
...
common/djangoapps/third_party_auth/tests/specs/base.py
View file @
700e8053
...
...
@@ -54,7 +54,7 @@ class IntegrationTestMixin(object):
self
.
addCleanup
(
patcher
.
stop
)
# Override this method in a subclass and enable at least one provider.
def
test_register
(
self
):
def
test_register
(
self
,
**
extra_defaults
):
# The user goes to the register page, and sees a button to register with the provider:
provider_register_url
=
self
.
_check_register_page
()
# The user clicks on the Dummy button:
...
...
@@ -76,6 +76,8 @@ class IntegrationTestMixin(object):
self
.
assertEqual
(
form_fields
[
'email'
][
'defaultValue'
],
self
.
USER_EMAIL
)
self
.
assertEqual
(
form_fields
[
'name'
][
'defaultValue'
],
self
.
USER_NAME
)
self
.
assertEqual
(
form_fields
[
'username'
][
'defaultValue'
],
self
.
USER_USERNAME
)
for
field_name
,
value
in
extra_defaults
.
items
():
self
.
assertEqual
(
form_fields
[
field_name
][
'defaultValue'
],
value
)
registration_values
=
{
'email'
:
'email-edited@tpa-test.none'
,
'name'
:
'My Customized Name'
,
...
...
common/djangoapps/third_party_auth/tests/specs/test_testshib.py
View file @
700e8053
...
...
@@ -309,6 +309,7 @@ class SuccessFactorsIntegrationTest(SamlIntegrationTestUtilities, IntegrationTes
'lastName'
:
'Smith'
,
'defaultFullName'
:
'John Smith'
,
'email'
:
'john@smith.com'
,
'country'
:
'Australia'
,
}
})
)
...
...
@@ -331,23 +332,119 @@ class SuccessFactorsIntegrationTest(SamlIntegrationTestUtilities, IntegrationTes
self
.
USER_USERNAME
=
"myself"
super
(
SuccessFactorsIntegrationTest
,
self
)
.
test_register
()
@patch.dict
(
'django.conf.settings.REGISTRATION_EXTRA_FIELDS'
,
country
=
'optional'
)
def
test_register_sapsf_metadata_present
(
self
):
"""
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
API, and ensure that the data it gets that way gets passed to the registration form.
Check that value mappings overrides work in cases where we override a value other than
what we're looking for, and when an empty override is provided (expected behavior is that
existing value maps will be left alone).
"""
expected_country
=
'AU'
provider_settings
=
{
'sapsf_oauth_root_url'
:
'http://successfactors.com/oauth/'
,
'sapsf_private_key'
:
'fake_private_key_here'
,
'odata_api_root_url'
:
'http://api.successfactors.com/odata/v2/'
,
'odata_company_id'
:
'NCC1701D'
,
'odata_client_id'
:
'TatVotSEiCMteSNWtSOnLanCtBGwNhGB'
,
}
self
.
_configure_testshib_provider
(
identity_provider_type
=
'sap_success_factors'
,
metadata_source
=
TESTSHIB_METADATA_URL
,
other_settings
=
json
.
dumps
({
'sapsf_oauth_root_url'
:
'http://successfactors.com/oauth/'
,
'sapsf_private_key'
:
'fake_private_key_here'
,
'odata_api_root_url'
:
'http://api.successfactors.com/odata/v2/'
,
'odata_company_id'
:
'NCC1701D'
,
'odata_client_id'
:
'TatVotSEiCMteSNWtSOnLanCtBGwNhGB'
,
})
other_settings
=
json
.
dumps
(
provider_settings
)
)
super
(
SuccessFactorsIntegrationTest
,
self
)
.
test_register
()
super
(
SuccessFactorsIntegrationTest
,
self
)
.
test_register
(
country
=
expected_country
)
@patch.dict
(
'django.conf.settings.REGISTRATION_EXTRA_FIELDS'
,
country
=
'optional'
)
def
test_register_sapsf_metadata_present_override_relevant_value
(
self
):
"""
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
API, and ensure that the data it gets that way gets passed to the registration form.
Check that value mappings overrides work in cases where we override a value other than
what we're looking for, and when an empty override is provided (expected behavior is that
existing value maps will be left alone).
"""
value_map
=
{
'country'
:
{
'Australia'
:
'NZ'
}}
expected_country
=
'NZ'
provider_settings
=
{
'sapsf_oauth_root_url'
:
'http://successfactors.com/oauth/'
,
'sapsf_private_key'
:
'fake_private_key_here'
,
'odata_api_root_url'
:
'http://api.successfactors.com/odata/v2/'
,
'odata_company_id'
:
'NCC1701D'
,
'odata_client_id'
:
'TatVotSEiCMteSNWtSOnLanCtBGwNhGB'
,
}
if
value_map
:
provider_settings
[
'sapsf_value_mappings'
]
=
value_map
self
.
_configure_testshib_provider
(
identity_provider_type
=
'sap_success_factors'
,
metadata_source
=
TESTSHIB_METADATA_URL
,
other_settings
=
json
.
dumps
(
provider_settings
)
)
super
(
SuccessFactorsIntegrationTest
,
self
)
.
test_register
(
country
=
expected_country
)
@patch.dict
(
'django.conf.settings.REGISTRATION_EXTRA_FIELDS'
,
country
=
'optional'
)
def
test_register_sapsf_metadata_present_override_other_value
(
self
):
"""
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
API, and ensure that the data it gets that way gets passed to the registration form.
Check that value mappings overrides work in cases where we override a value other than
what we're looking for, and when an empty override is provided (expected behavior is that
existing value maps will be left alone).
"""
value_map
=
{
'country'
:
{
'United States'
:
'blahfake'
}}
expected_country
=
'AU'
provider_settings
=
{
'sapsf_oauth_root_url'
:
'http://successfactors.com/oauth/'
,
'sapsf_private_key'
:
'fake_private_key_here'
,
'odata_api_root_url'
:
'http://api.successfactors.com/odata/v2/'
,
'odata_company_id'
:
'NCC1701D'
,
'odata_client_id'
:
'TatVotSEiCMteSNWtSOnLanCtBGwNhGB'
,
}
if
value_map
:
provider_settings
[
'sapsf_value_mappings'
]
=
value_map
self
.
_configure_testshib_provider
(
identity_provider_type
=
'sap_success_factors'
,
metadata_source
=
TESTSHIB_METADATA_URL
,
other_settings
=
json
.
dumps
(
provider_settings
)
)
super
(
SuccessFactorsIntegrationTest
,
self
)
.
test_register
(
country
=
expected_country
)
@patch.dict
(
'django.conf.settings.REGISTRATION_EXTRA_FIELDS'
,
country
=
'optional'
)
def
test_register_sapsf_metadata_present_empty_value_override
(
self
):
"""
Configure the provider such that it can talk to a mocked-out version of the SAP SuccessFactors
API, and ensure that the data it gets that way gets passed to the registration form.
Check that value mappings overrides work in cases where we override a value other than
what we're looking for, and when an empty override is provided (expected behavior is that
existing value maps will be left alone).
"""
value_map
=
{
'country'
:
{}}
expected_country
=
'AU'
provider_settings
=
{
'sapsf_oauth_root_url'
:
'http://successfactors.com/oauth/'
,
'sapsf_private_key'
:
'fake_private_key_here'
,
'odata_api_root_url'
:
'http://api.successfactors.com/odata/v2/'
,
'odata_company_id'
:
'NCC1701D'
,
'odata_client_id'
:
'TatVotSEiCMteSNWtSOnLanCtBGwNhGB'
,
}
if
value_map
:
provider_settings
[
'sapsf_value_mappings'
]
=
value_map
self
.
_configure_testshib_provider
(
identity_provider_type
=
'sap_success_factors'
,
metadata_source
=
TESTSHIB_METADATA_URL
,
other_settings
=
json
.
dumps
(
provider_settings
)
)
super
(
SuccessFactorsIntegrationTest
,
self
)
.
test_register
(
country
=
expected_country
)
def
test_register_http_failure
(
self
):
"""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment