Skip to content

Commit b1dca50

Browse files
committed
Version bumped: gcl_iam>=0.14.0
first/last_name is explicitly allowNone now Login and email fields are added for auth-ing, 1st/last names removed from registration.
1 parent 7e523ca commit b1dca50

11 files changed

Lines changed: 401 additions & 46 deletions

File tree

genesis_core/common/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626

2727
DEFAULT_USER_API_HOST = "127.0.0.1"
2828
DEFAULT_USER_API_PORT = 11010
29+
DEFAULT_ROOT_ENDPOINT = (
30+
f"http://{DEFAULT_USER_API_HOST}:{DEFAULT_USER_API_PORT}/v1/"
31+
)
2932

3033
DEFAULT_GLOBAL_SALT = "FOy/2kwwdn0ig1QOq7cestqe"
3134
DEFAULT_CLIENT_UUID = "00000000-0000-0000-0000-000000000000"

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

Lines changed: 167 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
# License for the specific language governing permissions and limitations
1515
# under the License.
1616
import uuid as sys_uuid
17+
from contextlib import nullcontext
1718

1819
import pytest
1920
from bazooka import exceptions as bazooka_exc
21+
from gcl_iam.tests.functional import clients as iam_clients
2022

2123
from genesis_core.common import constants as common_c
2224
from genesis_core.tests.functional.restapi.iam import base
@@ -48,6 +50,55 @@ def test_create_user_space_login_400_error(self, user_api_noauth_client):
4850
with pytest.raises(bazooka_exc.BadRequestError):
4951
client.create_user(username=" ", password="test")
5052

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

140191
assert result["username"] == "testxxx"
141192

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-
151193
def test_update_other_user_test1_auth_forbidden(
152194
self, user_api_client, auth_test1_user, auth_test2_user
153195
):
@@ -354,3 +396,119 @@ def test_fields_in_me_info_success(
354396
for field in user_has_only_fields:
355397
result["user"].pop(field)
356398
assert result["user"] == {}
399+
400+
@pytest.mark.parametrize(
401+
"grant_type, auth_param, expectation",
402+
[
403+
(c.GRANT_TYPE_PASSWORD, "username", nullcontext()),
404+
(c.GRANT_TYPE_PASSWORD_USERNAME, "username", nullcontext()),
405+
(c.GRANT_TYPE_PASSWORD_EMAIL, "email", nullcontext()),
406+
(
407+
c.GRANT_TYPE_PASSWORD_PHONE,
408+
"phone",
409+
pytest.raises(bazooka_exc.BaseHTTPException),
410+
# auth by phone is not implemented yet
411+
),
412+
("invalid_grant_type", "username", pytest.raises(ValueError)),
413+
],
414+
)
415+
def test_auth_with_param(
416+
self,
417+
grant_type,
418+
auth_param,
419+
expectation,
420+
user_api,
421+
auth_test1_user,
422+
):
423+
params = {
424+
"username": "dummy_username",
425+
"password": auth_test1_user.password,
426+
"grant_type": grant_type,
427+
}
428+
params[auth_param] = (getattr(auth_test1_user, auth_param, None),)
429+
auth = iam_clients.GenesisCoreAuth(**params)
430+
with expectation:
431+
client = iam_clients.GenesisCoreTestRESTClient(
432+
f"{user_api.get_endpoint()}v1/",
433+
auth,
434+
) # tries to authorise on init
435+
assert "access_token" in client._auth_cache
436+
437+
@pytest.mark.parametrize(
438+
"login, password, expectation",
439+
[
440+
("username", None, nullcontext()),
441+
("email", None, nullcontext()),
442+
("phone", None, pytest.raises(bazooka_exc.BaseHTTPException)),
443+
("username", "wrong", pytest.raises(bazooka_exc.BadRequestError)),
444+
("username", "", pytest.raises(bazooka_exc.BadRequestError)),
445+
("null", None, pytest.raises(bazooka_exc.NotFoundError)),
446+
],
447+
)
448+
def test_auth_with_login(
449+
self,
450+
login,
451+
password,
452+
expectation,
453+
user_api,
454+
auth_test1_user,
455+
):
456+
params = {
457+
"username": "dummy_username",
458+
"password": auth_test1_user.password,
459+
"grant_type": c.GRANT_TYPE_PASSWORD_LOGIN,
460+
"login": getattr(auth_test1_user, login, "doesnt_exist"),
461+
}
462+
if password is not None:
463+
params["password"] = password
464+
465+
auth = iam_clients.GenesisCoreAuth(**params)
466+
with expectation:
467+
client = iam_clients.GenesisCoreTestRESTClient(
468+
f"{user_api.get_endpoint()}v1/",
469+
auth,
470+
) # tries to authorise on init
471+
assert "access_token" in client._auth_cache
472+
473+
@pytest.mark.parametrize(
474+
"grant_type,use_email,field_name",
475+
[
476+
(c.GRANT_TYPE_PASSWORD_USERNAME, False, "username"),
477+
(c.GRANT_TYPE_PASSWORD_LOGIN, False, "login"),
478+
(c.GRANT_TYPE_PASSWORD_LOGIN, True, "login"),
479+
(c.GRANT_TYPE_PASSWORD_EMAIL, True, "email"),
480+
],
481+
)
482+
def test_auth_case_insensitivity(
483+
self, user_api, auth_test1_user, grant_type, use_email, field_name
484+
):
485+
# Set up email if needed
486+
if use_email:
487+
user = iam_models.User.objects.get_one(
488+
filters={"uuid": auth_test1_user.uuid}
489+
)
490+
user.email = "test1@mail.com"
491+
user.save()
492+
email_value = user.email.upper()
493+
494+
params = {
495+
"username": "dummy_username",
496+
"password": auth_test1_user.password,
497+
"grant_type": grant_type,
498+
}
499+
if grant_type == c.GRANT_TYPE_PASSWORD_USERNAME:
500+
params[field_name] = auth_test1_user.username.upper()
501+
elif grant_type == c.GRANT_TYPE_PASSWORD_EMAIL:
502+
params[field_name] = email_value
503+
elif grant_type == c.GRANT_TYPE_PASSWORD_LOGIN:
504+
params[field_name] = (
505+
email_value if use_email else auth_test1_user.username.upper()
506+
)
507+
508+
# Test authentication
509+
auth = iam_clients.GenesisCoreAuth(**params)
510+
client = iam_clients.GenesisCoreTestRESTClient(
511+
f"{user_api.get_endpoint()}v1/",
512+
auth,
513+
) # tries to authorise on init
514+
assert "access_token" in client._auth_cache

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: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,13 @@ def login(self, resource, user, password, **kwargs):
532532
@oa_utils.extend_schema(**oa_specs.OA_SPEC_GET_TOKEN_KWARGS)
533533
@actions.post
534534
def get_token(self, resource, grant_type, **kwargs):
535-
if grant_type == c.GRANT_TYPE_PASSWORD:
535+
if grant_type in (
536+
c.GRANT_TYPE_PASSWORD,
537+
c.GRANT_TYPE_PASSWORD_USERNAME,
538+
c.GRANT_TYPE_PASSWORD_EMAIL,
539+
c.GRANT_TYPE_PASSWORD_PHONE,
540+
c.GRANT_TYPE_PASSWORD_LOGIN,
541+
):
536542
client_id = kwargs.get(
537543
c.PARAM_CLIENT_ID,
538544
self._req.headers.get(c.HEADER_CLIENT_ID, ""),
@@ -545,16 +551,43 @@ def get_token(self, resource, grant_type, **kwargs):
545551
client_id=client_id,
546552
client_secret=client_secret,
547553
)
548-
token = resource.get_token_by_password(
549-
username=kwargs.get(c.PARAM_USERNAME),
554+
payload = dict(
550555
password=kwargs.get(c.PARAM_PASSWORD),
551556
scope=kwargs.get(c.PARAM_SCOPE, ""),
552557
ttl=kwargs.get(c.PARAM_TTL, None),
553558
refresh_ttl=kwargs.get(c.PARAM_REFRESH_TTL, None),
554559
otp_code=self._req.headers.get(c.HEADER_OTP_CODE, None),
555560
root_endpoint=resource.redirect_url,
556561
)
562+
if grant_type == c.GRANT_TYPE_PASSWORD:
563+
token = resource.get_token_by_password(
564+
username=kwargs.get(c.PARAM_USERNAME),
565+
**payload,
566+
)
567+
elif grant_type == c.GRANT_TYPE_PASSWORD_USERNAME:
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_EMAIL:
573+
token = resource.get_token_by_password_email(
574+
email=kwargs.get(c.PARAM_EMAIL),
575+
**payload,
576+
)
577+
elif grant_type == c.GRANT_TYPE_PASSWORD_PHONE:
578+
token = resource.get_token_by_password_phone(
579+
phone=kwargs.get(c.PARAM_PHONE),
580+
**payload,
581+
)
582+
elif grant_type == c.GRANT_TYPE_PASSWORD_LOGIN:
583+
token = resource.get_token_by_password_login(
584+
login=kwargs.get(c.PARAM_LOGIN),
585+
**payload,
586+
)
587+
else:
588+
raise ValueError(f"Unexpected {grant_type=}")
557589
return token.get_response_body()
590+
558591
elif grant_type == c.GRANT_TYPE_REFRESH_TOKEN:
559592
token = resource.get_token_by_refresh_token(
560593
refresh_token=kwargs.get("refresh_token"),

genesis_core/user_api/iam/api/openapi_specs.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# under the License.
1616

1717
from restalchemy.openapi import constants as oa_c
18+
from genesis_core.user_api.iam import constants as c
1819

1920

2021
responses = {}
@@ -133,7 +134,7 @@
133134
"properties": {
134135
"grant_type": {
135136
"type": "string",
136-
"enum": ["password"],
137+
"enum": [c.GRANT_TYPE_PASSWORD],
137138
},
138139
"client_id": {"type": "string"},
139140
"client_secret": {"type": "string"},
@@ -144,6 +145,52 @@
144145
"refresh_ttl": {"type": "string", "format": "float"},
145146
},
146147
},
148+
{
149+
"type": "object",
150+
"required": [
151+
"grant_type",
152+
"client_id",
153+
"client_secret",
154+
"email",
155+
"password",
156+
],
157+
"properties": {
158+
"grant_type": {
159+
"type": "string",
160+
"enum": [c.GRANT_TYPE_PASSWORD_EMAIL],
161+
},
162+
"client_id": {"type": "string"},
163+
"client_secret": {"type": "string"},
164+
"email": {"type": "string"},
165+
"password": {"type": "string"},
166+
"scope": {"type": "string"},
167+
"ttl": {"type": "number", "format": "float"},
168+
"refresh_ttl": {"type": "string", "format": "float"},
169+
},
170+
},
171+
{
172+
"type": "object",
173+
"required": [
174+
"grant_type",
175+
"client_id",
176+
"client_secret",
177+
"login",
178+
"password",
179+
],
180+
"properties": {
181+
"grant_type": {
182+
"type": "string",
183+
"enum": [c.GRANT_TYPE_PASSWORD_LOGIN],
184+
},
185+
"client_id": {"type": "string"},
186+
"client_secret": {"type": "string"},
187+
"login": {"type": "string"},
188+
"password": {"type": "string"},
189+
"scope": {"type": "string"},
190+
"ttl": {"type": "number", "format": "float"},
191+
"refresh_ttl": {"type": "string", "format": "float"},
192+
},
193+
},
147194
{
148195
"type": "object",
149196
"required": [

genesis_core/user_api/iam/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121

2222
# Grant Types
2323
GRANT_TYPE_PASSWORD = "password"
24+
GRANT_TYPE_PASSWORD_USERNAME = "username+password"
25+
GRANT_TYPE_PASSWORD_EMAIL = "email+password"
26+
GRANT_TYPE_PASSWORD_PHONE = "phone+password"
27+
GRANT_TYPE_PASSWORD_LOGIN = "login+password"
2428
GRANT_TYPE_REFRESH_TOKEN = "refresh_token"
2529

2630

@@ -36,6 +40,9 @@
3640

3741
# user creds in request
3842
PARAM_USERNAME = "username"
43+
PARAM_EMAIL = "email"
44+
PARAM_PHONE = "phone"
45+
PARAM_LOGIN = "login"
3946
PARAM_PASSWORD = "password"
4047
PARAM_SCOPE = "scope"
4148
PARAM_TTL = "ttl"

0 commit comments

Comments
 (0)