diff --git a/frappe/auth.py b/frappe/auth.py index dba8b05a62..1353acf10f 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -219,7 +219,10 @@ class LoginManager: user = frappe.db.get_value("User", filters={"username": user}, fieldname="name") or user self.check_if_enabled(user) - self.user = self.check_password(user, pwd) + if not frappe.form_dict.get('tmp_id'): + self.user = self.check_password(user, pwd) + else: + self.user = user def force_user_to_reset_password(self): if not self.user: diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 2ff3a72b38..b35abfa861 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -1,46 +1,45 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt -from __future__ import unicode_literals -from frappe import _ """ record of files naming for same name files: file.gif, file-1.gif, file-2.gif etc """ -import frappe -import json -import os +from __future__ import unicode_literals + import base64 -import re import hashlib -import mimetypes +import imghdr import io +import json +import mimetypes +import os +import re import shutil +import zipfile + import requests import requests.exceptions -import imghdr +from PIL import Image, ImageFile, ImageOps +from six import PY2, StringIO, string_types, text_type +from six.moves.urllib.parse import quote, unquote -from frappe.utils import get_hook_method, get_files_path, random_string, encode, cstr, call_hook_method, cint -from frappe import _ -from frappe import conf -from frappe.utils.nestedset import NestedSet +import frappe +from frappe import _, conf from frappe.model.document import Document -from frappe.utils import strip -from PIL import Image, ImageOps -from six import StringIO, string_types -from six.moves.urllib.parse import unquote, quote -from six import text_type, PY2 -import zipfile +from frappe.utils import call_hook_method, cint, cstr, encode, get_files_path, get_hook_method, random_string, strip + class MaxFileSizeReachedError(frappe.ValidationError): pass - -class FolderNotEmpty(frappe.ValidationError): pass +class FolderNotEmpty(frappe.ValidationError): + pass exclude_from_linked_with = True +ImageFile.LOAD_TRUNCATED_IMAGES = True class File(Document): @@ -697,7 +696,7 @@ def remove_file(fid=None, attached_to_doctype=None, attached_to_name=None, from_ def get_max_file_size(): - return conf.get('max_file_size') or 10485760 + return cint(conf.get('max_file_size')) or 10485760 def remove_all(dt, dn, from_delete=False): diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 36a75bd9d5..7bed8f4504 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -79,7 +79,7 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d to_date = chart.to_date timegrain = time_interval or chart.time_interval - filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json) + filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json) or [] # don't include cancelled documents filters.append([chart.document_type, 'docstatus', '<', 2, False]) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index 558f7117c0..80dfef2693 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _, safe_encode from frappe.model.document import Document - +from frappe.twofactor import (should_run_2fa, authenticate_for_2factor,confirm_otp_token) class LDAPSettings(Document): def validate(self): @@ -237,6 +237,10 @@ def login(): user = ldap.authenticate(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd)) frappe.local.login_manager.user = user.name + if should_run_2fa(user.name): + authenticate_for_2factor(user.name) + if not confirm_otp_token(frappe.local.login_manager): + return False frappe.local.login_manager.post_login() # because of a GET request! diff --git a/frappe/public/js/frappe/dom.js b/frappe/public/js/frappe/dom.js index 819ecb526e..7b59f9da08 100644 --- a/frappe/public/js/frappe/dom.js +++ b/frappe/public/js/frappe/dom.js @@ -85,6 +85,10 @@ frappe.dom = { ); }, + is_element_in_modal(element) { + return Boolean($(element).parents('.modal').length); + }, + set_style: function(txt, id) { if(!txt) return; diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 31d62dc445..97d081dbf5 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -22,9 +22,6 @@ export default class GridRow { if(me.grid.allow_on_grid_editing() && me.grid.is_editable()) { // pass } else { - if (!me.grid.is_editable()) { - me.docfields.map(df => df.read_only = 1); - } me.toggle_view(); return false; } @@ -527,7 +524,7 @@ export default class GridRow { return this; } show_form() { - if(!this.grid_form) { + if (!this.grid_form) { this.grid_form = new GridRowForm({ row: this }); @@ -536,13 +533,15 @@ export default class GridRow { this.row.toggle(false); // this.form_panel.toggle(true); frappe.dom.freeze("", "dark"); - if(cur_frm) cur_frm.cur_grid = this; + if (cur_frm) cur_frm.cur_grid = this; this.wrapper.addClass("grid-row-open"); - if(!frappe.dom.is_element_in_viewport(this.wrapper)) { - frappe.utils.scroll_to(this.wrapper, true, 15); + if (!frappe.dom.is_element_in_viewport(this.wrapper) + && !frappe.dom.is_element_in_modal(this.wrapper)) { + // -15 offset to make form look visually centered + frappe.utils.scroll_to(this.wrapper, true, -15); } - if(this.frm) { + if (this.frm) { this.frm.script_manager.trigger(this.doc.parentfield + "_on_form_rendered"); this.frm.script_manager.trigger("form_render", this.doc.doctype, this.doc.name); } @@ -550,7 +549,9 @@ export default class GridRow { hide_form() { frappe.dom.unfreeze(); this.row.toggle(true); - frappe.utils.scroll_to(this.row, true, 15); + if (!frappe.dom.is_element_in_modal(this.row)) { + frappe.utils.scroll_to(this.row, true, 15); + } this.refresh(); if(cur_frm) cur_frm.cur_grid = null; this.wrapper.removeClass("grid-row-open"); diff --git a/frappe/public/less/form_grid.less b/frappe/public/less/form_grid.less index ed457a9ce8..88c5cf7bbc 100644 --- a/frappe/public/less/form_grid.less +++ b/frappe/public/less/form_grid.less @@ -263,8 +263,10 @@ } .grid-form-body { - max-height: 75vh; - overflow-y: auto; + .form-area { + max-height: 70vh; + overflow-y: auto; + } } .grid-header-toolbar { diff --git a/frappe/twofactor.py b/frappe/twofactor.py index e60113215b..b44092d6a1 100644 --- a/frappe/twofactor.py +++ b/frappe/twofactor.py @@ -374,11 +374,11 @@ def delete_qrimage(user, check_expiry=False): def delete_all_barcodes_for_users(): '''Task to delete all barcodes for user.''' - if not two_factor_is_enabled(): - return users = frappe.get_all('User', {'enabled':1}) for user in users: + if not two_factor_is_enabled(user=user.name): + continue delete_qrimage(user.name, check_expiry=True) def should_remove_barcode_image(barcode): diff --git a/rollup/config.js b/rollup/config.js index 50e8f33061..8930a15a29 100644 --- a/rollup/config.js +++ b/rollup/config.js @@ -109,7 +109,7 @@ function get_rollup_options_for_js(output_file, input_files) { function get_rollup_options_for_css(output_file, input_files) { const output_path = path.resolve(assets_path, output_file); - const minimize_css = output_path.includes('/assets/css/') && production; + const starts_with_css = output_file.startsWith('css/'); const plugins = [ // enables array of inputs @@ -120,26 +120,29 @@ function get_rollup_options_for_css(output_file, input_files) { require('tailwindcss'), require('postcss-nested'), require('autoprefixer'), - minimize_css ? require('cssnano')({ preset: 'default' }) : null + starts_with_css && production ? require('cssnano')({ preset: 'default' }) : null ].filter(Boolean), extract: output_path, loaders: [less_loader], use: [ - [ - 'less', - { - // import other less/css files starting from these folders - paths: [path.resolve(get_public_path('frappe'), 'less')] - } - ], - ['sass', get_options_for_scss()] + ['less', { + // import other less/css files starting from these folders + paths: [ + path.resolve(get_public_path('frappe'), 'less') + ] + }], + ['sass', { + ...get_options_for_scss(), + outFile: output_path, + sourceMapContents: true + }] ], include: [ path.resolve(bench_path, '**/*.less'), path.resolve(bench_path, '**/*.scss'), path.resolve(bench_path, '**/*.css') ], - sourceMap: output_file.startsWith('css/') && !production + sourceMap: starts_with_css && !production }) ];