Skip to content

Commit 36844be

Browse files
author
Tome Petrovski
committed
Fix login: support email in user lookup
1 parent 95354a3 commit 36844be

File tree

6 files changed

+34
-23
lines changed

6 files changed

+34
-23
lines changed

ckanext/auth/logic/action.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from typing import TypedDict
55

66
import ckan.plugins.toolkit as tk
7-
from ckan import model, types
7+
from ckan import types
88
from ckan.lib import captcha
99
from ckan.logic import validate
1010

@@ -29,8 +29,7 @@ def auth_2fa_user_login(
2929
) -> LoginResponse:
3030
tk.check_access("auth_2fa_user_login", context, data_dict)
3131

32-
user = model.User.by_name(data_dict["login"])
33-
32+
user = utils.get_user_by_username_or_email(data_dict["login"])
3433
if not user:
3534
raise tk.ObjectNotFound("User not found")
3635

@@ -65,7 +64,7 @@ def auth_2fa_check_credentials(
6564
) -> LoginResponse:
6665
tk.check_access("auth_2fa_user_login", context, data_dict)
6766

68-
user = model.User.by_name(data_dict["login"])
67+
user = utils.get_user_by_username_or_email(data_dict["login"])
6968

7069
try:
7170
captcha.check_recaptcha(tk.request)

ckanext/auth/model.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from ckan.model.types import make_uuid
1919

2020
import ckanext.auth.config as auth_config
21+
from ckanext.auth import utils
2122
from ckanext.auth.exceptions import ReplayAttackError
2223

2324
log = logging.getLogger(__name__)
@@ -68,7 +69,7 @@ def get_for_user(cls, user_name: str) -> Self | None:
6869
6970
:raises ValueError if the user_name is not provided
7071
"""
71-
user = model.User.get(user_name)
72+
user = utils.get_user_by_username_or_email(user_name)
7273

7374
if not user:
7475
raise tk.ObjectNotFound("User not found")

ckanext/auth/tests/test_utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@
1616
@pytest.mark.usefixtures("with_plugins", "clean_db", "with_request_context")
1717
class TestSendVerificationCodeToUser:
1818
@mock.patch("ckan.lib.mailer.mail_user")
19-
def test_send_verification_code_to_user(self, mocker, app, user):
19+
def test_send_verification_code_to_user(self, mocker, user):
2020
mocker.return_value = True
2121
assert utils.send_verification_email_to_user(user["id"])
2222

2323
def test_user_does_not_exist(self):
2424
assert not utils.send_verification_email_to_user("xxx")
2525

26+
@mock.patch("ckan.lib.mailer.mail_user")
27+
def test_send_verification_code_to_user_with_email(self, mocker, user):
28+
mocker.return_value = True
29+
assert utils.send_verification_email_to_user(user["email"])
2630

2731
@pytest.mark.usefixtures("with_plugins", "clean_db")
2832
class TestGetEmailVerificationCode:

ckanext/auth/utils.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from ckan.lib.redis import connect_to_redis
1717
from ckan.views.user import next_page_or_default, rotate_token
1818

19-
from ckanext.auth import config
2019
from ckanext.auth import config as auth_config
2120
from ckanext.auth.exceptions import ReplayAttackError
2221
from ckanext.auth.model import UserSecret
@@ -84,8 +83,8 @@ def reset_all(cls) -> None:
8483
redis.delete(key)
8584

8685

87-
def send_verification_email_to_user(user_id: str) -> bool:
88-
user = model.User.get(user_id)
86+
def send_verification_email_to_user(user_reference: str) -> bool:
87+
user = get_user_by_username_or_email(user_reference)
8988

9089
if not user or not user.email:
9190
return False
@@ -96,7 +95,7 @@ def send_verification_email_to_user(user_id: str) -> bool:
9695
"site_url": tk.config["ckan.site_url"],
9796
"site_title": tk.config["ckan.site_title"],
9897
"user_name": user.display_name,
99-
"subject": tk._(config.get_2fa_subject()),
98+
"subject": tk._(auth_config.get_2fa_subject()),
10099
"body": f"Your verification code is: {code}",
101100
}
102101

@@ -138,23 +137,23 @@ def get_email_verification_code(user: model.User) -> str:
138137
return user_secret.get_code()
139138

140139

141-
def regenerate_user_secret(user_id: str) -> str:
140+
def regenerate_user_secret(user_reference: str) -> str:
142141
"""Regenerate the secret for a user.
143142
144143
Args:
145-
user_id (str): The id of the user
144+
user_reference (str): The user’s ID or email.
146145
147146
Returns:
148147
str: The new secret
149148
"""
150-
user = model.User.get(user_id)
149+
user = get_user_by_username_or_email(user_reference)
151150

152151
if not user:
153152
raise tk.ObjectNotFound("User not found")
154153

155154
user_secret = UserSecret.create_for_user(user.name)
156155

157-
log.debug("2FA: Rotated the 2fa secret for user %s", user_id)
156+
log.debug("2FA: Rotated the 2fa secret for user %s", user.id)
158157

159158
return cast(str, user_secret.secret)
160159

@@ -205,13 +204,16 @@ def authenticate(identity: IdentityDict) -> model.User | model.AnonymousUser | N
205204
if LoginManager.is_login_blocked(identity["login"]):
206205
return None
207206

208-
if LoginManager.get_user_login_attempts(identity["login"]) > config.get_2fa_max_attempts():
207+
if (
208+
LoginManager.get_user_login_attempts(identity["login"])
209+
> auth_config.get_2fa_max_attempts()
210+
):
209211
LoginManager.block_user_login(identity["login"])
210212

211213
if not ckan_auth_result:
212214
return LoginManager.log_user_login_attempt(identity["login"])
213215

214-
if not config.is_2fa_enabled():
216+
if not auth_config.is_2fa_enabled():
215217
LoginManager.reset_for_user(identity["login"])
216218
return ckan_auth_result
217219

@@ -254,3 +256,7 @@ def authenticate_totp(user_name: str) -> str | None:
254256
)
255257
else:
256258
return user_name if result else None
259+
260+
261+
def get_user_by_username_or_email(user_reference: str) -> model.User | None:
262+
return model.User.get(user_reference) or model.User.by_email(user_reference)

ckanext/auth/views.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import logging
4+
import contextlib
45
from collections.abc import Callable
56
from functools import wraps
67
from typing import Any, cast
@@ -9,7 +10,6 @@
910
from flask.views import MethodView
1011

1112
from ckan import model, types
12-
from ckan.lib import helpers
1313
from ckan.logic import parse_params
1414
from ckan.plugins import plugin_loaded
1515
from ckan.plugins import toolkit as tk
@@ -113,15 +113,17 @@ def _setup_totp_extra_vars(self, user_id: str) -> dict[str, Any]:
113113
def regenerate_secret(user_id: str):
114114
utils.regenerate_user_secret(user_id)
115115
tk.h.flash_success(tk._("Your 2FA secret has been regenerated."))
116-
return helpers.redirect_to("auth.configure_2fa", user_id=user_id)
116+
return tk.redirect_to("auth.configure_2fa", user_id=user_id)
117117

118118

119119
@auth.route("/send_verification_code", methods=["POST"])
120120
def send_verification_code() -> Response:
121-
user_name: str = tk.get_or_bust(dict(tk.request.form), "login")
121+
login: str = tk.get_or_bust(dict(tk.request.form), "login")
122+
123+
with contextlib.suppress(tk.ObjectNotFound):
124+
utils.regenerate_user_secret(login)
122125

123-
utils.regenerate_user_secret(user_name)
124-
success = utils.send_verification_email_to_user(user_name)
126+
success = utils.send_verification_email_to_user(login)
125127

126128
return jsonify(
127129
{
@@ -137,7 +139,6 @@ def init_qr_code() -> Response:
137139
user_name: str = tk.get_or_bust(dict(tk.request.form), "login")
138140

139141
secret = UserSecret.get_for_user(user_name)
140-
141142
if not secret:
142143
secret = UserSecret.create_for_user(user_name)
143144

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "ckanext-auth"
7-
version = "0.4.4"
7+
version = "0.4.5"
88
description = "2FA authentication for CKAN"
99
authors = [
1010
{name = "DataShades", email = "[email protected]"},

0 commit comments

Comments
 (0)