Skip to content

Commit

Permalink
Merge pull request #49 from fjelltopp/development
Browse files Browse the repository at this point in the history
Production deployment
  • Loading branch information
tomeksabala authored Sep 17, 2022
2 parents aad3715 + 45c098a commit 2bb23d8
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 73 deletions.
36 changes: 24 additions & 12 deletions ckanext/emailasusername/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import ckan.plugins.toolkit as toolkit
import ckan.logic.schema as schema
from ckan.lib.plugins import DefaultTranslation
from ckan.model import User
import ckan.model as model
from ckan.common import _
from ckanext.emailasusername.blueprint import emailasusername
from ckanext.emailasusername.logic import (
Expand All @@ -30,11 +30,11 @@ def update_config(self, config_):
toolkit.add_template_directory(config_, 'templates')
toolkit.add_public_directory(config_, 'public')
toolkit.add_resource('fanstatic', 'emailasusername')
schema.user_new_form_schema = emailasusername_new_user_schema
schema.user_new_form_schema = emailasusername_user_new_form_schema

def get_validators(self):
return {
'email_exists': email_exists,
'email_is_unique': email_is_unique,
'user_both_emails_entered': user_both_emails_entered,
'user_emails_match': user_emails_match
}
Expand All @@ -61,9 +61,9 @@ def get_helpers(self):


@schema.validator_args
def emailasusername_new_user_schema(
def emailasusername_user_new_form_schema(
unicode_safe, unicode_only, user_both_passwords_entered,
user_password_validator, user_passwords_match, email_exists,
user_password_validator, user_passwords_match, email_is_unique,
not_empty, email_validator):
emailasusername_schema = schema.default_user_schema()
emailasusername_schema['fullname'] = [
Expand All @@ -78,7 +78,7 @@ def emailasusername_new_user_schema(
]
emailasusername_schema['email'] = [unicode_safe, email_validator]
emailasusername_schema['email1'] = [
not_empty, unicode_safe, email_validator, email_exists
not_empty, unicode_safe, email_validator, email_is_unique
]
if helpers.config_require_user_email_input_confirmation():
emailasusername_schema['email1'] += [
Expand All @@ -88,13 +88,25 @@ def emailasusername_new_user_schema(
return emailasusername_schema


def email_exists(key, data, errors, context):
result = User.by_email(data[key])
if result:
errors[('email',)] = errors.get(key, [])
errors[('email',)] = [
_('An account is already registered to that email.')
def email_is_unique(key, data, errors, context):
if data.get(('state',)) != model.State.DELETED:

def is_me(user):
# Core logic taken from ckan.logic.validators.email_is_unique
return (user.name in (data.get(("name",)), data.get(("id",)))
or user.id == data.get(("id",)))

users_matching_email = model.User.by_email(data[key])
undeleted_users_matching_email = [
a for a in users_matching_email if a.state != model.State.DELETED
]
undeleted_users_matching_email_not_including_me = [
a for a in undeleted_users_matching_email if not is_me(a)
]
if undeleted_users_matching_email_not_including_me:
raise toolkit.Invalid(
_('An account is already registered to that email.')
)


def user_both_emails_entered(key, data, errors, context):
Expand Down
72 changes: 33 additions & 39 deletions ckanext/emailasusername/tests/test_authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,54 @@
import ckan.tests.factories
import ckanext.emailasusername.authenticator as authenticator
import pytest
import mock
from ckan.logic import ValidationError


@pytest.mark.usefixtures('clean_db', 'with_plugins')
@pytest.mark.usefixtures('mail_server')
class TestAuthenticator(object):

@mock.patch('ckanext.emailasusername.blueprint.h.flash_error')
def test_authenticate(self, flash_mock):
@pytest.mark.parametrize("identity, existing_user, result", [
({}, False, None),
({'login': 'tester', 'password': 'RandomPassword123'}, True, 'tester'),
({'login': '[email protected]', 'password': 'RandomPassword123'}, True, 'tester'),
({'login': '[email protected]', 'password': 'RandomPassword123'}, True, None),
({'login': 'testerwrong', 'password': 'RandomPassword123'}, True, None),
({'login': '[email protected]', 'password': 'wrongpassword'}, True, None)
], ids=[
"Empty credentials",
"Correct credentials with username",
"Correct credentials with email",
"Incorrect email",
"Incorrect username",
"Incorrect password"
])
def test_authenticate(self, existing_user, identity, result):
auth = authenticator.EmailAsUsernameAuthenticator()
if existing_user:
ckan.tests.factories.User(
name='tester',
email='[email protected]',
password='RandomPassword123'
)
assert auth.authenticate({}, identity) == result

def test_email_authentication_fails_if_multiple_accounts_share_email(self):
auth = authenticator.EmailAsUsernameAuthenticator()
response = auth.authenticate({}, {})
assert response is None
identity = {'login': 'tester', 'password': 'RandomPassword123'}
ckan.tests.factories.User(
name=identity['login'],
name='tester1',
email='[email protected]',
password=identity['password']
password='RandomPassword123'
)

# Test that a correct login returns the username
response = auth.authenticate({}, identity)
assert response == 'tester'

# Test that a correct email based login returns the username
identity['login'] = '[email protected]'
response = auth.authenticate({}, identity)
assert response == 'tester'

# Test that an incorrect email based login fails login
identity['login'] = '[email protected]'
response = auth.authenticate({}, identity)
assert response is None
flash_mock.assert_not_called()

# Test that login fails when two accounts registered with email exists
identity = {'login': 'tester2', 'password': 'RandomPassword123'}
email = '[email protected]'
try:
ckan.tests.factories.User(
name=identity['login'],
email=email,
password=identity['password']
name="tester2",
email="[email protected]",
password="RandomPassword123"
)
identity['login'] = email
identity = {'login': '[email protected]', 'password': 'RandomPassword123'}
response = auth.authenticate({}, identity)
assert response is None
flash_mock.assert_not_called()
except ValidationError as e:
# CKAN 2.9 does not allow users to have identical emails
assert e.error_summary['Email'] == 'The email address \'{}\' belongs to a registered user.'.format(email)

# Test that an incorrect password fails login
identity['password'] += '!'
response = auth.authenticate({}, identity)
assert response is None
flash_mock.assert_not_called()
assert "Email" in e.error_summary
62 changes: 43 additions & 19 deletions ckanext/emailasusername/tests/test_plugin.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"""Tests for plugin.py."""
import ckan.plugins
from ckan.plugins import toolkit
import ckan.model
import ckan.logic.schema
import ckan.tests.factories
from ckan.tests.factories import User
import ckan.tests.helpers
from ckan.lib.helpers import url_for
import ckanext.emailasusername.plugin as plugin
from ckanext.emailasusername.helpers import (
config_require_user_email_input_confirmation
)
from contextlib import nullcontext as does_not_raise
import logging
import pytest

Expand Down Expand Up @@ -81,24 +82,47 @@ def test_user_both_emails_entered(self):
plugin.user_both_emails_entered(key, data, errors, context)
assert len(errors[('email',)]) == 1

def test_email_exists(self):
# Test email exists validator for valid data
key = ('email',)
data = {('email',): '[email protected]'}
errors = {('email',): []}
context = {}
plugin.email_exists(key, data, errors, context)
assert errors, {('email',): []}

# Test email exists validator for invalid data
# i.e. a pre-existing account already exists with the given email
test_user_dict = ckan.tests.factories.User(
name='tester1',
email='[email protected]'
@pytest.mark.parametrize("existing_user, data, result", [
(
None,
{('email',): '[email protected]'},
does_not_raise()
), (
{'email': '[email protected]'},
{('email',): '[email protected]'},
pytest.raises(toolkit.Invalid)
), (
{'email': '[email protected]', 'state': 'deleted'},
{('email',): '[email protected]'},
does_not_raise()
), (
{'email': '[email protected]', 'state': 'deleted', 'name': 'tester1'},
{('email',): '[email protected]', 'state': 'active', ('name',): 'tester1'},
does_not_raise()
), (
{'email': '[email protected]', 'name': 'tester1'},
{('email',): '[email protected]', ('name',): 'tester1'},
does_not_raise()
), (
{'email': '[email protected]', 'name': 'tester1'},
{('email',): '[email protected]', ('name',): 'tester2'},
pytest.raises(toolkit.Invalid)
)
data = {('email',): test_user_dict['email']}
plugin.email_exists(key, data, errors, context)
assert len(errors[('email',)]) == 1
], ids=[
"creating user with a unique email address",
"creating user with an existing email address",
"creating user with an email address used by deleted user",
"updating user state to active",
"updating user without changing the email address",
"updating user with an existing email address"
])
def test_email_is_unique(self, existing_user, data, result):

if existing_user:
User(**existing_user)

with result:
plugin.email_is_unique(('email',), data, {('email',): []}, {})

def test_emailasusername_new_user_schema(self):
schema = ckan.logic.schema.user_new_form_schema()
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# http://packaging.python.org/en/latest/tutorial.html#version
version='1.0.0',
# python 2.7+, 3.6+
python_requires='>2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*',
version='1.1.0',
# python 3.6+
python_requires='>=3.6.*',
description='''Allows users to login with email as well as username.''',
long_description=long_description,
long_description_content_type='text/markdown',
Expand Down

0 comments on commit 2bb23d8

Please sign in to comment.