Resolve the log in issue for a user having a non-existing email id

1) Added CHECK_EMAIL_DELIVERABILITY & SECURITY_EMAIL_VALIDATOR_ARGS.
  2) Added test cases for deliverability check.

Fixes #6550
This commit is contained in:
Rahul Shirsat 2021-07-05 12:55:40 +05:30 committed by Akshay Joshi
parent ef67409d61
commit 9fdda038a9
10 changed files with 266 additions and 19 deletions

View File

@ -388,7 +388,9 @@ SECURITY_EMAIL_SUBJECT_PASSWORD_CHANGE_NOTICE = \
# flask-security-too will validate email addresses and check deliverability # flask-security-too will validate email addresses and check deliverability
# by default. Disable the deliverability check by default, which was the old # by default. Disable the deliverability check by default, which was the old
# behaviour in <= v5.3 # behaviour in <= v5.3
SECURITY_EMAIL_VALIDATOR_ARGS = {"check_deliverability": False} CHECK_EMAIL_DELIVERABILITY = False
SECURITY_EMAIL_VALIDATOR_ARGS = \
{"check_deliverability": CHECK_EMAIL_DELIVERABILITY}
########################################################################## ##########################################################################
# Upgrade checks # Upgrade checks

View File

@ -465,6 +465,8 @@ def create_app(app_name=None):
# CSRF Token expiration till session expires # CSRF Token expiration till session expires
'WTF_CSRF_TIME_LIMIT': getattr(config, 'CSRF_TIME_LIMIT', None), 'WTF_CSRF_TIME_LIMIT': getattr(config, 'CSRF_TIME_LIMIT', None),
'WTF_CSRF_METHODS': ['GET', 'POST', 'PUT', 'DELETE'], 'WTF_CSRF_METHODS': ['GET', 'POST', 'PUT', 'DELETE'],
# Disable deliverable check for email addresss
'SECURITY_EMAIL_VALIDATOR_ARGS': config.SECURITY_EMAIL_VALIDATOR_ARGS
})) }))
security.init_app(app, user_datastore) security.init_app(app, user_datastore)

View File

@ -0,0 +1,62 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2021, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from pgadmin.utils.route import BaseTestGenerator
from pgadmin.setup import user_info
import config
from regression.python_test_utils.test_utils import module_patch
from unittest.mock import patch
from pgadmin.utils.constants import ENTER_EMAIL_ADDRESS
class EmailValidationOnSetup(BaseTestGenerator):
"""
This class tests the non-deliverability of email for invalid email id.
This test case is only responsible for testing non-deliverability emails
only.
"""
PPROMPT_RETURN_VALUE = '1234567'
scenarios = [
# scenario for testing invalid email for non-deliverability only
('TestCase for email validation', dict(
data=['postgres@local.dev', 'pg@pgadminrocks.com',
'me.pg@demo.dev', 'pg@123.pgcom',
'pg@postgres.local', 'postgres@pg.blah'],
check_deliverability=False,
)),
]
@patch('builtins.input')
@patch('os.environ')
def runTest(self, os_environ_mock, input_mock):
if config.SERVER_MODE is False:
self.skipTest(
"Can not email validation test cases in the DESKTOP mode."
)
os_environ_mock.return_value = []
config.CHECK_EMAIL_DELIVERABILITY = self.check_deliverability
with module_patch('pgadmin.setup.user_info.pprompt') as pprompt_mock:
pprompt_mock.return_value \
= self.PPROMPT_RETURN_VALUE, self.PPROMPT_RETURN_VALUE
for e in self.data:
input_mock.return_value = e
# skipping some setup-db part as we are only testing the
# mail validation through setup.
email, password = user_info()
input_mock.assert_called_once_with(ENTER_EMAIL_ADDRESS)
# assert equal means deliverability is not done, and entered
# email id is returned as it is.
self.assertEqual(e, email)
input_mock.reset_mock()

View File

@ -13,6 +13,7 @@ import random
import os import os
import re import re
import getpass import getpass
from pgadmin.utils.constants import ENTER_EMAIL_ADDRESS
from pgadmin.utils.validation_utils import validate_email from pgadmin.utils.validation_utils import validate_email
@ -27,6 +28,10 @@ def user_info_desktop():
return email, p1 return email, p1
def pprompt():
return getpass.getpass(), getpass.getpass('Retype password:')
def user_info_server(): def user_info_server():
print("NOTE: Configuring authentication for SERVER mode.\n") print("NOTE: Configuring authentication for SERVER mode.\n")
@ -45,13 +50,10 @@ def user_info_server():
"pgAdmin user account:\n" "pgAdmin user account:\n"
) )
email = input("Email address: ") email = input(ENTER_EMAIL_ADDRESS)
while not validate_email(email): while not validate_email(email):
print('Invalid email address. Please try again.') print('Invalid email address. Please try again.')
email = input("Email address: ") email = input(ENTER_EMAIL_ADDRESS)
def pprompt():
return getpass.getpass(), getpass.getpass('Retype password:')
p1, p2 = pprompt() p1, p2 = pprompt()
while p1 != p2 or len(p1) < 6: while p1 != p2 or len(p1) < 6:

View File

@ -0,0 +1,62 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2021, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from pgadmin.utils.route import BaseTestGenerator
from pgadmin.tools.user_management import validate_user
from unittest.mock import patch
import config
class TestValidateUser(BaseTestGenerator):
""" This class will test the user email validation with/without email
deliverability while validating user. """
scenarios = [
('User email validation (no deliverability)',
dict(
data=dict(
email='postgres@local.dev',
check_deliverability=False,
expected_data=dict(
test_result='postgres@local.dev'
)
)
)),
('User email validation (with deliverability)',
dict(
data=dict(
email='postgres@local.dev',
check_deliverability=True,
expected_data=dict(
test_result='Invalid email address.'
)
)
))
]
@patch('pgadmin.tools.user_management.validate_password')
def runTest(self, validate_password_mock):
if config.SERVER_MODE is False:
self.skipTest(
"Can not email validation test cases in the DESKTOP mode."
)
config.CHECK_EMAIL_DELIVERABILITY = self.data['check_deliverability']
ndata = {}
validate_password_mock.return_value.method.return_value = ''
try:
ndata = validate_user(self.data)
except Exception as e:
ndata['email'] = str(e.description)
# assert equal means deliverability is not done, and entered
# email id is returned as it is.
self.assertEqual(ndata['email'],
self.data['expected_data']['test_result'])

View File

@ -98,3 +98,5 @@ BINARY_PATHS = {
} }
UTILITIES_ARRAY = ['pg_dump', 'pg_dumpall', 'pg_restore', 'psql'] UTILITIES_ARRAY = ['pg_dump', 'pg_dumpall', 'pg_restore', 'psql']
ENTER_EMAIL_ADDRESS = "Email address: "

View File

@ -0,0 +1,82 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2021, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from pgadmin.utils.route import BaseTestGenerator
from pgadmin.utils.validation_utils import validate_email
from unittest.mock import patch
import config
class TestEmailValidate(BaseTestGenerator):
""" This class will test the email validation utility with or without email
deliverability. """
scenarios = [
('Email validation (no deliverability)',
dict(
data=dict(
email_list=['postgres@local.dev', 'pg@pgadminrocks.com',
'me.pg@demo.dev', 'pg@123.pgcom',
'pg@postgres.local', 'postgres@pg.blah',
'john@doe.com', 'punster@tr.co',
'admin@example.com'],
check_deliverability=False,
expected_data=dict(
test_result=True
)
)
)),
('Email validation (with deliverability)',
dict(
data=dict(
email_list=['postgres@local.dev', 'pg@pgadminrocks.com',
'pg@postgres.local'],
check_deliverability=True,
expected_data=dict(
test_result=False
)
)
)),
('Empty email validation (no deliverability)',
dict(
data=dict(
email_list=[''],
check_deliverability=False,
expected_data=dict(
test_result=False
)
)
)),
('Empty email validation (with deliverability)',
dict(
data=dict(
email_list=[''],
check_deliverability=True,
expected_data=dict(
test_result=False
)
)
))
]
def runTest(self):
if config.SERVER_MODE is False:
self.skipTest(
"Can not run email validation test cases in the DESKTOP mode."
)
config.CHECK_EMAIL_DELIVERABILITY = self.data['check_deliverability']
for e in self.data['email_list']:
result = validate_email(e)
# validate_email returns True if email is valid,
# even if non-deliverable. False if email is not valid or
# deliverability is turned ON.
self.assertEqual(result,
self.data['expected_data']['test_result'])

View File

@ -7,20 +7,21 @@
# #
########################################################################## ##########################################################################
import re from email_validator import validate_email as email_validate, \
EmailNotValidError
import config
def validate_email(email): def validate_email(email):
if email == '' or email is None: try:
# Validate.
valid = email_validate(
email, check_deliverability=config.CHECK_EMAIL_DELIVERABILITY)
# Update with the normalized form.
email = valid.email
return True
except EmailNotValidError as e:
# email is not valid, exception message is human-readable
print(str(e))
return False 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

View File

@ -17,6 +17,7 @@ import sqlite3
import shutil import shutil
from functools import partial from functools import partial
import random import random
import importlib
from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support.wait import WebDriverWait
from testtools.testcase import clone_test_with_new_id from testtools.testcase import clone_test_with_new_id
@ -1747,3 +1748,34 @@ def create_users_for_parallel_tests(tester):
user_id = json.loads(response.data.decode('utf-8'))['id'] user_id = json.loads(response.data.decode('utf-8'))['id']
user_details['user_id'] = user_id user_details['user_id'] = user_id
return user_details return user_details
def module_patch(*args):
"""
This is a helper function responsible to import a function inside
a module with the same name
e.g. user_info module has user_info function in it.
:param args:
:return:
"""
target = args[0]
components = target.split('.')
from unittest import mock
for i in range(len(components), 0, -1):
try:
# attempt to import the module
imported = importlib.import_module('.'.join(components[:i]))
# module was imported, let's use it in the patch
patch = mock.patch(*args)
patch.getter = lambda: imported
patch.attribute = '.'.join(components[i:])
return patch
except Exception as exc:
pass
# did not find a module, just return the default mock
return mock.patch(*args)