Merge branch 'develop' into password-strength-for-long-passwords

This commit is contained in:
Raffael Meyer 2023-01-24 15:06:05 +01:00 committed by GitHub
commit ad77a74df9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 181 additions and 86 deletions

View file

@ -11,6 +11,7 @@ from frappe.model.utils import render_include
from frappe.modules import get_module_path, load_doctype_module, scrub
from frappe.translate import extract_messages_from_code, make_dict_from_messages
from frappe.utils import get_html_format
from frappe.utils.data import get_link_to_form
ASSET_KEYS = (
"__js",
@ -199,14 +200,19 @@ class FormMeta(Meta):
# customizations are removed or some custom app is removed but hasn't cleaned
# up after itself.
frappe.clear_last_message()
customize_form_link = f'<a href="/app/customize-form/?doc_type={self.name}">Customize Form</a>'
frappe.throw(
_(
"Field {0} is referring to non-existing doctype {1}, please remove the field from {2} or add the required doctype."
).format(frappe.bold(df.fieldname), frappe.bold(df.options), customize_form_link),
title=_("Missing DocType"),
msg = _("Field {0} is referring to non-existing doctype {1}.").format(
frappe.bold(df.fieldname), frappe.bold(df.options)
)
if df.get("is_custom_field"):
custom_field_link = get_link_to_form("Custom Field", df.name)
msg += " " + _("Please delete the field from {2} or add the required doctype.").format(
custom_field_link
)
frappe.throw(msg, title=_("Missing DocType"))
def add_linked_document_type(self):
for df in self.get("fields", {"fieldtype": "Link"}):
if df.options:

View file

@ -45,6 +45,7 @@ class Notification(Document):
frappe.cache().hdel("notifications", self.document_type)
def on_update(self):
frappe.cache().hdel("notifications", self.document_type)
path = export_module_json(self, self.is_standard, self.module)
if path:
# js

View file

@ -88,8 +88,7 @@
"fieldtype": "Link",
"label": "Default User Role",
"mandatory_depends_on": "eval: doc.default_user_type == \"System User\"",
"options": "Role",
"reqd": 1
"options": "Role"
},
{
"description": "Must be enclosed in '()' and include '{0}', which is a placeholder for the user/login name. i.e. (&(objectclass=user)(uid={0}))",
@ -302,7 +301,7 @@
"in_create": 1,
"issingle": 1,
"links": [],
"modified": "2022-12-05 21:52:31.146035",
"modified": "2023-01-24 11:20:06.049708",
"modified_by": "Administrator",
"module": "Integrations",
"name": "LDAP Settings",

View file

@ -26,7 +26,7 @@ if TYPE_CHECKING:
class LDAPSettings(Document):
def validate(self):
self.default_user_type = self.default_user_type or "System User"
self.default_user_type = self.default_user_type or "Website User"
if not self.enabled:
return

View file

@ -173,7 +173,6 @@ class LDAP_TestCase:
"ldap_username_field",
"ldap_first_name_field",
"require_trusted_certificate",
"default_role",
] # fields that are required to have ldap functioning need to be mandatory
for mandatory_field in mandatory_fields:

View file

@ -35,54 +35,60 @@ DOCTYPES_FOR_DOCTYPE = {"DocType", *TABLE_DOCTYPES_FOR_DOCTYPE.values()}
def get_controller(doctype):
"""Returns the **class** object of the given DocType.
"""
Returns the locally cached **class** object of the given DocType.
For `custom` type, returns `frappe.model.document.Document`.
:param doctype: DocType name as string."""
def _get_controller():
from frappe.model.document import Document
from frappe.utils.nestedset import NestedSet
module_name, custom = frappe.db.get_value(
"DocType", doctype, ("module", "custom"), cache=True
) or ("Core", False)
if custom:
is_tree = frappe.db.get_value("DocType", doctype, "is_tree", ignore=True, cache=True)
_class = NestedSet if is_tree else Document
else:
class_overrides = frappe.get_hooks("override_doctype_class")
if class_overrides and class_overrides.get(doctype):
import_path = class_overrides[doctype][-1]
module_path, classname = import_path.rsplit(".", 1)
module = frappe.get_module(module_path)
if not hasattr(module, classname):
raise ImportError(f"{doctype}: {classname} does not exist in module {module_path}")
else:
module = load_doctype_module(doctype, module_name)
classname = doctype.replace(" ", "").replace("-", "")
if hasattr(module, classname):
_class = getattr(module, classname)
if issubclass(_class, BaseDocument):
_class = getattr(module, classname)
else:
raise ImportError(doctype)
else:
raise ImportError(doctype)
return _class
:param doctype: DocType name as string.
"""
if frappe.local.dev_server:
return _get_controller()
return import_controller(doctype)
site_controllers = frappe.controllers.setdefault(frappe.local.site, {})
if doctype not in site_controllers:
site_controllers[doctype] = _get_controller()
site_controllers[doctype] = import_controller(doctype)
return site_controllers[doctype]
def import_controller(doctype):
from frappe.model.document import Document
from frappe.utils.nestedset import NestedSet
module_name = "Core"
if doctype not in DOCTYPES_FOR_DOCTYPE:
meta = frappe.get_meta(doctype)
if meta.custom:
return NestedSet if meta.get("is_tree") else Document
module_name = meta.module
module_path = None
class_overrides = frappe.get_hooks("override_doctype_class")
if class_overrides and class_overrides.get(doctype):
import_path = class_overrides[doctype][-1]
module_path, classname = import_path.rsplit(".", 1)
module = frappe.get_module(module_path)
else:
module = load_doctype_module(doctype, module_name)
classname = doctype.replace(" ", "").replace("-", "")
class_ = getattr(module, classname, None)
if class_ is None:
raise ImportError(
doctype
if module_path is None
else f"{doctype}: {classname} does not exist in module {module_path}"
)
if not issubclass(class_, BaseDocument):
raise ImportError(f"{doctype}: {classname} is not a subclass of BaseDocument")
return class_
class BaseDocument:
_reserved_keywords = {
"doctype",

View file

@ -7,22 +7,49 @@ frappe.ui.form.ControlPassword = class ControlPassword extends frappe.ui.form.Co
make_input() {
var me = this;
super.make_input();
this.$input
.parent()
.append($('<span class="password-strength-indicator indicator"></span>'));
this.$wrapper
.find(".control-input-wrapper")
.append($('<p class="password-strength-message text-muted small hidden"></p>'));
this.indicator = this.$wrapper.find(".password-strength-indicator");
this.indicator = $(
`<div class="password-strength-indicator hidden">
<div class="progress-text"></div>
<div class="progress">
<div class="progress-bar" role="progressbar"
aria-valuenow="0"
aria-valuemin="0" aria-valuemax="100">
</div>
</div>
</div>`
).insertAfter(this.$input);
this.progress_text = this.indicator.find(".progress-text");
this.progress_bar = this.indicator.find(".progress-bar");
this.message = this.$wrapper.find(".help-box");
this.$input.on("keyup", () => {
clearTimeout(this.check_password_timeout);
this.check_password_timeout = setTimeout(() => {
this.$input.on(
"keyup",
frappe.utils.debounce(() => {
let hide_icon = me.$input.val() && !me.$input.val().includes("*");
me.toggle_password.toggleClass("hidden", !hide_icon);
me.get_password_strength(me.$input.val());
}, 500);
}, 500)
);
this.toggle_password = $(`
<div class="toggle-password hidden">
${frappe.utils.icon("unhide", "sm")}
</div>
`).insertAfter(this.$input);
this.toggle_password.on("click", () => {
if (this.$input.attr("type") === "password") {
this.$input.attr("type", "text");
this.toggle_password.html(frappe.utils.icon("hide", "sm"));
} else {
this.$input.attr("type", "password");
this.toggle_password.html(frappe.utils.icon("unhide", "sm"));
}
});
!this.value && this.toggle_password.removeClass("hidden");
}
disable_password_checks() {
@ -33,6 +60,13 @@ frappe.ui.form.ControlPassword = class ControlPassword extends frappe.ui.form.Co
if (!this.enable_password_checks) {
return;
}
if (!value) {
this.indicator.addClass("hidden");
this.message.addClass("hidden");
return;
}
var me = this;
frappe.call({
type: "POST",
@ -43,15 +77,34 @@ frappe.ui.form.ControlPassword = class ControlPassword extends frappe.ui.form.Co
callback: function (r) {
if (r.message) {
let score = r.message.score;
var indicators = ["red", "red", "orange", "yellow", "green"];
var indicators = ["red", "red", "orange", "blue", "green"];
me.set_strength_indicator(indicators[score]);
}
},
});
}
set_strength_indicator(color) {
var message = __("Include symbols, numbers and capital letters in the password");
this.indicator.removeClass().addClass("password-strength-indicator indicator " + color);
let strength = {
red: [__("Weak"), "danger", 25],
orange: [__("Average"), "warning", 50],
blue: [__("Strong"), "info", 75],
green: [__("Excellent"), "success", 100],
};
let progress_text = strength[color][0];
let progress_color = strength[color][1];
let progress_percent = strength[color][2];
this.indicator.removeClass("hidden");
this.progress_text.html(progress_text).css("color", `var(--${color}-500)`);
this.progress_bar
.css("width", progress_percent + "%")
.attr("aria-valuenow", progress_percent)
.removeClass()
.addClass("progress-bar progress-bar-" + progress_color);
let message = __("Include symbols, numbers and capital letters in the password");
this.message.html(message).toggleClass("hidden", color == "green");
}
};

View file

@ -195,7 +195,9 @@ $.extend(frappe.perm, {
}
if (!perm) {
return df && (cint(df.hidden) || cint(df.hidden_due_to_dependency)) ? "None" : "Write";
let is_hidden = df && (cint(df.hidden) || cint(df.hidden_due_to_dependency));
let is_read_only = df && cint(df.read_only);
return is_hidden ? "None" : is_read_only ? "Read" : "Write";
}
if (!df.permlevel) df.permlevel = 0;

View file

@ -5,20 +5,43 @@
@import "phone_picker";
// password
.form-control[data-fieldtype="Password"] {
position: inherit;
}
.frappe-control[data-fieldtype="Password"] {
.control-input-wrapper {
position: relative;
.password-strength-indicator {
// TODO: Review
float: right;
padding: 15px;
margin-top: -41px;
margin-right: -7px;
}
.form-control[data-fieldtype="Password"] {
position: inherit;
}
.password-strength-message {
margin-top: -10px;
.password-strength-indicator {
display: flex;
align-items: center;
position: absolute;
gap: 5px;
top: -20px;
right: 0px;
.progress-text {
font-size: var(--text-xs);
font-weight: 600;
}
.progress {
background-color: var(--bg-light-gray);
width: 100px;
height: 5px;
}
}
.toggle-password {
position: absolute;
top: 4px;
right: 8px;
padding: 3px;
z-index: 3;
cursor: pointer;
}
}
}
// select
@ -232,6 +255,10 @@ a.progress-small {
background-color: var(--red-500);
}
.progress-bar-info {
background-color: var(--blue-500);
}
.progress-bar-warning {
background-color: var(--orange-500);
}

View file

@ -43,16 +43,18 @@
}
}
// hide row index in 6 column child tables
.form-column.col-sm-6 .form-grid {
.row-index {
display: none;
}
.btn-open-row {
.edit-grid-row {
// hide row index in 6/4 column child tables
.form-column.col-sm-6, .form-column.col-sm-4 {
.form-grid {
.row-index {
display: none;
}
.btn-open-row {
.edit-grid-row {
display: none;
}
}
}
}

View file

@ -742,9 +742,9 @@ cookie@^0.4.0, cookie@~0.4.1:
integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
cookiejar@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==
version "2.1.4"
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b"
integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==
copy-anything@^2.0.1:
version "2.0.3"