Skip to content

Commit c54a517

Browse files
committed
Username field swapped with Login for auth-ing, 1st/last names removed from registration.
1 parent c6d38cb commit c54a517

8 files changed

Lines changed: 162 additions & 27 deletions

File tree

genesis_core/tests/functional/restapi/iam/test_users.py

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,55 @@ def test_create_user_space_login_400_error(self, user_api_noauth_client):
4848
with pytest.raises(bazooka_exc.BadRequestError):
4949
client.create_user(username=" ", password="test")
5050

51+
def test_create_user_without_first_last_name_success(
52+
self, user_api_noauth_client
53+
):
54+
client = user_api_noauth_client()
55+
for empty_name in ["", None]:
56+
name = f"test_no_names_{empty_name}".lower()
57+
user = client.create_user(
58+
username=name,
59+
password="password",
60+
first_name=empty_name,
61+
last_name=empty_name,
62+
)
63+
assert user["username"] == name
64+
assert user["first_name"] == ""
65+
assert user["last_name"] == ""
66+
67+
def test_update_user_clear_first_last_name_success(
68+
self, user_api_client, auth_test1_user
69+
):
70+
client = user_api_client(auth_test1_user)
71+
for empty_name in ["", None]:
72+
result = client.update_user(
73+
auth_test1_user.uuid,
74+
first_name=empty_name,
75+
last_name=empty_name,
76+
)
77+
assert result.get("first_name", None) == empty_name
78+
assert result.get("last_name", None) == empty_name
79+
80+
def test_me_endpoint_with_empty_names_success(
81+
self, user_api_client, auth_test1_user
82+
):
83+
client = user_api_client(auth_test1_user)
84+
85+
# First clear the names
86+
client.update_user(
87+
auth_test1_user.uuid,
88+
first_name="",
89+
last_name="",
90+
)
91+
92+
# Verify in /me endpoint
93+
result = client.get(
94+
auth_test1_user.get_me_url(client.endpoint),
95+
).json()
96+
97+
assert result["user"]["first_name"] == ""
98+
assert result["user"]["last_name"] == ""
99+
51100
def test_create_user_and_check_roles(
52101
self, user_api_client, auth_test1_user
53102
):
@@ -139,15 +188,6 @@ def test_update_my_user_test1_auth_success(
139188

140189
assert result["username"] == "testxxx"
141190

142-
def test_update_my_user_400_error(self, user_api_client, auth_test1_user):
143-
client = user_api_client(auth_test1_user)
144-
145-
with pytest.raises(bazooka_exc.BadRequestError):
146-
client.update_user(
147-
auth_test1_user.uuid,
148-
first_name="",
149-
)
150-
151191
def test_update_other_user_test1_auth_forbidden(
152192
self, user_api_client, auth_test1_user, auth_test2_user
153193
):
@@ -354,3 +394,36 @@ def test_fields_in_me_info_success(
354394
for field in user_has_only_fields:
355395
result["user"].pop(field)
356396
assert result["user"] == {}
397+
398+
def test_login_success(self, user_api_noauth_client, auth_test1_user):
399+
client = user_api_noauth_client()
400+
for login in [auth_test1_user.username, auth_test1_user.email]:
401+
response = client.login(
402+
login=login,
403+
password="test1",
404+
)
405+
assert "access_token" in response
406+
407+
def test_login_with_invalid_login_404_error(self, user_api_noauth_client):
408+
client = user_api_noauth_client()
409+
for login in ["nonexistent_user", "nonexistent@mail.com", "", " " * 8]:
410+
with pytest.raises(bazooka_exc.NotFoundError):
411+
client.login(login=login, password="password")
412+
413+
def test_login_with_wrong_password_400_error(
414+
self, user_api_noauth_client, auth_test1_user
415+
):
416+
client = user_api_noauth_client()
417+
for password in ["wrong_password", "", " " * 8]:
418+
with pytest.raises(bazooka_exc.BadRequestError):
419+
client.login(login=auth_test1_user.email, password=password)
420+
421+
def test_login_case_insensitive_username(
422+
self, user_api_noauth_client, auth_test1_user
423+
):
424+
client = user_api_noauth_client()
425+
response = client.login(
426+
login=auth_test1_user.username.upper(),
427+
password="test1",
428+
)
429+
assert "access_token" in response

genesis_core/tests/unit/user_api/iam/dm/test_types.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def test_instance(self):
3030
params=[
3131
("u", True),
3232
("用户123!", True),
33-
("test+user@domain.com", True),
33+
("test+user@domain.com", False),
3434
("a#b$c%&'*", True),
3535
("~underscore_", True),
3636
("john.doe{2023}", True),
@@ -42,7 +42,7 @@ def test_instance(self):
4242
("pipe|symbol", True),
4343
("tilde~wave", True),
4444
("dash-test", True),
45-
("123.45@domain", True),
45+
("123.45@domain", False),
4646
("أحمد_2023", True),
4747
("", False),
4848
(" space ", False),
@@ -56,8 +56,6 @@ def test_instance(self):
5656
("angle<tag", False),
5757
("comma,separated", False),
5858
("dash-", True), # Дефис в конце разрешен
59-
("@start-with", True), # @ в начале разрешен
60-
("user@", True), # @ в конце разрешен
6159
("a\nb", False),
6260
],
6361
)

genesis_core/user_api/iam/api/controllers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ def get_token(self, resource, grant_type, **kwargs):
546546
client_secret=client_secret,
547547
)
548548
token = resource.get_token_by_password(
549-
username=kwargs.get(c.PARAM_USERNAME),
549+
login=kwargs.get(c.PARAM_LOGIN),
550550
password=kwargs.get(c.PARAM_PASSWORD),
551551
scope=kwargs.get(c.PARAM_SCOPE, ""),
552552
ttl=kwargs.get(c.PARAM_TTL, None),

genesis_core/user_api/iam/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636

3737
# user creds in request
38-
PARAM_USERNAME = "username"
38+
PARAM_LOGIN = "login"
3939
PARAM_PASSWORD = "password"
4040
PARAM_SCOPE = "scope"
4141
PARAM_TTL = "ttl"

genesis_core/user_api/iam/dm/models.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,13 @@ class User(
169169
)
170170

171171
first_name = properties.property(
172-
types.Name(min_length=1, max_length=128),
173-
required=True,
172+
types.Name(min_length=0, max_length=128),
173+
default="",
174174
)
175175

176176
last_name = properties.property(
177-
types.Name(min_length=1, max_length=128),
178-
required=True,
177+
types.Name(min_length=0, max_length=128),
178+
default="",
179179
)
180180
surname = properties.property(
181181
types.Name(min_length=0, max_length=128),
@@ -990,7 +990,7 @@ def validate_client_creds(self, client_id, client_secret):
990990

991991
def get_token_by_password(
992992
self,
993-
username,
993+
login,
994994
password,
995995
scope=iam_c.PARAM_SCOPE_DEFAULT,
996996
ttl=None,
@@ -1011,9 +1011,9 @@ def get_token_by_password(
10111011
if refresh_ttl is not None
10121012
else Token.get_default_refresh_expiration_delta()
10131013
)
1014-
for user in User.objects.get_all(
1015-
filters={"name": ra_filters.EQ(username)}
1016-
):
1014+
lookup_field = "email" if "@" in login else "name"
1015+
filters = {lookup_field: ra_filters.EQ(login)}
1016+
for user in User.objects.get_all(filters=filters):
10171017
user.validate_secret(secret=password)
10181018
if user.otp_enabled and not user.validate_otp(otp_code):
10191019
raise iam_e.OTPInvalidCodeError()
@@ -1029,8 +1029,8 @@ def get_token_by_password(
10291029
)
10301030
token.insert()
10311031
return token
1032-
else:
1033-
raise iam_exceptions.UserNotFound(username=username)
1032+
1033+
raise iam_exceptions.UserNotFound(login=login)
10341034

10351035
def get_token_by_refresh_token(self, refresh_token, scope=None):
10361036
context = contexts.get_context()

genesis_core/user_api/iam/dm/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class Username(types.BaseCompiledRegExpType):
2323

2424
def __init__(self, min_length=1, max_length=128):
2525
pattern = re.compile(
26-
r"^[\w!#$%&\'*+/=?^_`{|}~.@-]+$",
26+
r"^[\w!#$%&\'*+/=?^_`{|}~.-]+$",
2727
flags=re.UNICODE,
2828
)
2929
super().__init__(pattern=pattern)

genesis_core/user_api/iam/exceptions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121

2222
class UserNotFound(exceptions.CommonNotFoundException):
23-
__template__ = "User with login {username} not found"
23+
__template__ = "User with login {login} not found"
2424

2525

2626
class CanNotSetOwner(exceptions.CommonForbiddenException, iam_exc.Forbidden):
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2016 Eugene Frolov <eugene@frolov.net.ru>
2+
#
3+
# All Rights Reserved.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
17+
from restalchemy.storage.sql import migrations
18+
19+
20+
class MigrationStep(migrations.AbstarctMigrationStep):
21+
22+
def __init__(self):
23+
self._depends = ["0021-dns-permissions-7adac7.py"]
24+
25+
@property
26+
def migration_id(self):
27+
return "fcf9b5f7-c351-43e4-af94-f73cef52472d"
28+
29+
@property
30+
def is_manual(self):
31+
return False
32+
33+
def upgrade(self, session):
34+
# Migration to make first_name and last_name nullable in iam_users table
35+
session.execute(
36+
"""
37+
ALTER TABLE "iam_users"
38+
ALTER COLUMN "first_name" DROP NOT NULL,
39+
ALTER COLUMN "last_name" DROP NOT NULL;
40+
"""
41+
)
42+
43+
def downgrade(self, session):
44+
# Reverse migration to make first_name and last_name NOT NULL with empty string default
45+
session.execute(
46+
"""
47+
-- First update any existing NULL values to empty string
48+
UPDATE "iam_users"
49+
SET "first_name" = ''
50+
WHERE "first_name" IS NULL;
51+
52+
UPDATE "iam_users"
53+
SET "last_name" = ''
54+
WHERE "last_name" IS NULL;
55+
56+
-- Then alter the columns to be NOT NULL
57+
ALTER TABLE "iam_users"
58+
ALTER COLUMN "first_name" SET NOT NULL,
59+
ALTER COLUMN "last_name" SET NOT NULL;
60+
"""
61+
)
62+
63+
64+
migration_step = MigrationStep()

0 commit comments

Comments
 (0)