Skip to content

Commit 0d5b65c

Browse files
committed
Do not lowercase User.names, keep the capital letters, but login by name is case-insensitive.
1 parent ebbe24b commit 0d5b65c

5 files changed

Lines changed: 135 additions & 90 deletions

File tree

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

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,14 @@ class TestUsers(base.BaseIamResourceTest):
3030

3131
USERS_ENDPOINT = "iam/users"
3232

33-
def test_create_user_success(self, user_api_noauth_client):
33+
@pytest.mark.parametrize("name", ["test", "Spider-Man"])
34+
def test_create_user_success(self, name, user_api_noauth_client):
3435
client = user_api_noauth_client()
3536

36-
user = client.create_user(username="test", password="test")
37+
user = client.create_user(username=name, password="test")
3738

3839
assert user["password"] == "*******"
39-
assert user["username"] == "test"
40+
assert user["username"] == name
4041

4142
def test_create_user_400_error(self, user_api_noauth_client):
4243
client = user_api_noauth_client()
@@ -182,24 +183,16 @@ def test_update_my_user_test1_auth_success(
182183
self, user_api_client, auth_test1_user
183184
):
184185
client = user_api_client(auth_test1_user)
185-
186-
result = client.update_user(
187-
auth_test1_user.uuid,
188-
username="testXXX",
189-
)
190-
191-
assert result["username"] == "testxxx"
186+
name = "testXXX"
187+
result = client.update_user(auth_test1_user.uuid, username=name)
188+
assert result["username"] == name
192189

193190
def test_update_other_user_test1_auth_forbidden(
194191
self, user_api_client, auth_test1_user, auth_test2_user
195192
):
196193
client = user_api_client(auth_test1_user)
197-
198194
with pytest.raises(bazooka_exc.ForbiddenError):
199-
client.update_user(
200-
auth_test2_user.uuid,
201-
username="testXXX",
202-
)
195+
client.update_user(auth_test2_user.uuid, username="testXXX")
203196

204197
def test_update_other_user_test1_auth_access(
205198
self, user_api_client, auth_test1_user, auth_test2_user
@@ -210,13 +203,9 @@ def test_update_other_user_test1_auth_access(
210203
c.PERMISSION_USER_WRITE_ALL,
211204
],
212205
)
213-
214-
result = client.update_user(
215-
auth_test2_user.uuid,
216-
username="testXXX",
217-
)
218-
219-
assert result["username"] == "testxxx"
206+
name = "testXXX"
207+
result = client.update_user(auth_test2_user.uuid, username=name)
208+
assert result["username"] == name
220209

221210
def test_update_my_user_update_password_test1_auth_bad_request(
222211
self, user_api_client, auth_test1_user
@@ -428,6 +417,26 @@ def test_fields_in_me_info_success(
428417
# auth by phone is not implemented yet
429418
),
430419
("invalid_grant_type", "username", pytest.raises(ValueError)),
420+
(
421+
c.GRANT_TYPE_PASSWORD,
422+
"login",
423+
pytest.raises(bazooka_exc.BadRequestError),
424+
),
425+
(
426+
c.GRANT_TYPE_PASSWORD,
427+
"email",
428+
pytest.raises(bazooka_exc.BadRequestError),
429+
),
430+
(
431+
c.GRANT_TYPE_PASSWORD_EMAIL,
432+
"username",
433+
pytest.raises(bazooka_exc.BadRequestError),
434+
),
435+
(
436+
c.GRANT_TYPE_PASSWORD_LOGIN,
437+
"username",
438+
pytest.raises(bazooka_exc.BadRequestError),
439+
),
431440
],
432441
)
433442
def test_auth_with_param(
@@ -439,7 +448,7 @@ def test_auth_with_param(
439448
auth_test1_user,
440449
):
441450
params = {
442-
"username": "dummy_username",
451+
"username": None,
443452
"password": auth_test1_user.password,
444453
"grant_type": grant_type,
445454
}
@@ -500,28 +509,24 @@ def test_auth_with_login(
500509
def test_auth_case_insensitivity(
501510
self, user_api, auth_test1_user, grant_type, use_email, field_name
502511
):
503-
# Set up email if needed
512+
user = iam_models.User.objects.get_one(
513+
filters={"uuid": auth_test1_user.uuid}
514+
)
515+
user.name = user.name.title()
516+
user.email = "Test1@mail.com"
517+
user.save()
518+
504519
if use_email:
505-
user = iam_models.User.objects.get_one(
506-
filters={"uuid": auth_test1_user.uuid}
507-
)
508-
user.email = "test1@mail.com"
509-
user.save()
510-
email_value = user.email.upper()
520+
login = user.email.upper()
521+
else:
522+
login = auth_test1_user.username.upper()
511523

512524
params = {
513-
"username": "dummy_username",
525+
"username": None,
514526
"password": auth_test1_user.password,
515527
"grant_type": grant_type,
516528
}
517-
if grant_type == c.GRANT_TYPE_PASSWORD_USERNAME:
518-
params[field_name] = auth_test1_user.username.upper()
519-
elif grant_type == c.GRANT_TYPE_PASSWORD_EMAIL:
520-
params[field_name] = email_value
521-
elif grant_type == c.GRANT_TYPE_PASSWORD_LOGIN:
522-
params[field_name] = (
523-
email_value if use_email else auth_test1_user.username.upper()
524-
)
529+
params[field_name] = login
525530

526531
# Test authentication
527532
auth = iam_clients.GenesisCoreAuth(**params)

genesis_core/user_api/iam/api/controllers.py

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from restalchemy.api import controllers
2727
from restalchemy.api import resources
2828
from restalchemy.common import contexts
29+
from restalchemy.common import exceptions as ra_e
2930
from restalchemy.dm import filters as ra_filters
3031
from restalchemy.openapi import utils as oa_utils
3132
import pyotp
@@ -537,13 +538,29 @@ def login(self, resource, user, password, **kwargs):
537538
@oa_utils.extend_schema(**oa_specs.OA_SPEC_GET_TOKEN_KWARGS)
538539
@actions.post
539540
def get_token(self, resource, grant_type, **kwargs):
540-
if grant_type in (
541-
c.GRANT_TYPE_PASSWORD,
542-
c.GRANT_TYPE_PASSWORD_USERNAME,
543-
c.GRANT_TYPE_PASSWORD_EMAIL,
544-
c.GRANT_TYPE_PASSWORD_PHONE,
545-
c.GRANT_TYPE_PASSWORD_LOGIN,
546-
):
541+
grant_type_map = {
542+
c.GRANT_TYPE_PASSWORD: (
543+
c.PARAM_USERNAME,
544+
resource.get_token_by_password,
545+
),
546+
c.GRANT_TYPE_PASSWORD_USERNAME: (
547+
c.PARAM_USERNAME,
548+
resource.get_token_by_password_username,
549+
),
550+
c.GRANT_TYPE_PASSWORD_EMAIL: (
551+
c.PARAM_EMAIL,
552+
resource.get_token_by_password_email,
553+
),
554+
c.GRANT_TYPE_PASSWORD_PHONE: (
555+
c.PARAM_PHONE,
556+
resource.get_token_by_password_phone,
557+
),
558+
c.GRANT_TYPE_PASSWORD_LOGIN: (
559+
c.PARAM_LOGIN,
560+
resource.get_token_by_password_login,
561+
),
562+
}
563+
if grant_type in grant_type_map:
547564
client_id = kwargs.get(
548565
c.PARAM_CLIENT_ID,
549566
self._req.headers.get(c.HEADER_CLIENT_ID, ""),
@@ -564,33 +581,12 @@ def get_token(self, resource, grant_type, **kwargs):
564581
otp_code=self._req.headers.get(c.HEADER_OTP_CODE, None),
565582
root_endpoint=resource.redirect_url,
566583
)
567-
if grant_type == c.GRANT_TYPE_PASSWORD:
568-
token = resource.get_token_by_password(
569-
username=kwargs.get(c.PARAM_USERNAME),
570-
**payload,
571-
)
572-
elif grant_type == c.GRANT_TYPE_PASSWORD_USERNAME:
573-
token = resource.get_token_by_password(
574-
username=kwargs.get(c.PARAM_USERNAME),
575-
**payload,
576-
)
577-
elif grant_type == c.GRANT_TYPE_PASSWORD_EMAIL:
578-
token = resource.get_token_by_password_email(
579-
email=kwargs.get(c.PARAM_EMAIL),
580-
**payload,
581-
)
582-
elif grant_type == c.GRANT_TYPE_PASSWORD_PHONE:
583-
token = resource.get_token_by_password_phone(
584-
phone=kwargs.get(c.PARAM_PHONE),
585-
**payload,
586-
)
587-
elif grant_type == c.GRANT_TYPE_PASSWORD_LOGIN:
588-
token = resource.get_token_by_password_login(
589-
login=kwargs.get(c.PARAM_LOGIN),
590-
**payload,
591-
)
592-
else:
593-
raise ValueError(f"Unexpected {grant_type=}")
584+
login_attr, token_getter = grant_type_map[grant_type]
585+
payload[login_attr] = kwargs.get(login_attr)
586+
if not payload[login_attr]:
587+
raise ra_e.ValidationErrorException()
588+
589+
token = token_getter(**payload)
594590
return token.get_response_body()
595591

596592
elif grant_type == c.GRANT_TYPE_REFRESH_TOKEN:

genesis_core/user_api/iam/dm/models.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,20 +1066,22 @@ def get_token_by_password(self, username, **kwargs):
10661066
"""
10671067
Get auth token by username + password (default approach).
10681068
"""
1069-
users_query = User.objects.get_all(
1070-
filters={"name": ra_filters.EQ(username)}
1069+
users_query = User.objects.query(
1070+
where_conditions="LOWER(name) = %s",
1071+
where_values=[username.lower()],
1072+
limit=1,
10711073
)
10721074
return self._get_token_by_password_and_smth(
10731075
users_query=users_query, **kwargs
10741076
)
10751077

1076-
def get_token_by_password_username(self, **kwargs):
1078+
def get_token_by_password_username(self, username, **kwargs):
10771079
"""
10781080
Get auth token by username + password.
10791081
This is just an alias for get_token_by_password_username,
10801082
to ensure consistency.
10811083
"""
1082-
return self.get_token_by_password(**kwargs)
1084+
return self.get_token_by_password(username, **kwargs)
10831085

10841086
def get_token_by_password_email(self, email, **kwargs):
10851087
"""
@@ -1092,7 +1094,7 @@ def get_token_by_password_email(self, email, **kwargs):
10921094
users_query=users_query, **kwargs
10931095
)
10941096

1095-
def get_token_by_password_phone(self, **kwargs):
1097+
def get_token_by_password_phone(self, phone, **kwargs):
10961098
"""
10971099
Get auth token by phone + password.
10981100
Will be added later.
@@ -1107,12 +1109,10 @@ def get_token_by_password_login(self, login, **kwargs):
11071109
- by username (if no "@")
11081110
- by phone [to be done later]
11091111
"""
1110-
lookup_field = "email" if "@" in login else "name"
1111-
filters = {lookup_field: ra_filters.EQ(login)}
1112-
users_query = User.objects.get_all(filters=filters)
1113-
return self._get_token_by_password_and_smth(
1114-
users_query=users_query, **kwargs
1115-
)
1112+
if "@" in login:
1113+
return self.get_token_by_password_email(login, **kwargs)
1114+
else:
1115+
return self.get_token_by_password_username(login, **kwargs)
11161116

11171117
def get_token_by_refresh_token(self, refresh_token, scope=None):
11181118
context = contexts.get_context()

genesis_core/user_api/iam/dm/types.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,6 @@ def min_length(self):
3838
def max_length(self):
3939
return self._max_length
4040

41-
def to_simple_type(self, value):
42-
return value.lower()
43-
44-
def from_simple_type(self, value):
45-
return value.lower()
46-
4741
def from_unicode(self, value):
4842
return self.from_simple_type(value)
4943

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 = ["0024-user_confirmation_code_made_at-added-435e66.py"]
24+
25+
@property
26+
def migration_id(self):
27+
return "41f4b995-d237-4b89-bec9-046a50de8d27"
28+
29+
@property
30+
def is_manual(self):
31+
return False
32+
33+
def upgrade(self, session):
34+
expression = """
35+
DROP INDEX IF EXISTS iam_users_name_idx;
36+
CREATE UNIQUE INDEX iam_users_name_lower_idx ON iam_users (
37+
LOWER(name)
38+
);
39+
"""
40+
session.execute(expression)
41+
42+
def downgrade(self, session):
43+
expression = """
44+
DROP INDEX IF EXISTS iam_users_name_lower_idx;
45+
CREATE UNIQUE INDEX iam_users_name_idx ON iam_users (name);
46+
"""
47+
session.execute(expression)
48+
49+
50+
migration_step = MigrationStep()

0 commit comments

Comments
 (0)