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
7f2c956e
Commit
7f2c956e
authored
Nov 06, 2015
by
Fred Smith
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #560 from edx-solutions/rc/2015-11-05
Rc/2015 11 05
parents
15265445
2b0976f5
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
156 additions
and
313 deletions
+156
-313
common/djangoapps/student/views.py
+9
-22
common/djangoapps/third_party_auth/migrations/0005_auto__del_field_samlproviderconfig_autoprovision_account__del_field_oa.py
+130
-0
common/djangoapps/third_party_auth/models.py
+0
-9
common/djangoapps/third_party_auth/pipeline.py
+4
-52
common/djangoapps/third_party_auth/settings.py
+2
-1
common/djangoapps/third_party_auth/strategy.py
+0
-45
common/djangoapps/third_party_auth/tests/specs/test_testshib.py
+5
-55
common/djangoapps/third_party_auth/tests/test_pipeline.py
+0
-37
common/djangoapps/third_party_auth/tests/test_strategy.py
+0
-74
lms/envs/aws.py
+0
-8
lms/envs/test.py
+0
-3
requirements/edx/custom.txt
+4
-5
requirements/edx/github.txt
+2
-2
No files found.
common/djangoapps/student/views.py
View file @
7f2c956e
...
...
@@ -1357,22 +1357,11 @@ def change_setting(request):
class
AccountValidationError
(
Exception
):
""" Exception thrown if some account validation error happened """
def
__init__
(
self
,
message
,
field
):
super
(
AccountValidationError
,
self
)
.
__init__
(
message
)
self
.
field
=
field
class
AccountUserNameValidationError
(
AccountValidationError
):
""" Exception thrown if attempted to create account with username already taken """
pass
class
AccountEmailAlreadyExistsValidationError
(
AccountValidationError
):
""" Exception thrown if attempted to create account with email already used by other account """
pass
@receiver
(
post_save
,
sender
=
User
)
def
user_signup_handler
(
sender
,
**
kwargs
):
# pylint: disable=unused-argument
"""
...
...
@@ -1414,12 +1403,12 @@ def _do_create_account(form):
except
IntegrityError
:
# Figure out the cause of the integrity error
if
len
(
User
.
objects
.
filter
(
username
=
user
.
username
))
>
0
:
raise
Account
UserName
ValidationError
(
raise
AccountValidationError
(
_
(
"An account with the Public Username '{username}' already exists."
)
.
format
(
username
=
user
.
username
),
field
=
"username"
)
elif
len
(
User
.
objects
.
filter
(
email
=
user
.
email
))
>
0
:
raise
Account
EmailAlreadyExists
ValidationError
(
raise
AccountValidationError
(
_
(
"An account with the Email '{email}' already exists."
)
.
format
(
email
=
user
.
email
),
field
=
"email"
)
...
...
@@ -1454,7 +1443,7 @@ def _do_create_account(form):
# pylint: disable=too-many-statements
def
create_account_with_params
(
request
,
params
,
skip_email
=
False
):
def
create_account_with_params
(
request
,
params
):
"""
Given a request and a dict of parameters (which may or may not have come
from the request), create an account for the requesting user, including
...
...
@@ -1548,8 +1537,7 @@ def create_account_with_params(request, params, skip_email=False):
(
user
,
profile
,
registration
)
=
_do_create_account
(
form
)
# next, link the account with social auth, if provided via the API.
# (If the user is using the normal register page or account is automatically provisioned,
# the social auth pipeline does the linking, not this code)
# (If the user is using the normal register page, the social auth pipeline does the linking, not this code)
if
should_link_with_social_auth
:
backend_name
=
params
[
'provider'
]
request
.
social_strategy
=
social_utils
.
load_strategy
(
request
)
...
...
@@ -1633,12 +1621,11 @@ def create_account_with_params(request, params, skip_email=False):
# the other for *new* systems. we need to be careful about
# changing settings on a running system to make sure no users are
# left in an inconsistent state (or doing a migration if they are).
send_email
=
not
(
skip_email
or
settings
.
FEATURES
.
get
(
'SKIP_EMAIL_VALIDATION'
,
False
)
or
settings
.
FEATURES
.
get
(
'AUTOMATIC_AUTH_FOR_TESTING'
,
False
)
or
(
do_external_auth
and
settings
.
FEATURES
.
get
(
'BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'
))
or
(
send_email
=
(
not
settings
.
FEATURES
.
get
(
'SKIP_EMAIL_VALIDATION'
,
None
)
and
not
settings
.
FEATURES
.
get
(
'AUTOMATIC_AUTH_FOR_TESTING'
)
and
not
(
do_external_auth
and
settings
.
FEATURES
.
get
(
'BYPASS_ACTIVATION_EMAIL_FOR_EXTAUTH'
))
and
not
(
third_party_provider
and
third_party_provider
.
skip_email_verification
and
user
.
email
==
running_pipeline
[
'kwargs'
]
.
get
(
'details'
,
{})
.
get
(
'email'
)
)
...
...
common/djangoapps/third_party_auth/migrations/0005_auto__del_field_samlproviderconfig_autoprovision_account__del_field_oa.py
0 → 100644
View file @
7f2c956e
# -*- coding: utf-8 -*-
from
south.utils
import
datetime_utils
as
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Deleting field 'SAMLProviderConfig.autoprovision_account'
db
.
delete_column
(
'third_party_auth_samlproviderconfig'
,
'autoprovision_account'
)
# Deleting field 'OAuth2ProviderConfig.autoprovision_account'
db
.
delete_column
(
'third_party_auth_oauth2providerconfig'
,
'autoprovision_account'
)
def
backwards
(
self
,
orm
):
# Adding field 'SAMLProviderConfig.autoprovision_account'
db
.
add_column
(
'third_party_auth_samlproviderconfig'
,
'autoprovision_account'
,
self
.
gf
(
'django.db.models.fields.BooleanField'
)(
default
=
False
),
keep_default
=
False
)
# Adding field 'OAuth2ProviderConfig.autoprovision_account'
db
.
add_column
(
'third_party_auth_oauth2providerconfig'
,
'autoprovision_account'
,
self
.
gf
(
'django.db.models.fields.BooleanField'
)(
default
=
False
),
keep_default
=
False
)
models
=
{
'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"('content_type__app_label', 'content_type__model', 'codename')"
,
'unique_together'
:
"(('content_type', 'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Group']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
'third_party_auth.oauth2providerconfig'
:
{
'Meta'
:
{
'object_name'
:
'OAuth2ProviderConfig'
},
'backend_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
,
'db_index'
:
'True'
}),
'change_date'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'changed_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
,
'null'
:
'True'
,
'on_delete'
:
'models.PROTECT'
}),
'enabled'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'icon_class'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'fa-sign-in'"
,
'max_length'
:
'50'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'key'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
}),
'other_settings'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'secondary'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'secret'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'skip_email_verification'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'skip_registration_form'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
})
},
'third_party_auth.samlconfiguration'
:
{
'Meta'
:
{
'object_name'
:
'SAMLConfiguration'
},
'change_date'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'changed_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
,
'null'
:
'True'
,
'on_delete'
:
'models.PROTECT'
}),
'enabled'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'entity_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'http://saml.example.com'"
,
'max_length'
:
'255'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'org_info_str'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
'
\'
{"en-US": {"url": "http://www.example.com", "displayname": "Example Inc.", "name": "example"}}
\'
'
}),
'other_config_str'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
'
\'
{
\\
n"SECURITY_CONFIG": {"metadataCacheDuration": 604800, "signMetadata": false}
\\
n}
\'
'
}),
'private_key'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'public_key'
:
(
'django.db.models.fields.TextField'
,
[],
{})
},
'third_party_auth.samlproviderconfig'
:
{
'Meta'
:
{
'object_name'
:
'SAMLProviderConfig'
},
'attr_email'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'blank'
:
'True'
}),
'attr_first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'blank'
:
'True'
}),
'attr_full_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'blank'
:
'True'
}),
'attr_last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'blank'
:
'True'
}),
'attr_user_permanent_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'blank'
:
'True'
}),
'attr_username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'blank'
:
'True'
}),
'backend_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'tpa-saml'"
,
'max_length'
:
'50'
}),
'change_date'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'changed_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
,
'null'
:
'True'
,
'on_delete'
:
'models.PROTECT'
}),
'enabled'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'entity_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'icon_class'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'fa-sign-in'"
,
'max_length'
:
'50'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'idp_slug'
:
(
'django.db.models.fields.SlugField'
,
[],
{
'max_length'
:
'30'
}),
'metadata_source'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
}),
'other_settings'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'secondary'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'skip_email_verification'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'skip_registration_form'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
})
},
'third_party_auth.samlproviderdata'
:
{
'Meta'
:
{
'ordering'
:
"('-fetched_at',)"
,
'object_name'
:
'SAMLProviderData'
},
'entity_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'expires_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'db_index'
:
'True'
}),
'fetched_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'public_key'
:
(
'django.db.models.fields.TextField'
,
[],
{}),
'sso_url'
:
(
'django.db.models.fields.URLField'
,
[],
{
'max_length'
:
'200'
})
}
}
complete_apps
=
[
'third_party_auth'
]
\ No newline at end of file
common/djangoapps/third_party_auth/models.py
View file @
7f2c956e
...
...
@@ -93,15 +93,6 @@ class ProviderConfig(ConfigurationModel):
"email, and their account will be activated immediately upon registration."
),
)
autoprovision_account
=
models
.
BooleanField
(
default
=
False
,
help_text
=
_
(
"If this option is selected, users will not be required to confirm their details even if "
"some required data is missing or fails validation (e.g. duplicate email). Instead, fake or generated "
"values will be used. This setting forces skipping email verification, so 'Skip email verification' "
"setting have no effect."
)
)
prefix
=
None
# used for provider_id. Set to a string value in subclass
backend_name
=
None
# Set to a field or fixed value in subclass
...
...
common/djangoapps/third_party_auth/pipeline.py
View file @
7f2c956e
...
...
@@ -77,7 +77,6 @@ from social.apps.django_app.default import models
from
social.exceptions
import
AuthException
from
social.pipeline
import
partial
from
social.pipeline.social_auth
import
associate_by_email
from
social.pipeline.user
import
create_user
as
social_create_user
import
student
...
...
@@ -85,7 +84,6 @@ from logging import getLogger
from
.
import
provider
# These are the query string params you can pass
# to the URL that starts the authentication process.
#
...
...
@@ -194,20 +192,6 @@ class NotActivatedException(AuthException):
)
class
EmailAlreadyInUseException
(
AuthException
):
""" Raised when new user account is created with an email already used by another account """
def
__init__
(
self
,
backend
,
email
):
self
.
email
=
email
super
(
EmailAlreadyInUseException
,
self
)
.
__init__
(
backend
,
email
)
def
__str__
(
self
):
return
(
_
(
'Email {email_address} is already used in our system. To link your accounts, '
'sign in now using your {platform_name} password.'
)
.
format
(
email_address
=
self
.
email
,
platform_name
=
settings
.
PLATFORM_NAME
)
)
class
ProviderUserState
(
object
):
"""Object representing the provider state (attached or not) for a user.
...
...
@@ -554,34 +538,18 @@ def ensure_user_information(strategy, auth_entry, backend=None, user=None, socia
"""Redirects to the registration page."""
return
redirect
(
AUTH_DISPATCH_URLS
[
AUTH_ENTRY_REGISTER
])
def
get_provider
():
"""
Gets third-party provider for request
"""
return
provider
.
Registry
.
get_from_pipeline
({
'backend'
:
backend
.
name
,
'kwargs'
:
kwargs
})
def
should_autoprovision_account
():
""" For some third party providers we trust the provider so much that we automatically provision the account """
current_provider
=
get_provider
()
return
current_provider
and
current_provider
.
autoprovision_account
def
autosubmit_registration_form
():
""" For some third party providers, we auto-submit registration forms """
current_provider
=
get_provider
()
def
should_force_account_creation
():
""" For some third party providers, we auto-create user accounts """
current_provider
=
provider
.
Registry
.
get_from_pipeline
({
'backend'
:
backend
.
name
,
'kwargs'
:
kwargs
})
return
current_provider
and
current_provider
.
skip_email_verification
if
not
user
:
if
should_autoprovision_account
():
# User has authenticated with the third party provider and provider is configured
# to automatically provision edX account, which is done via strategy.create_user in next pipeline step
return
{
'autoprovision'
:
True
}
if
auth_entry
in
[
AUTH_ENTRY_LOGIN_API
,
AUTH_ENTRY_REGISTER_API
]:
return
HttpResponseBadRequest
()
elif
auth_entry
in
[
AUTH_ENTRY_LOGIN
,
AUTH_ENTRY_LOGIN_2
]:
# User has authenticated with the third party provider but we don't know which edX
# account corresponds to them yet, if any.
if
autosubmit_registration_form
():
if
should_force_account_creation
():
return
dispatch_to_register
()
return
dispatch_to_login
()
elif
auth_entry
in
[
AUTH_ENTRY_REGISTER
,
AUTH_ENTRY_REGISTER_2
]:
...
...
@@ -625,22 +593,6 @@ def ensure_user_information(strategy, auth_entry, backend=None, user=None, socia
@partial.partial
def
create_user
(
strategy
,
details
,
user
=
None
,
*
args
,
**
kwargs
):
"""
Substitution method for stock social create_user that catches email validation error and redirects to login
"""
from
student.views
import
AccountEmailAlreadyExistsValidationError
try
:
return
social_create_user
(
strategy
,
details
,
user
,
*
args
,
**
kwargs
)
except
AccountEmailAlreadyExistsValidationError
as
exc
:
logger
.
exception
(
exc
.
message
)
# We're raising an exception that inherits from AuthException. Such exceptions are properly handled
# by social auth pipeline: their string representation (see __str__ method) is displayed to user on the page
# we're redirecting to.
raise
EmailAlreadyInUseException
(
exc
.
message
,
details
[
'email'
])
@partial.partial
def
set_logged_in_cookies
(
backend
=
None
,
user
=
None
,
strategy
=
None
,
auth_entry
=
None
,
*
args
,
**
kwargs
):
"""This pipeline step sets the "logged in" cookie for authenticated users.
...
...
common/djangoapps/third_party_auth/settings.py
View file @
7f2c956e
...
...
@@ -9,6 +9,7 @@ If true, it:
a) loads this module.
b) calls apply_settings(), passing in the Django settings
"""
_FIELDS_STORED_IN_SESSION
=
[
'auth_entry'
,
'next'
]
_MIDDLEWARE_CLASSES
=
(
'third_party_auth.middleware.ExceptionMiddleware'
,
...
...
@@ -52,7 +53,7 @@ def apply_settings(django_settings):
'social.pipeline.user.get_username'
,
'third_party_auth.pipeline.set_pipeline_timeout'
,
'third_party_auth.pipeline.ensure_user_information'
,
'
third_party_auth.pipeline
.create_user'
,
'
social.pipeline.user
.create_user'
,
'social.pipeline.social_auth.associate_user'
,
'social.pipeline.social_auth.load_extra_data'
,
'social.pipeline.user.user_details'
,
...
...
common/djangoapps/third_party_auth/strategy.py
View file @
7f2c956e
...
...
@@ -2,16 +2,12 @@
A custom Strategy for python-social-auth that allows us to fetch configuration from
ConfigurationModels rather than django.settings
"""
import
logging
from
.models
import
OAuth2ProviderConfig
from
.pipeline
import
AUTH_ENTRY_CUSTOM
from
social.backends.oauth
import
BaseOAuth2
from
social.strategies.django_strategy
import
DjangoStrategy
log
=
logging
.
getLogger
(
__name__
)
class
ConfigurationModelStrategy
(
DjangoStrategy
):
"""
A DjangoStrategy customized to load settings from ConfigurationModels
...
...
@@ -47,47 +43,6 @@ class ConfigurationModelStrategy(DjangoStrategy):
# It's probably a global Django setting like 'FIELDS_STORED_IN_SESSION':
return
super
(
ConfigurationModelStrategy
,
self
)
.
setting
(
name
,
default
,
backend
)
def
_ensure_passes_length_check
(
self
,
user_data
,
key
,
fallback
,
min_length
=
2
):
"""
Ensures that value we get from user_data is meets length requirements. IF it is shorter than required, fallback
is used
"""
assert
len
(
fallback
)
>=
min_length
value
=
user_data
.
get
(
key
)
if
value
and
len
(
value
)
>=
min_length
:
return
value
return
fallback
def
create_user
(
self
,
*
args
,
**
kwargs
):
"""
# Creates user using information provided by pipeline. This method is called in create_user pipeline step.
# Unless the workflow is changed, create_user immediately terminates if the user already found/
# So far, user is either created in ensure_user_information via registration form or account needs to be
# autoprovisioned. So, this method is only called when autoprovisioning account.
"""
from
student.views
import
create_account_with_params
from
.pipeline
import
make_random_password
user_fields
=
dict
(
kwargs
)
# needs to be >2 chars to pass validation
name
=
self
.
_ensure_passes_length_check
(
user_fields
,
'fullname'
,
self
.
setting
(
"THIRD_PARTY_AUTH_FALLBACK_FULL_NAME"
)
)
password
=
self
.
_ensure_passes_length_check
(
user_fields
,
'password'
,
make_random_password
())
user_fields
[
'name'
]
=
name
user_fields
[
'password'
]
=
password
user_fields
[
'honor_code'
]
=
True
user_fields
[
'terms_of_service'
]
=
True
if
not
user_fields
.
get
(
'email'
):
user_fields
[
'email'
]
=
"{username}@{domain}"
.
format
(
username
=
user_fields
[
'username'
],
domain
=
self
.
setting
(
"FAKE_EMAIL_DOMAIN"
)
)
# when autoprovisioning we need to skip email activation, hence skip_email is True
return
create_account_with_params
(
self
.
request
,
user_fields
,
skip_email
=
True
)
def
request_host
(
self
):
"""
Host in use for this request
...
...
common/djangoapps/third_party_auth/tests/specs/test_testshib.py
View file @
7f2c956e
...
...
@@ -111,7 +111,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
continue_response
=
self
.
client
.
get
(
TPA_TESTSHIB_COMPLETE_URL
)
# And we should be redirected to the dashboard:
self
.
assertEqual
(
continue_response
.
status_code
,
302
)
self
.
assertEqual
(
continue_response
[
'Location'
],
self
.
url_prefix
+
reverse
(
'dashboard'
)
)
self
.
assertEqual
(
continue_response
[
'Location'
],
self
.
url_prefix
+
self
.
dashboard_page_url
)
# Now check that we can login again:
self
.
client
.
logout
()
...
...
@@ -157,32 +157,8 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
self
.
client
.
logout
()
self
.
_test_return_login
()
def
test_autoprovision_from_login
(
self
):
self
.
_configure_testshib_provider
(
autoprovision_account
=
True
)
self
.
_freeze_time
(
timestamp
=
1434326820
)
# This is the time when the saved request/response was recorded.
# check that we don't have a user we're autoprovisioning account for
self
.
_assert_user_does_not_exist
(
'myself'
)
# The user goes to the register page, and sees a button to register with TestShib:
self
.
_check_login_page
()
self
.
_test_autoprovision
(
TPA_TESTSHIB_LOGIN_URL
)
def
test_autoprovision_from_register
(
self
):
self
.
_configure_testshib_provider
(
autoprovision_account
=
True
)
self
.
_freeze_time
(
timestamp
=
1434326820
)
# This is the time when the saved request/response was recorded.
# check that we don't have a user we're autoprovisioning account for
self
.
_assert_user_does_not_exist
(
'myself'
)
# The user goes to the register page, and sees a button to register with TestShib:
self
.
_check_register_page
()
self
.
_test_autoprovision
(
TPA_TESTSHIB_REGISTER_URL
)
def
test_custom_form_does_not_link_by_email
(
self
):
self
.
_configure_testshib_provider
(
autoprovision_account
=
False
)
self
.
_configure_testshib_provider
()
self
.
_freeze_time
(
timestamp
=
1434326820
)
# This is the time when the saved request/response was recorded.
email
=
'myself@testshib.org'
...
...
@@ -201,7 +177,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
self
.
assertEqual
(
testshib_response
[
'Location'
],
self
.
url_prefix
+
'/auth/custom_auth_entry'
)
def
test_custom_form_links_by_email
(
self
):
self
.
_configure_testshib_provider
(
autoprovision_account
=
False
)
self
.
_configure_testshib_provider
()
self
.
_freeze_time
(
timestamp
=
1434326820
)
# This is the time when the saved request/response was recorded.
email
=
'myself@testshib.org'
...
...
@@ -229,35 +205,10 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
self
.
client
.
logout
()
self
.
_test_return_login
()
def
_test_autoprovision
(
self
,
entry_point
):
""" Actual autoprovision code """
# The user clicks on the TestShib button:
try_entry_response
=
self
.
client
.
get
(
entry_point
)
# The user should be redirected to TestShib:
self
.
assertEqual
(
try_entry_response
.
status_code
,
302
)
self
.
assertTrue
(
try_entry_response
[
'Location'
]
.
startswith
(
TESTSHIB_SSO_URL
))
# Now the user will authenticate with the SAML provider
self
.
_fake_testshib_login_and_return
()
# Then there's one more redirect to set logged_in cookie
continue_response
=
self
.
client
.
get
(
TPA_TESTSHIB_COMPLETE_URL
)
# We should be redirected to the dashboard screen since profile should be created and logged in
self
.
assertEqual
(
continue_response
.
status_code
,
302
)
self
.
assertEqual
(
continue_response
[
'Location'
],
self
.
url_prefix
+
self
.
dashboard_page_url
)
# assert account is created and activated
self
.
_assert_account_created
(
username
=
'myself'
,
email
=
'myself@testshib.org'
,
full_name
=
'Me Myself And I'
)
# Now check that we can login again:
self
.
client
.
logout
()
self
.
_test_return_login
()
def
_test_return_login
(
self
):
""" Test logging in to an account that is already linked. """
# Make sure we're not logged in:
dashboard_response
=
self
.
client
.
get
(
reverse
(
'dashboard'
)
)
dashboard_response
=
self
.
client
.
get
(
self
.
dashboard_page_url
)
self
.
assertEqual
(
dashboard_response
.
status_code
,
302
)
# The user goes to the login page, and sees a button to login with TestShib:
self
.
_check_login_page
()
...
...
@@ -276,7 +227,7 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
self
.
assertEqual
(
login_response
.
status_code
,
302
)
self
.
assertEqual
(
login_response
[
'Location'
],
self
.
url_prefix
+
self
.
dashboard_page_url
)
# Now we are logged in:
dashboard_response
=
self
.
client
.
get
(
reverse
(
'dashboard'
)
)
dashboard_response
=
self
.
client
.
get
(
self
.
dashboard_page_url
)
self
.
assertEqual
(
dashboard_response
.
status_code
,
200
)
def
_freeze_time
(
self
,
timestamp
):
...
...
@@ -311,7 +262,6 @@ class TestShibIntegrationTest(testutil.SAMLTestCase):
kwargs
.
setdefault
(
'metadata_source'
,
TESTSHIB_METADATA_URL
)
kwargs
.
setdefault
(
'icon_class'
,
'fa-university'
)
kwargs
.
setdefault
(
'attr_email'
,
'urn:oid:1.3.6.1.4.1.5923.1.1.1.6'
)
# eduPersonPrincipalName
kwargs
.
setdefault
(
'autoprovision_account'
,
False
)
self
.
configure_saml_provider
(
**
kwargs
)
if
fetch_metadata
:
...
...
common/djangoapps/third_party_auth/tests/test_pipeline.py
View file @
7f2c956e
"""Unit tests for third_party_auth/pipeline.py."""
import
random
import
mock
from
student.views
import
AccountEmailAlreadyExistsValidationError
from
third_party_auth
import
pipeline
,
provider
from
third_party_auth.tests
import
testutil
import
unittest
...
...
@@ -45,38 +43,3 @@ class ProviderUserStateTestCase(testutil.TestCase):
google_provider
=
self
.
configure_google_provider
(
enabled
=
True
)
state
=
pipeline
.
ProviderUserState
(
google_provider
,
object
(),
None
)
self
.
assertEqual
(
google_provider
.
provider_id
+
'_unlink_form'
,
state
.
get_unlink_form_name
())
@unittest.skipUnless
(
testutil
.
AUTH_FEATURE_ENABLED
,
'third_party_auth not enabled'
)
class
TestCreateUser
(
testutil
.
TestCase
):
"""
Tests for custom create_user step
"""
def
_raise_email_in_use_exception
(
self
,
*
unused_args
,
**
unused_kwargs
):
""" Helper to raise AccountEmailAlreadyExistsValidationError """
raise
AccountEmailAlreadyExistsValidationError
(
mock
.
Mock
(),
mock
.
Mock
())
def
test_create_user_normal_scenario
(
self
):
""" Tests happy path - user is created and results are returned intact """
retval
=
mock
.
Mock
()
with
mock
.
patch
(
"third_party_auth.pipeline.social_create_user"
)
as
patched_social_create_user
:
patched_social_create_user
.
return_value
=
retval
strategy
,
details
,
user
,
idx
=
mock
.
Mock
(),
{
'email'
:
'qwe@asd.com'
},
mock
.
Mock
(),
1
# pylint: disable=redundant-keyword-arg
result
=
pipeline
.
create_user
(
strategy
,
idx
,
details
=
details
,
user
=
user
)
self
.
assertEqual
(
result
,
retval
)
def
test_create_user_exception_scenario
(
self
):
"""
Tests sad path - expected exception is thrown, captured and transformed into AuthException subclass instance
"""
with
mock
.
patch
(
"third_party_auth.pipeline.social_create_user"
)
as
patched_social_create_user
:
patched_social_create_user
.
side_effect
=
self
.
_raise_email_in_use_exception
strategy
,
details
,
user
=
mock
.
Mock
(),
{
'email'
:
'qwe@asd.com'
},
mock
.
Mock
()
with
self
.
assertRaises
(
pipeline
.
EmailAlreadyInUseException
):
# pylint: disable=redundant-keyword-arg
pipeline
.
create_user
(
strategy
,
1
,
details
=
details
,
user
=
user
)
common/djangoapps/third_party_auth/tests/test_strategy.py
View file @
7f2c956e
...
...
@@ -24,80 +24,6 @@ class TestStrategy(TestCase):
args
,
unused_kwargs
=
patched_create_account
.
call_args
return
args
def
test_create_user_sets_tos_and_honor_code
(
self
,
patched_create_account
):
self
.
strategy
.
create_user
(
username
=
'myself'
)
self
.
assertTrue
(
patched_create_account
.
called
)
request
,
user_data
=
self
.
_get_last_call_args
(
patched_create_account
)
self
.
assertEqual
(
request
,
self
.
request_mock
)
self
.
assertTrue
(
user_data
[
'terms_of_service'
])
self
.
assertTrue
(
user_data
[
'honor_code'
])
@ddt.data
(
(
None
,
'Fallback Name'
,
'Fallback Name'
),
(
'q'
,
'Other Name'
,
'Other Name'
),
(
'q2'
,
'Other Name'
,
'q2'
),
(
'qwe'
,
'Other Name'
,
'qwe'
),
(
'user1'
,
'Fallback Name'
,
'user1'
)
)
@ddt.unpack
def
test_create_user_sets_name
(
self
,
full_name
,
fallback_name
,
expected_name
,
patched_create_account
):
with
mock
.
patch
.
object
(
self
.
strategy
,
'setting'
,
mock
.
Mock
())
as
patched_setting
:
patched_setting
.
return_value
=
fallback_name
self
.
strategy
.
create_user
(
username
=
'myself'
,
fullname
=
full_name
)
# it is actually always called, but this assertion is relaxed to allow not actually going to settings
# if there's no point in that
if
expected_name
==
fallback_name
:
self
.
assertIn
(
mock
.
call
(
"THIRD_PARTY_AUTH_FALLBACK_FULL_NAME"
),
patched_setting
.
mock_calls
)
_
,
user_data
=
self
.
_get_last_call_args
(
patched_create_account
)
self
.
assertEqual
(
user_data
[
'name'
],
expected_name
)
def
test_sets_password_if_missing
(
self
,
patched_create_account
):
self
.
strategy
.
create_user
(
username
=
'myself'
,
fullname
=
'myself'
)
_
,
user_data
=
self
.
_get_last_call_args
(
patched_create_account
)
self
.
assertIn
(
'password'
,
user_data
)
@ddt.data
(
(
None
,
False
),
(
'q'
,
False
),
(
'12'
,
False
),
(
'456'
,
True
),
(
'$up3r_$e(ur3_p/|$$w0rd'
,
True
),
)
@ddt.unpack
def
test_passes_password_if_specified
(
self
,
password
,
should_match
,
patched_create_account
):
self
.
strategy
.
create_user
(
username
=
'myself'
,
fullname
=
'myself'
,
password
=
password
)
_
,
user_data
=
self
.
_get_last_call_args
(
patched_create_account
)
self
.
assertIn
(
'password'
,
user_data
)
if
should_match
:
self
.
assertEqual
(
user_data
[
'password'
],
password
)
@ddt.data
(
(
None
,
'fallback_domain.com'
,
'myself@fallback_domain.com'
),
(
''
,
'other_domain.com'
,
'myself@other_domain.com'
),
(
'qwe@asd.com'
,
'fallback_domain.com'
,
'qwe@asd.com'
),
(
'zxc@darpa.gov.mil.edu'
,
'fallback_domain.com'
,
'zxc@darpa.gov.mil.edu'
),
)
@ddt.unpack
def
test_sets_email_if_not_provided
(
self
,
email
,
fallback_domain
,
expected_email
,
patched_create_account
):
with
mock
.
patch
.
object
(
self
.
strategy
,
'setting'
,
mock
.
Mock
())
as
patched_setting
:
patched_setting
.
return_value
=
fallback_domain
# fullname is needed to avoid calling setting twice
self
.
strategy
.
create_user
(
username
=
'myself'
,
fullname
=
'myself'
,
email
=
email
)
# it is actually always called, but this assertion is relaxed to allow not actually going to settings
# if there's no point in that
if
email
!=
expected_email
:
self
.
assertIn
(
mock
.
call
(
"FAKE_EMAIL_DOMAIN"
),
patched_setting
.
mock_calls
)
_
,
user_data
=
self
.
_get_last_call_args
(
patched_create_account
)
self
.
assertEqual
(
user_data
[
'email'
],
expected_email
)
@ddt.data
(
(
True
,
None
,
'host'
,
'host'
),
(
True
,
""
,
'other_host'
,
'other_host'
),
...
...
lms/envs/aws.py
View file @
7f2c956e
...
...
@@ -592,14 +592,6 @@ if FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
SOCIAL_AUTH_RESPECT_X_FORWARDED_HEADERS
=
ENV_TOKENS
.
get
(
'SOCIAL_AUTH_RESPECT_X_FORWARDED_HEADERS'
)
# FAKE EMAIL DOMAIN setting is used to generate an email for an automatically provisioned account in case
# it is not provided by IdP (which should'nt normally be the case for providers with automatic provisioning)
FAKE_EMAIL_DOMAIN
=
ENV_TOKENS
.
get
(
'FAKE_EMAIL_DOMAIN'
,
'fake-email-domain.foo'
)
# This setting is used as a user full name when automatically provisioning an account in case
# IdP provided name is empty, missing or does not pass minimal length check
THIRD_PARTY_AUTH_FALLBACK_FULL_NAME
=
ENV_TOKENS
.
get
(
'THIRD_PARTY_AUTH_FALLBACK_FULL_NAME'
,
"Unknown"
)
# The following can be used to integrate a custom login form with third_party_auth.
# It should be a dict where the key is a word passed via ?auth_entry=, and the value is a
# dict with an arbitrary 'secret_key' and a 'url'.
...
...
lms/envs/test.py
View file @
7f2c956e
...
...
@@ -258,9 +258,6 @@ AUTHENTICATION_BACKENDS = (
'third_party_auth.saml.SAMLAuthBackend'
,
)
+
AUTHENTICATION_BACKENDS
FAKE_EMAIL_DOMAIN
=
'fake-email-domain.foo'
THIRD_PARTY_AUTH_FALLBACK_FULL_NAME
=
"Unknown"
THIRD_PARTY_AUTH_CUSTOM_AUTH_FORMS
=
{
'custom1'
:
{
'secret_key'
:
'opensesame'
,
...
...
requirements/edx/custom.txt
View file @
7f2c956e
# Custom requirements to be customized by individual OpenEdX instances
# When updating a hash of an XBlock that users xblock-utils, please update its version hash in github.txt
-e git+https://github.com/edx/xblock-utils.git@3b58c757f06943072b170654d676e95b9adb37b0#egg=xblock-utils
# When updating a hash of an XBlock that uses xblock-utils, please update its version hash in github.txt.
-e git+https://github.com/edx-solutions/xblock-mentoring.git@bd0b3f413ae7e8274985555adfd7de7af3eca84c#egg=xblock-mentoring
-e git+https://github.com/edx-solutions/xblock-image-explorer.git@21b9bcc4f2c7917463ab18a596161ac6c58c9c4a#egg=xblock-image-explorer
-e git+https://github.com/edx-solutions/xblock-drag-and-drop.git@92ee2055a16899090a073e1df81e35d5293ad767#egg=xblock-drag-and-drop
...
...
@@ -9,9 +8,9 @@
-e git+https://github.com/edx-solutions/xblock-ooyala.git@42f769d422850df81bcbd2dbcc344f86b6a17d8e#egg=xblock-ooyala
-e git+https://github.com/edx-solutions/xblock-group-project.git@6b3393a1a5eb76224ecd3311e870ab8adf4badbf#egg=xblock-group-project
-e git+https://github.com/edx-solutions/xblock-adventure.git@effa22006bb6528bc6d3788787466eb4e74e1161#egg=xblock-adventure
-e git+https://github.com/
mckinseyacademy/xblock-poll.git@ca0e6eb4ef10c128d573c3cec015dcfee7984730
#egg=xblock-poll
-e git+https://github.com/
open-craft/xblock-poll.git@ed7f32a570fb29a18cce57b14f322667a5f33cd5
#egg=xblock-poll
-e git+https://github.com/edx/edx-notifications.git@275b8354593048ecae3e06642985b702b81140cc#egg=edx-notifications
-e git+https://github.com/open-craft/problem-builder.git@
c6e606027155d92ca78e96e6bc23ea86dcc588fc
#egg=problem-builder
-e git+https://github.com/open-craft/xblock-group-project-v2.git@
533a3d70b8ff58af0c4f5e22abc674c60881cea3
#egg=xblock-group-project-v2
-e git+https://github.com/open-craft/problem-builder.git@
ff271b2dcf8098cc3bfed424244c76ea2c53cc96
#egg=problem-builder
-e git+https://github.com/open-craft/xblock-group-project-v2.git@
da8ab1e0e110cb3d2525157958a88f101fd1abdb
#egg=xblock-group-project-v2
-e git+https://github.com/OfficeDev/xblock-officemix.git@86238f5968a08db005717dbddc346808f1ed3716#egg=xblock-officemix
-e git+https://github.com/edx-solutions/xblock.git@80d11e883cb0f4b554e1e566294cb7de383cffed#egg=xblock
requirements/edx/github.txt
View file @
7f2c956e
...
...
@@ -50,8 +50,8 @@ git+https://github.com/edx/ease.git@release-2015-07-14#egg=ease==0.1.3
-e git+https://github.com/edx/edx-search.git@release-2015-07-03#egg=edx-search
-e git+https://github.com/edx/edx-milestones.git@release-2015-06-17#egg=edx-milestones
git+https://github.com/edx/edx-lint.git@ed8c8d2a0267d4d42f43642d193e25f8bd575d9b#egg=edx_lint==0.2.3
# Note for the next rebase:
custom.txt or one of XBlocks installed there might require a newer version of xblock-utils - please check versions
-e git+https://github.com/edx/xblock-utils.git@
588f7fd3ee88847c57cf09d10e81caa6b267ec51
#egg=xblock-utils
# Note for the next rebase:
one of XBlocks in custom.txt might require a newer version of xblock-utils - please check versions.
-e git+https://github.com/edx/xblock-utils.git@
08e5a5a9fc8ab46b627435427fd7e04c20809009
#egg=xblock-utils
-e git+https://github.com/edx-solutions/xblock-google-drive.git@138e6fa0bf3a2013e904a085b9fed77dab7f3f21#egg=xblock-google-drive
-e git+https://github.com/edx/edx-reverification-block.git@a286e89c73e1b788e35ac5b08a54b71a9fa63cfd#egg=edx-reverification-block
git+https://github.com/edx/ecommerce-api-client.git@1.0.0#egg=ecommerce-api-client==1.0.0
...
...
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