diff --git a/web/pgadmin/authenticate/internal.py b/web/pgadmin/authenticate/internal.py index d63fd9204..105e78c4e 100644 --- a/web/pgadmin/authenticate/internal.py +++ b/web/pgadmin/authenticate/internal.py @@ -17,16 +17,18 @@ from flask_babelex import gettext from .registry import AuthSourceRegistry from pgadmin.model import User +from pgadmin.utils.validation_utils import validate_email @six.add_metaclass(AuthSourceRegistry) class BaseAuthentication(object): DEFAULT_MSG = { - 'USER_DOES_NOT_EXIST': 'Incorrect username or password.', - 'LOGIN_FAILED': 'Login failed', - 'EMAIL_NOT_PROVIDED': 'Email/Username not provided', - 'PASSWORD_NOT_PROVIDED': 'Password not provided' + 'USER_DOES_NOT_EXIST': gettext('Incorrect username or password.'), + 'LOGIN_FAILED': gettext('Login failed'), + 'EMAIL_NOT_PROVIDED': gettext('Email/Username not provided'), + 'PASSWORD_NOT_PROVIDED': gettext('Password not provided'), + 'INVALID_EMAIL': gettext('Email/Username is not valid') } @abstractproperty @@ -85,7 +87,10 @@ class InternalAuthentication(BaseAuthentication): def validate(self, form): """User validation""" - + # validate the email id first + if not validate_email(form.data['email']): + form.errors['email'] = [self.messages('INVALID_EMAIL')] + return False # Flask security validation return form.validate_on_submit() diff --git a/web/pgadmin/misc/file_manager/__init__.py b/web/pgadmin/misc/file_manager/__init__.py index c0fb4f721..9ededc1b2 100644 --- a/web/pgadmin/misc/file_manager/__init__.py +++ b/web/pgadmin/misc/file_manager/__init__.py @@ -18,6 +18,7 @@ from urllib.parse import unquote from sys import platform as _platform import config import codecs +import pathlib from werkzeug.exceptions import InternalServerError import simplejson as json @@ -317,6 +318,11 @@ class Filemanager(object): # Stores list of dict for filename & its encoding loaded_file_encoding_list = [] + ERROR_NOT_ALLOWED = { + 'Error': gettext('Not allowed'), + 'Code': 0 + } + def __init__(self, trans_id): self.trans_id = trans_id self.patherror = encode_json( @@ -822,10 +828,7 @@ class Filemanager(object): Rename file or folder """ if not self.validate_request('rename'): - return { - 'Error': gettext('Not allowed'), - 'Code': 0 - } + return self.ERROR_NOT_ALLOWED the_dir = self.dir if self.dir is not None else '' @@ -883,10 +886,7 @@ class Filemanager(object): Delete file or folder """ if not self.validate_request('delete'): - return { - 'Error': gettext('Not allowed'), - 'Code': 0 - } + return self.ERROR_NOT_ALLOWED the_dir = self.dir if self.dir is not None else '' orig_path = "{0}{1}".format(the_dir, path) @@ -924,10 +924,7 @@ class Filemanager(object): File upload functionality """ if not self.validate_request('upload'): - return { - 'Error': gettext('Not allowed'), - 'Code': 0 - } + return self.ERROR_NOT_ALLOWED the_dir = self.dir if self.dir is not None else '' err_msg = '' @@ -940,6 +937,12 @@ class Filemanager(object): orig_path = "{0}{1}".format(the_dir, path) new_name = "{0}{1}".format(orig_path, file_name) + try: + # Check if the new file is inside the users directory + pathlib.Path(new_name).relative_to(the_dir) + except ValueError as _: + return self.ERROR_NOT_ALLOWED + with open(new_name, 'wb') as f: while True: # 4MB chunk (4 * 1024 * 1024 Bytes) @@ -1103,10 +1106,7 @@ class Filemanager(object): Functionality to create new folder """ if not self.validate_request('create'): - return { - 'Error': gettext('Not allowed'), - 'Code': 0 - } + return self.ERROR_NOT_ALLOWED the_dir = self.dir if self.dir is not None else '' @@ -1156,10 +1156,7 @@ class Filemanager(object): Functionality to download file """ if not self.validate_request('download'): - return { - 'Error': gettext('Not allowed'), - 'Code': 0 - } + return self.ERROR_NOT_ALLOWED the_dir = self.dir if self.dir is not None else '' orig_path = "{0}{1}".format(the_dir, path) diff --git a/web/pgadmin/setup/user_info.py b/web/pgadmin/setup/user_info.py index ab38e567a..6aed075db 100644 --- a/web/pgadmin/setup/user_info.py +++ b/web/pgadmin/setup/user_info.py @@ -14,6 +14,8 @@ import os import re import getpass +from pgadmin.utils.validation_utils import validate_email + def user_info_desktop(): print("NOTE: Configuring authentication for DESKTOP mode.") @@ -43,14 +45,8 @@ def user_info_server(): "pgAdmin user account:\n" ) - email_filter = re.compile( - "^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9]" - "(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]" - "(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" - ) - email = input("Email address: ") - while email == '' or not email_filter.match(email): + while not validate_email(email): print('Invalid email address. Please try again.') email = input("Email address: ") diff --git a/web/pgadmin/tools/user_management/__init__.py b/web/pgadmin/tools/user_management/__init__.py index 496a6afe5..df9e9c728 100644 --- a/web/pgadmin/tools/user_management/__init__.py +++ b/web/pgadmin/tools/user_management/__init__.py @@ -22,9 +22,10 @@ from werkzeug.exceptions import InternalServerError import config from pgadmin.utils import PgAdminModule from pgadmin.utils.ajax import make_response as ajax_response, \ - make_json_response, bad_request, internal_server_error + make_json_response, bad_request, internal_server_error, forbidden from pgadmin.utils.csrf import pgCSRFProtect from pgadmin.utils.constants import MIMETYPE_APP_JS +from pgadmin.utils.validation_utils import validate_email from pgadmin.model import db, Role, User, UserPreference, Server, \ ServerGroup, Process, Setting @@ -104,16 +105,11 @@ def validate_password(data, new_data): def validate_user(data): new_data = dict() - email_filter = re.compile( - "^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9]" - "(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9]" - "(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" - ) validate_password(data, new_data) if 'email' in data and data['email'] and data['email'] != "": - if email_filter.match(data['email']): + if validate_email(data['email']): new_data['email'] = data['email'] else: raise InternalServerError(_("Invalid email address.")) @@ -383,6 +379,18 @@ def update(uid): request.data, encoding='utf-8' ) + # Username and email can not be changed for internal users + if usr.auth_source == current_app.PGADMIN_DEFAULT_AUTH_SOURCE: + non_editable_params = ('username', 'email') + + for f in non_editable_params: + if f in data: + return forbidden( + errmsg=_( + "'{0}' is not allowed to modify." + ).format(f) + ) + try: new_data = validate_user(data) diff --git a/web/pgadmin/utils/paths.py b/web/pgadmin/utils/paths.py index efc39a8bd..30bbcfa47 100644 --- a/web/pgadmin/utils/paths.py +++ b/web/pgadmin/utils/paths.py @@ -34,9 +34,18 @@ def get_storage_directory(): if storage_dir is None: return None - username = current_user.username.split('@')[0] - if len(username) == 0 or username[0].isdigit(): - username = 'pga_user_' + username + def _preprocess_username(un): + ret_un = un + if len(ret_un) == 0 or ret_un[0].isdigit(): + ret_un = 'pga_user_' + username + + ret_un = ret_un.replace('@', '_')\ + .replace('/', 'slash')\ + .replace('\\', 'slash') + + return ret_un + + username = _preprocess_username(current_user.username.split('@')[0]) # Figure out the old-style storage directory name old_storage_dir = os.path.join( @@ -45,11 +54,13 @@ def get_storage_directory(): username ) + username = _preprocess_username(current_user.username) + # Figure out the new style storage directory name storage_dir = os.path.join( storage_dir.decode('utf-8') if hasattr(storage_dir, 'decode') else storage_dir, - current_user.username.replace('@', '_') + username ) # Rename an old-style storage directory, if the new style doesn't exist diff --git a/web/pgadmin/utils/validation_utils.py b/web/pgadmin/utils/validation_utils.py new file mode 100644 index 000000000..4bb3f8574 --- /dev/null +++ b/web/pgadmin/utils/validation_utils.py @@ -0,0 +1,26 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2020, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import re + + +def validate_email(email): + if email == '' or email is None: + return False + + email_filter = re.compile( + "^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9]" + "(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]" + "(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" + ) + + if not email_filter.match(email): + return False + + return True