Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ def get_config_and_user() -> ResponseReturnValue | None:
g.user_domain_settings = default_domain_settings
else:
g.user_domain_settings = init_get_user_domain_settings(user)
inter = InterfaceAuthUser(process_config, system_settings, g.user_domain_settings)
inter = InterfaceAuthUser(process_config, system_settings, g.user_domain_settings, user=user)
creds_ok, new_user = inter.check_user_and_fill_info(user)
if not creds_ok:
return create_api_base_response(error=err.ERROR_USER_CREDS_NOT_VALID)
Expand Down
2 changes: 1 addition & 1 deletion app/api/v1/auth/AuthUserApi.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def init_admin_config() -> None:
process: ProcessSetting = g.process_settings
system_settings: dict = g.system_settings
default_domain: dict = g.default_domain_settings
interface_api = InterfaceAuthUser(process, system_settings, default_domain)
interface_api = InterfaceAuthUser(process, system_settings, default_domain, user=g.user)
g.inter = interface_api


Expand Down
5 changes: 4 additions & 1 deletion app/interface/auth/InterfaceAuthUser.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class InterfaceAuthUser:
Interface for user authentication
"""

def __init__(self, process: ProcessSetting, system: dict, default_domain: dict):
def __init__(self, process: ProcessSetting, system: dict, default_domain: dict, user: User):
system_settings = SystemSettingsObj(system[SystemSettings.subparent])
default_auth = AuthSettingsObj(default_domain[AuthSettings.subparent])

Expand All @@ -39,6 +39,7 @@ def __init__(self, process: ProcessSetting, system: dict, default_domain: dict):

self.process = process
self.default_domain = default_domain
self.user = user
self.module_auth = ModuleAuth(process, system_settings, default_auth, default_us_source)
self.module_user_profile = ModuleUserProfile(process, default_domain)
self._module_calendar: ModuleCalendar = ModuleCalendar(process)
Expand Down Expand Up @@ -91,6 +92,8 @@ def plain_login(self, data:dict) -> tuple[dict, int]:
"""
uid = data["username"]
password = data["password"]
if self.user.authenticated and self.user.uid == data.get("username"):
return create_api_base_response(None, err.ERROR_ALREADY_LOGGED_IN)

success, user, module_us = self._check_login(uid, password)

Expand Down
1 change: 1 addition & 0 deletions app/utils/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def __init__(self, c:str, m:str, h:int = HTTPStatus.INTERNAL_SERVER_ERROR):
ERROR_VALIDITY_TIME_BELOW_0 = E("S000206", "Voucher Validity Time Below 0", HTTPStatus.INTERNAL_SERVER_ERROR)
ERROR_USER_CREDS_NOT_VALID = E("S000207", "User credentials stores in session are not valid anymore", HTTPStatus.UNAUTHORIZED)
ERROR_LOGIN_FAILED = E("S000208", "Login Failed: Invalid Credentials", HTTPStatus.UNAUTHORIZED)
ERROR_ALREADY_LOGGED_IN = E("S000209", "User Is Already Logged In", HTTPStatus.CONFLICT)


#API
Expand Down
98 changes: 86 additions & 12 deletions tests/test_interface/test_auth/test_InterfaceAuthUser.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,15 @@ def create_personal_addressbook(self, user_uid, name="Personal contacts"):

class FakeUser:
"""Fake User object for testing."""
def __init__(self, uid, password):
def __init__(self, uid, password, authenticated=False):
self.uid = uid
self.password = password
self.mail = uid
self.source_id = "source1"
self.authenticated = authenticated


ANONYMOUS_USER = FakeUser("anonymous", "anonymous", authenticated=False)


class FakeModuleMail:
Expand Down Expand Up @@ -205,7 +209,8 @@ def test_get_login_mech_success(monkeypatch):
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}}
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}},
user=ANONYMOUS_USER
)

result, status_code = interface.get_login_mech(user_uid="testuser@example.com", redirect="/dashboard")
Expand All @@ -228,7 +233,8 @@ def test_get_login_mech_request_exception(monkeypatch):
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}}
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}},
user=ANONYMOUS_USER
)

result, status_code = interface.get_login_mech(user_uid="unknown@example.com", redirect="/dashboard")
Expand Down Expand Up @@ -257,7 +263,8 @@ def fake_us_class(sources):
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}}
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}},
user=ANONYMOUS_USER
)

data = {"username": "testuser@example.com", "password": "secret123"}
Expand Down Expand Up @@ -287,7 +294,8 @@ def fake_us_class(sources):
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}}
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}},
user=ANONYMOUS_USER
)

data = {"username": "testuser@example.com", "password": "wrong"}
Expand All @@ -296,6 +304,65 @@ def fake_us_class(sources):
assert status_code == 401


def test_plain_login_already_logged_in_same_user(monkeypatch):
"""Test that a login request from an already authenticated user for the same uid is short-circuited."""
fake_auth = FakeModuleAuth(None, None, None, None)
fake_profile = FakeModuleUserProfile(None, None)

fake_us_instance = FakeModuleUserSource({})
def fake_us_class(sources):
return fake_us_instance

patch_modules_on_interface(monkeypatch, fake_auth, fake_profile, fake_us_class)

logged_user = FakeUser("testuser@example.com", "secret123", authenticated=True)
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}},
user=logged_user
)

data = {"username": "testuser@example.com", "password": "secret123"}
result, status_code = interface.plain_login(data)

assert status_code == err.ERROR_ALREADY_LOGGED_IN.h
assert result["error_code"] == err.ERROR_ALREADY_LOGGED_IN.c
# No new session should have been created
assert fake_auth.get_user_and_domain_user_sources_args is None


def test_plain_login_already_logged_in_different_user_proceeds(monkeypatch):
"""Test that an authenticated user trying to login as someone else goes through the normal flow."""
fake_auth = FakeModuleAuth(None, None, None, None)
fake_user = FakeUser("otheruser@example.com", "secret123")
fake_auth.get_user_and_domain_user_sources_result = (fake_user, {"source1": {}})

fake_profile = FakeModuleUserProfile(None, None)
fake_profile.is_user_profile_present_result = True # User already exists

fake_us_instance = FakeModuleUserSource({})
def fake_us_class(sources):
return fake_us_instance

patch_modules_on_interface(monkeypatch, fake_auth, fake_profile, fake_us_class)

logged_user = FakeUser("testuser@example.com", "secret123", authenticated=True)
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}},
user=logged_user
)

data = {"username": "otheruser@example.com", "password": "secret123"}
result, status_code = interface.plain_login(data)

assert status_code == 200
assert result["data"]["voucher"] == "test-voucher-123"
assert fake_auth.get_user_and_domain_user_sources_args == ("otheruser@example.com", "secret123")


def test_plain_login_create_user_profile(monkeypatch):
"""Test plain login creates user profile and personal calendar on first login."""
fake_auth = FakeModuleAuth(None, None, None, None)
Expand All @@ -314,7 +381,8 @@ def fake_us_class(sources):
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}, "MAIL_SETTINGS": {"test": "value"}}
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}, "MAIL_SETTINGS": {"test": "value"}},
user=ANONYMOUS_USER
)

data = {"username": "newuser@example.com", "password": "secret123"}
Expand Down Expand Up @@ -351,7 +419,8 @@ def fake_us_class(sources):
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}, "MAIL_SETTINGS": {"test": "value"}}
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}, "MAIL_SETTINGS": {"test": "value"}},
user=ANONYMOUS_USER
)

data = {"username": "newuser@example.com", "password": "secret123"}
Expand Down Expand Up @@ -383,7 +452,8 @@ def fake_us_class(sources):
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}, "MAIL_SETTINGS": {"test": "value"}}
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}, "MAIL_SETTINGS": {"test": "value"}},
user=ANONYMOUS_USER
)

data = {"username": "newuser@example.com", "password": "secret123"}
Expand All @@ -407,7 +477,8 @@ def test_logout_success(monkeypatch):
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}}
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}},
user=ANONYMOUS_USER
)

result, status_code = interface.logout("fake-jwt-token")
Expand All @@ -427,7 +498,8 @@ def test_logout_empty_token(monkeypatch):
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}}
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}},
user=ANONYMOUS_USER
)

result, status_code = interface.logout("")
Expand All @@ -449,7 +521,8 @@ def test_logout_request_exception_returns_error_response(monkeypatch):
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}}
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}},
user=ANONYMOUS_USER
)

result, status_code = interface.logout("expired-jwt-token")
Expand All @@ -471,7 +544,8 @@ def test_logout_invalid_voucher_type_returns_error_response(monkeypatch):
interface = InterfaceAuthUser(
process={"test": "config"},
system={"SYSTEM_SETTINGS": {"test": "value"}},
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}}
default_domain={"AUTH_SETTINGS": {"test": "value"}, "USER_SOURCE": {}},
user=ANONYMOUS_USER
)

result, status_code = interface.logout(12345) # wrong type
Expand Down
Loading