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
1ff4671b
Commit
1ff4671b
authored
Jan 30, 2014
by
chrisndodge
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2312 from edx/cdodge/password-complexity-policy
Optional Password Policy enforcement
parents
1c4d852e
0d49305c
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
388 additions
and
1 deletions
+388
-1
CHANGELOG.rst
+2
-0
cms/envs/aws.py
+7
-0
cms/envs/common.py
+11
-0
cms/urls.py
+1
-1
common/djangoapps/student/tests/test_password_policy.py
+239
-0
common/djangoapps/student/views.py
+18
-0
common/djangoapps/util/password_policy_validators.py
+92
-0
lms/envs/aws.py
+7
-0
lms/envs/common.py
+11
-0
No files found.
CHANGELOG.rst
View file @
1ff4671b
...
...
@@ -333,6 +333,8 @@ assessors to edit the original submitter's work.
LMS: Fixed a bug that caused links from forum user profile pages to
threads to lead to 404s if the course id contained a '-' character.
Studio/LMS: Add password policy enforcement to new account creation
Studio/LMS: Added ability to set due date formatting through Studio's Advanced
Settings. The key is due_date_display_format, and the value should be a format
supported by Python's strftime function.
...
...
cms/envs/aws.py
View file @
1ff4671b
...
...
@@ -238,3 +238,10 @@ if len(MICROSITE_CONFIGURATION.keys()) > 0:
VIRTUAL_UNIVERSITIES
,
microsites_root
=
path
(
MICROSITE_ROOT_DIR
)
)
#### PASSWORD POLICY SETTINGS #####
PASSWORD_MIN_LENGTH
=
ENV_TOKENS
.
get
(
"PASSWORD_MIN_LENGTH"
)
PASSWORD_MAX_LENGTH
=
ENV_TOKENS
.
get
(
"PASSWORD_MAX_LENGTH"
)
PASSWORD_COMPLEXITY
=
ENV_TOKENS
.
get
(
"PASSWORD_COMPLEXITY"
,
{})
PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD
=
ENV_TOKENS
.
get
(
"PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD"
)
PASSWORD_DICTIONARY
=
ENV_TOKENS
.
get
(
"PASSWORD_DICTIONARY"
,
[])
cms/envs/common.py
View file @
1ff4671b
...
...
@@ -63,6 +63,9 @@ FEATURES = {
# edX has explicitly added them to the course creator group.
'ENABLE_CREATOR_GROUP'
:
False
,
# whether to use password policy enforcement or not
'ENFORCE_PASSWORD_POLICY'
:
False
,
# If set to True, Studio won't restrict the set of advanced components
# to just those pre-approved by edX
'ALLOW_ALL_ADVANCED_COMPONENTS'
:
False
,
...
...
@@ -477,6 +480,14 @@ TRACKING_BACKENDS = {
}
}
#### PASSWORD POLICY SETTINGS #####
PASSWORD_MIN_LENGTH
=
None
PASSWORD_MAX_LENGTH
=
None
PASSWORD_COMPLEXITY
=
{}
PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD
=
None
PASSWORD_DICTIONARY
=
[]
# We're already logging events, and we don't want to capture user
# names/passwords. Heartbeat events are likely not interesting.
TRACKING_IGNORE_URL_PATTERNS
=
[
r'^/event'
,
r'^/login'
,
r'^/heartbeat'
]
...
...
cms/urls.py
View file @
1ff4671b
...
...
@@ -42,7 +42,7 @@ urlpatterns = patterns('', # nopep8
urlpatterns
+=
patterns
(
''
,
url
(
r'^create_account$'
,
'student.views.create_account'
),
url
(
r'^create_account$'
,
'student.views.create_account'
,
name
=
'create_account'
),
url
(
r'^activate/(?P<key>[^/]*)$'
,
'student.views.activate_account'
,
name
=
'activate'
),
# ajax view that actually does the work
...
...
common/djangoapps/student/tests/test_password_policy.py
0 → 100644
View file @
1ff4671b
# -*- coding: utf-8 -*-
"""
This test file will verify proper password policy enforcement, which is an option feature
"""
import
json
import
uuid
from
django.test
import
TestCase
from
django.core.urlresolvers
import
reverse
from
mock
import
patch
from
django.test.utils
import
override_settings
@patch.dict
(
"django.conf.settings.FEATURES"
,
{
'ENFORCE_PASSWORD_POLICY'
:
True
})
class
TestPasswordPolicy
(
TestCase
):
"""
Go through some password policy tests to make sure things are properly working
"""
def
setUp
(
self
):
super
(
TestPasswordPolicy
,
self
)
.
setUp
()
self
.
url
=
reverse
(
'create_account'
)
self
.
url_params
=
{
'username'
:
'foo_bar'
+
uuid
.
uuid4
()
.
hex
,
'email'
:
'foo'
+
uuid
.
uuid4
()
.
hex
+
'@bar.com'
,
'name'
:
'username'
,
'terms_of_service'
:
'true'
,
'honor_code'
:
'true'
,
}
@override_settings
(
PASSWORD_MIN_LENGTH
=
6
)
def
test_password_length_too_short
(
self
):
self
.
url_params
[
'password'
]
=
'aaa'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
400
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
obj
[
'value'
],
"Password: Invalid Length (must be 6 characters or more)"
,
)
@override_settings
(
PASSWORD_MIN_LENGTH
=
6
)
def
test_password_length_long_enough
(
self
):
self
.
url_params
[
'password'
]
=
'ThisIsALongerPassword'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertTrue
(
obj
[
'success'
])
@override_settings
(
PASSWORD_MAX_LENGTH
=
12
)
def
test_password_length_too_long
(
self
):
self
.
url_params
[
'password'
]
=
'ThisPasswordIsWayTooLong'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
400
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
obj
[
'value'
],
"Password: Invalid Length (must be 12 characters or less)"
,
)
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'UPPER'
:
3
})
def
test_password_not_enough_uppercase
(
self
):
self
.
url_params
[
'password'
]
=
'thisshouldfail'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
400
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
obj
[
'value'
],
"Password: Must be more complex (must contain 3 or more uppercase characters)"
,
)
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'UPPER'
:
3
})
def
test_password_enough_uppercase
(
self
):
self
.
url_params
[
'password'
]
=
'ThisShouldPass'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertTrue
(
obj
[
'success'
])
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'LOWER'
:
3
})
def
test_password_not_enough_lowercase
(
self
):
self
.
url_params
[
'password'
]
=
'THISSHOULDFAIL'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
400
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
obj
[
'value'
],
"Password: Must be more complex (must contain 3 or more lowercase characters)"
,
)
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'LOWER'
:
3
})
def
test_password_not_enough_lowercase
(
self
):
self
.
url_params
[
'password'
]
=
'ThisShouldPass'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertTrue
(
obj
[
'success'
])
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'DIGITS'
:
3
})
def
test_not_enough_digits
(
self
):
self
.
url_params
[
'password'
]
=
'thishasnodigits'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
400
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
obj
[
'value'
],
"Password: Must be more complex (must contain 3 or more digits)"
,
)
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'DIGITS'
:
3
})
def
test_enough_digits
(
self
):
self
.
url_params
[
'password'
]
=
'Th1sSh0uldPa88'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertTrue
(
obj
[
'success'
])
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'PUNCTUATION'
:
3
})
def
test_not_enough_punctuations
(
self
):
self
.
url_params
[
'password'
]
=
'thisshouldfail'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
400
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
obj
[
'value'
],
"Password: Must be more complex (must contain 3 or more punctuation characters)"
,
)
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'PUNCTUATION'
:
3
})
def
test_enough_punctuations
(
self
):
self
.
url_params
[
'password'
]
=
'Th!sSh.uldPa$*'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertTrue
(
obj
[
'success'
])
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'WORDS'
:
3
})
def
test_not_enough_words
(
self
):
self
.
url_params
[
'password'
]
=
'thisshouldfail'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
400
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
obj
[
'value'
],
"Password: Must be more complex (must contain 3 or more unique words)"
,
)
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'WORDS'
:
3
})
def
test_enough_wordss
(
self
):
self
.
url_params
[
'password'
]
=
u'this should pass'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertTrue
(
obj
[
'success'
])
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'PUNCTUATION'
:
3
,
'WORDS'
:
3
,
'DIGITS'
:
3
,
'LOWER'
:
3
,
'UPPER'
:
3
,
})
def
test_multiple_errors_fail
(
self
):
self
.
url_params
[
'password'
]
=
'thisshouldfail'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
400
)
obj
=
json
.
loads
(
response
.
content
)
errstring
=
(
"Password: Must be more complex ("
"must contain 3 or more uppercase characters, "
"must contain 3 or more digits, "
"must contain 3 or more punctuation characters, "
"must contain 3 or more unique words"
")"
)
self
.
assertEqual
(
obj
[
'value'
],
errstring
)
@patch.dict
(
"django.conf.settings.PASSWORD_COMPLEXITY"
,
{
'PUNCTUATION'
:
3
,
'WORDS'
:
3
,
'DIGITS'
:
3
,
'LOWER'
:
3
,
'UPPER'
:
3
,
})
def
test_multiple_errors_pass
(
self
):
self
.
url_params
[
'password'
]
=
u'tH1s Sh0u!d P3#$'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertTrue
(
obj
[
'success'
])
@override_settings
(
PASSWORD_DICTIONARY
=
[
'foo'
,
'bar'
])
@override_settings
(
PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD
=
1
)
def
test_dictionary_similarity_fail1
(
self
):
self
.
url_params
[
'password'
]
=
'foo'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
400
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
obj
[
'value'
],
"Password: Too similar to a restricted dictionary word."
,
)
@override_settings
(
PASSWORD_DICTIONARY
=
[
'foo'
,
'bar'
])
@override_settings
(
PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD
=
1
)
def
test_dictionary_similarity_fail2
(
self
):
self
.
url_params
[
'password'
]
=
'bar'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
400
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
obj
[
'value'
],
"Password: Too similar to a restricted dictionary word."
,
)
@override_settings
(
PASSWORD_DICTIONARY
=
[
'foo'
,
'bar'
])
@override_settings
(
PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD
=
1
)
def
test_dictionary_similarity_fail3
(
self
):
self
.
url_params
[
'password'
]
=
'fo0'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
400
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
obj
[
'value'
],
"Password: Too similar to a restricted dictionary word."
,
)
@override_settings
(
PASSWORD_DICTIONARY
=
[
'foo'
,
'bar'
])
@override_settings
(
PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD
=
1
)
def
test_dictionary_similarity_pass
(
self
):
self
.
url_params
[
'password'
]
=
'this_is_ok'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertTrue
(
obj
[
'success'
])
def
test_with_unicode
(
self
):
self
.
url_params
[
'password'
]
=
u'四節比分和七年前'
response
=
self
.
client
.
post
(
self
.
url
,
self
.
url_params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
obj
=
json
.
loads
(
response
.
content
)
self
.
assertTrue
(
obj
[
'success'
])
common/djangoapps/student/views.py
View file @
1ff4671b
...
...
@@ -73,6 +73,11 @@ from util.json_request import JsonResponse
from
microsite_configuration.middleware
import
MicrositeConfiguration
from
util.password_policy_validators
import
(
validate_password_length
,
validate_password_complexity
,
validate_password_dictionary
)
log
=
logging
.
getLogger
(
"edx.student"
)
AUDIT_LOG
=
logging
.
getLogger
(
"audit"
)
...
...
@@ -973,6 +978,19 @@ def create_account(request, post_override=None):
js
[
'field'
]
=
'username'
return
JsonResponse
(
js
,
status
=
400
)
# enforce password complexity as an optional feature
if
settings
.
FEATURES
.
get
(
'ENFORCE_PASSWORD_POLICY'
,
False
):
try
:
password
=
post_vars
[
'password'
]
validate_password_length
(
password
)
validate_password_complexity
(
password
)
validate_password_dictionary
(
password
)
except
ValidationError
,
err
:
js
[
'value'
]
=
_
(
'Password: '
)
+
'; '
.
join
(
err
.
messages
)
js
[
'field'
]
=
'password'
return
JsonResponse
(
js
,
status
=
400
)
# Ok, looks like everything is legit. Create the account.
ret
=
_do_create_account
(
post_vars
)
if
isinstance
(
ret
,
HttpResponse
):
# if there was an error then return that
...
...
common/djangoapps/util/password_policy_validators.py
0 → 100644
View file @
1ff4671b
# pylint: disable=E1101
"""
This file exposes a number of password complexity validators which can be optionally added to
account creation
This file was inspired by the django-passwords project at https://github.com/dstufft/django-passwords
authored by dstufft (https://github.com/dstufft)
"""
from
__future__
import
division
import
string
# pylint: disable=W0402
from
django.core.exceptions
import
ValidationError
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.conf
import
settings
import
nltk
def
validate_password_length
(
value
):
"""
Validator that enforces minimum length of a password
"""
message
=
_
(
"Invalid Length ({0})"
)
code
=
"length"
min_length
=
getattr
(
settings
,
'PASSWORD_MIN_LENGTH'
,
None
)
max_length
=
getattr
(
settings
,
'PASSWORD_MAX_LENGTH'
,
None
)
if
min_length
and
len
(
value
)
<
min_length
:
raise
ValidationError
(
message
.
format
(
_
(
"must be {0} characters or more"
)
.
format
(
min_length
)),
code
=
code
)
elif
max_length
and
len
(
value
)
>
max_length
:
raise
ValidationError
(
message
.
format
(
_
(
"must be {0} characters or less"
)
.
format
(
max_length
)),
code
=
code
)
def
validate_password_complexity
(
value
):
"""
Validator that enforces minimum complexity
"""
message
=
_
(
"Must be more complex ({0})"
)
code
=
"complexity"
complexities
=
getattr
(
settings
,
"PASSWORD_COMPLEXITY"
,
None
)
if
complexities
is
None
:
return
uppercase
,
lowercase
,
digits
,
non_ascii
,
punctuation
=
set
(),
set
(),
set
(),
set
(),
set
()
for
character
in
value
:
if
character
.
isupper
():
uppercase
.
add
(
character
)
elif
character
.
islower
():
lowercase
.
add
(
character
)
elif
character
.
isdigit
():
digits
.
add
(
character
)
elif
character
in
string
.
punctuation
:
punctuation
.
add
(
character
)
else
:
non_ascii
.
add
(
character
)
words
=
set
(
value
.
split
())
errors
=
[]
if
len
(
uppercase
)
<
complexities
.
get
(
"UPPER"
,
0
):
errors
.
append
(
_
(
"must contain {0} or more uppercase characters"
)
.
format
(
complexities
[
"UPPER"
]))
if
len
(
lowercase
)
<
complexities
.
get
(
"LOWER"
,
0
):
errors
.
append
(
_
(
"must contain {0} or more lowercase characters"
)
.
format
(
complexities
[
"LOWER"
]))
if
len
(
digits
)
<
complexities
.
get
(
"DIGITS"
,
0
):
errors
.
append
(
_
(
"must contain {0} or more digits"
)
.
format
(
complexities
[
"DIGITS"
]))
if
len
(
punctuation
)
<
complexities
.
get
(
"PUNCTUATION"
,
0
):
errors
.
append
(
_
(
"must contain {0} or more punctuation characters"
)
.
format
(
complexities
[
"PUNCTUATION"
]))
if
len
(
non_ascii
)
<
complexities
.
get
(
"NON ASCII"
,
0
):
errors
.
append
(
_
(
"must contain {0} or more non ascii characters"
)
.
format
(
complexities
[
"NON ASCII"
]))
if
len
(
words
)
<
complexities
.
get
(
"WORDS"
,
0
):
errors
.
append
(
_
(
"must contain {0} or more unique words"
)
.
format
(
complexities
[
"WORDS"
]))
if
errors
:
raise
ValidationError
(
message
.
format
(
u', '
.
join
(
errors
)),
code
=
code
)
def
validate_password_dictionary
(
value
):
"""
Insures that the password is not too similar to a defined set of dictionary words
"""
password_max_edit_distance
=
getattr
(
settings
,
"PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD"
,
None
)
password_dictionary
=
getattr
(
settings
,
"PASSWORD_DICTIONARY"
,
None
)
if
password_max_edit_distance
and
password_dictionary
:
for
word
in
password_dictionary
:
distance
=
nltk
.
metrics
.
distance
.
edit_distance
(
value
,
word
)
if
distance
<=
password_max_edit_distance
:
raise
ValidationError
(
_
(
"Too similar to a restricted dictionary word."
),
code
=
"dictionary_word"
)
lms/envs/aws.py
View file @
1ff4671b
...
...
@@ -357,3 +357,10 @@ if MICROSITE_CONFIGURATION:
VIRTUAL_UNIVERSITIES
,
microsites_root
=
path
(
MICROSITE_ROOT_DIR
)
)
#### PASSWORD POLICY SETTINGS #####
PASSWORD_MIN_LENGTH
=
ENV_TOKENS
.
get
(
"PASSWORD_MIN_LENGTH"
)
PASSWORD_MAX_LENGTH
=
ENV_TOKENS
.
get
(
"PASSWORD_MAX_LENGTH"
)
PASSWORD_COMPLEXITY
=
ENV_TOKENS
.
get
(
"PASSWORD_COMPLEXITY"
,
{})
PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD
=
ENV_TOKENS
.
get
(
"PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD"
)
PASSWORD_DICTIONARY
=
ENV_TOKENS
.
get
(
"PASSWORD_DICTIONARY"
,
[])
lms/envs/common.py
View file @
1ff4671b
...
...
@@ -203,6 +203,9 @@ FEATURES = {
# grades CSV files to S3 and give links for downloads.
'ENABLE_S3_GRADE_DOWNLOADS'
:
False
,
# whether to use password policy enforcement or not
'ENFORCE_PASSWORD_POLICY'
:
False
,
# Give course staff unrestricted access to grade downloads (if set to False,
# only edX superusers can perform the downloads)
'ALLOW_COURSE_STAFF_GRADE_DOWNLOADS'
:
False
,
...
...
@@ -1188,6 +1191,14 @@ GRADES_DOWNLOAD = {
'ROOT_PATH'
:
'/tmp/edx-s3/grades'
,
}
#### PASSWORD POLICY SETTINGS #####
PASSWORD_MIN_LENGTH
=
None
PASSWORD_MAX_LENGTH
=
None
PASSWORD_COMPLEXITY
=
{}
PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD
=
None
PASSWORD_DICTIONARY
=
[]
##################### LinkedIn #####################
INSTALLED_APPS
+=
(
'django_openid_auth'
,)
...
...
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