* Added DocType Social Login Key WIP for https://github.com/frappe/frappe/issues/4496 added basic fields after_insert add provider_username and provider_userid fields on User dt on_trash deletes added fields on User dt * Added field to store fontawesome icon for provider * [Patch] Social Login Keys to Social Login Key * [Patch] Social Login Keys to Social Login Key * Social Login Key generates boilerplate * patch fixed for social_login_refactor * removed patch-not working * use social login keys to initiate flow * Login page shows Social Login Key * show login via if base_url present * removed boilerplate generator * Multiple Changes fix zxcvbn import in password_strength.py use of child table instead of additional fields on user dt to store username and userid * Fetched Template on Client JS * Frappe social login template working * Added Social Login Key Templates * Codacy fixes and validate social login key urls * [Patch] Social Login Keys (untested) * [Fix] Patch refactor social login keys * [Fix] Patch refactor_social_login_keys manually tested * Refactor OAuth 2.0 related changes for Social Login Key * [Fix] Patch refactor social login keys * Test - Adding Frappe Social Login Key * Social Login Key Tests check added child table entry on user for provider frappe it also checks if userid is created * [WIP] Office 365 Social Login Key Template * [Fix] Social Login - Redirect URL * [Test] Single sign-on icons for added provider * [Fix] Codacy Errors * [Fix] Social Login Key Form JS * Docs Added for Social Login Key * [Fix] Patch Refactor Social Login Keys * Handle different icon types Handle different icon types (image, icon, emoji) with just icon field * Move the login methods to a new py file frappe.integrations.oauth2_logins added copied whitelisted guest oauth2 redirect endpoints from login.py removing the functions from login.py will break backward compatibility * Social Login Key Form Changes Moved Enable field to top Fields which are not editable are collapsed * [Fix] Codacy Errors * Corrected Docs, sync.py * [Docs] Adding a social login provider * [Fix] set frappe userid from User Social Login * [Fix] frappe userid in oauth.py * removed icon_type * Use frappe.utils.is_image
207 lines
5.7 KiB
Python
207 lines
5.7 KiB
Python
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# MIT License. See license.txt
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
try:
|
|
from zxcvbn import zxcvbn
|
|
except Exception as e:
|
|
import zxcvbn
|
|
|
|
import frappe
|
|
from frappe import _
|
|
|
|
|
|
def test_password_strength(password, user_inputs=None):
|
|
'''Wrapper around zxcvbn.password_strength'''
|
|
result = zxcvbn(password, user_inputs)
|
|
result.update({
|
|
"feedback": get_feedback(result.get('score'), result.get('sequence'))
|
|
})
|
|
return result
|
|
|
|
# NOTE: code modified for frappe translations
|
|
# -------------------------------------------
|
|
# feedback functionality code from https://github.com/sans-serif/python-zxcvbn/blob/master/zxcvbn/feedback.py
|
|
# see license for feedback code at https://github.com/sans-serif/python-zxcvbn/blob/master/LICENSE.txt
|
|
|
|
# Used for regex matching capitalization
|
|
import re
|
|
# Used to get the regex patterns for capitalization
|
|
# (Used the same way in the original zxcvbn)
|
|
from zxcvbn import scoring
|
|
|
|
# Default feedback value
|
|
default_feedback = {
|
|
"warning": "",
|
|
"suggestions":[
|
|
_("Use a few words, avoid common phrases."),
|
|
_("No need for symbols, digits, or uppercase letters."),
|
|
],
|
|
}
|
|
|
|
|
|
def get_feedback(score, sequence):
|
|
"""
|
|
Returns the feedback dictionary consisting of ("warning","suggestions") for the given sequences.
|
|
"""
|
|
global default_feedback
|
|
minimum_password_score = int(frappe.db.get_single_value("System Settings", "minimum_password_score") or 2)
|
|
|
|
# Starting feedback
|
|
if len(sequence) == 0:
|
|
return default_feedback
|
|
|
|
# No feedback if score is good or great
|
|
if score >= minimum_password_score:
|
|
return dict({"warning": "", "suggestions": []})
|
|
|
|
# Tie feedback to the longest match for longer sequences
|
|
longest_match = max(sequence, key=lambda seq: len(seq.get('token', '')))
|
|
|
|
# Get feedback for this match
|
|
feedback = get_match_feedback(longest_match, len(sequence) == 1)
|
|
|
|
# If no concrete feedback returned, give more general feedback
|
|
if not feedback:
|
|
feedback = {
|
|
"warning": "",
|
|
"suggestions":[
|
|
_("Better add a few more letters or another word")
|
|
],
|
|
}
|
|
return feedback
|
|
|
|
|
|
def get_match_feedback(match, is_sole_match):
|
|
"""
|
|
Returns feedback as a dictionary for a certain match
|
|
"""
|
|
def fun_bruteforce():
|
|
# Define a number of functions that are used in a look up dictionary
|
|
return None
|
|
|
|
def fun_dictionary():
|
|
# If the match is of type dictionary, call specific function
|
|
return get_dictionary_match_feedback(match, is_sole_match)
|
|
|
|
def fun_spatial():
|
|
feedback = {
|
|
"warning": _('Short keyboard patterns are easy to guess'),
|
|
"suggestions": [
|
|
_("Make use of longer keyboard patterns")
|
|
],
|
|
}
|
|
|
|
if match.get("turns") == 1:
|
|
feedback = {
|
|
"warning": _('Straight rows of keys are easy to guess'),
|
|
"suggestions": [
|
|
_("Try to use a longer keyboard pattern with more turns")
|
|
],
|
|
}
|
|
|
|
return feedback
|
|
|
|
def fun_repeat():
|
|
feedback = {
|
|
"warning": _('Repeats like "abcabcabc" are only slightly harder to guess than "abc"'),
|
|
"suggestions": [
|
|
_("Try to avoid repeated words and characters")
|
|
],
|
|
}
|
|
if len(match.get("repeated_char")) == 1:
|
|
feedback = {
|
|
"warning": _('Repeats like "aaa" are easy to guess'),
|
|
"suggestions": [
|
|
_("Let's avoid repeated words and characters")
|
|
],
|
|
}
|
|
return feedback
|
|
|
|
def fun_sequence():
|
|
return {
|
|
"suggestions": [
|
|
_("Avoid sequences like abc or 6543 as they are easy to guess")
|
|
],
|
|
}
|
|
|
|
def fun_regex():
|
|
if match["regex_name"] == "recent_year":
|
|
return {
|
|
"warning": _("Recent years are easy to guess."),
|
|
"suggestions": [
|
|
_("Avoid recent years."),
|
|
_("Avoid years that are associated with you.")
|
|
],
|
|
}
|
|
|
|
def fun_date():
|
|
return {
|
|
"warning": _("Dates are often easy to guess."),
|
|
"suggestions": [
|
|
_("Avoid dates and years that are associated with you.")
|
|
],
|
|
}
|
|
|
|
# Dictionary that maps pattern names to funtions that return feedback
|
|
patterns = {
|
|
"bruteforce": fun_bruteforce,
|
|
"dictionary": fun_dictionary,
|
|
"spatial": fun_spatial,
|
|
"repeat": fun_repeat,
|
|
"sequence": fun_sequence,
|
|
"regex": fun_regex,
|
|
"date": fun_date,
|
|
"year": fun_date
|
|
}
|
|
pattern_fn = patterns.get(match['pattern'])
|
|
if pattern_fn:
|
|
return (pattern_fn())
|
|
|
|
def get_dictionary_match_feedback(match, is_sole_match):
|
|
"""
|
|
Returns feedback for a match that is found in a dictionary
|
|
"""
|
|
warning = ""
|
|
suggestions = []
|
|
|
|
# If the match is a common password
|
|
if match.get("dictionary_name") == "passwords":
|
|
if is_sole_match and not match.get("l33t_entropy"):
|
|
if match.get("rank") <= 10:
|
|
warning = _("This is a top-10 common password.")
|
|
elif match.get("rank") <= 100:
|
|
warning = _("This is a top-100 common password.")
|
|
else:
|
|
warning = _("This is a very common password.")
|
|
else:
|
|
warning = _("This is similar to a commonly used password.")
|
|
|
|
# If the match is a common english word
|
|
elif match.get("dictionary_name") == "english":
|
|
if is_sole_match:
|
|
warning = _("A word by itself is easy to guess.")
|
|
|
|
# If the match is a common surname/name
|
|
elif match.get("dictionary_name") in ["surnames", "male_names", "female_names"]:
|
|
if is_sole_match:
|
|
warning = _("Names and surnames by themselves are easy to guess.")
|
|
else:
|
|
warning = _("Common names and surnames are easy to guess.")
|
|
|
|
word = match.get("token")
|
|
# Variations of the match like UPPERCASES
|
|
if re.match(scoring.START_UPPER, word):
|
|
suggestions.append(_("Capitalization doesn't help very much."))
|
|
elif re.match(scoring.ALL_UPPER, word):
|
|
suggestions.append(_("All-uppercase is almost as easy to guess as all-lowercase."))
|
|
|
|
# Match contains l33t speak substitutions
|
|
if match.get("l33t_entropy"):
|
|
suggestions.append(_("Predictable substitutions like '@' instead of 'a' don't help very much."))
|
|
|
|
return {
|
|
"warning": warning,
|
|
"suggestions": suggestions
|
|
}
|