Skip to content

Commit 74fba53

Browse files
author
maxim-lixakov
committed
[DOP-21482] - implement unit tests for KeycloakAuthProvider
1 parent fad630c commit 74fba53

File tree

8 files changed

+311
-17
lines changed

8 files changed

+311
-17
lines changed

.env.local

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,16 @@ export SYNCMASTER__CRYPTO_KEY=UBgPTioFrtH2unlC4XFDiGf5sYfzbdSf_VgiUSaQc94=
1818
# Postgres
1919
export SYNCMASTER__DATABASE__URL=postgresql+asyncpg://syncmaster:changeme@localhost:5432/syncmaster
2020

21-
# Auth
21+
# Keycloack Auth
22+
export SYNCMASTER__AUTH__SERVER_URL=http://keycloak:8080/
23+
export SYNCMASTER__AUTH__REALM_NAME=manually_created
24+
export SYNCMASTER__AUTH__CLIENT_ID=manually_created
25+
export SYNCMASTER__AUTH__CLIENT_SECRET=generated_by_keycloak
26+
export SYNCMASTER__AUTH__REDIRECT_URI=http://localhost:8000/auth/callback
27+
export SYNCMASTER__AUTH__SCOPE=email
28+
export SYNCMASTER__AUTH__PROVIDER=syncmaster.backend.providers.auth.keycloak_provider.KeycloakAuthProvider
29+
30+
# Dummy Auth
2231
export SYNCMASTER__AUTH__PROVIDER=syncmaster.backend.providers.auth.dummy_provider.DummyAuthProvider
2332
export SYNCMASTER__AUTH__ACCESS_TOKEN__SECRET_KEY=secret
2433

poetry.lock

Lines changed: 32 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ onetl = {extras = ["spark", "s3", "hdfs"], version = "^0.12.0"}
136136
faker = ">=28.4.1,<34.0.0"
137137
coverage = "^7.6.1"
138138
gevent = "^24.2.1"
139+
responses = "*"
139140

140141
[tool.poetry.group.dev.dependencies]
141142
mypy = "^1.11.2"

tests/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ def event_loop():
6464
loop.close()
6565

6666

67-
@pytest.fixture(scope="session")
68-
def settings():
69-
return Settings()
67+
@pytest.fixture(scope="session", params=[{}])
68+
def settings(request: pytest.FixtureRequest) -> Settings:
69+
return Settings.parse_obj(request.param)
7070

7171

7272
@pytest.fixture(scope="session")

tests/test_unit/test_auth/__init__.py

Whitespace-only changes.

tests/test_unit/test_auth/mocks/__init__.py

Whitespace-only changes.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import json
2+
from base64 import b64encode
3+
4+
import responses
5+
from cryptography.hazmat.primitives import serialization
6+
from cryptography.hazmat.primitives.asymmetric import rsa
7+
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
8+
from itsdangerous import TimestampSigner
9+
from jose import jwt
10+
11+
# copied from .env.docker as backend tries to send requests to corresponding
12+
KEYCLOAK_CONFIG = {
13+
"server_url": "http://keycloak:8080",
14+
"realm_name": "manually_created",
15+
"redirect_uri": "http://localhost:8000/v1/auth/callback",
16+
"client_secret": "generated_by_keycloak",
17+
"scope": "email",
18+
"client_id": "test-client",
19+
}
20+
# create private & public keys to emulate Keycloak signing
21+
PRIVATE_KEY = rsa.generate_private_key(
22+
public_exponent=65537,
23+
key_size=2048,
24+
)
25+
PRIVATE_PEM = PRIVATE_KEY.private_bytes(
26+
encoding=serialization.Encoding.PEM,
27+
format=serialization.PrivateFormat.PKCS8,
28+
encryption_algorithm=serialization.NoEncryption(),
29+
)
30+
PUBLIC_KEY = PRIVATE_KEY.public_key()
31+
32+
33+
def get_public_key_pem(public_key):
34+
public_pem = public_key.public_bytes(
35+
encoding=Encoding.PEM,
36+
format=PublicFormat.SubjectPublicKeyInfo,
37+
)
38+
public_pem_str = public_pem.decode("utf-8")
39+
public_pem_str = public_pem_str.replace("-----BEGIN PUBLIC KEY-----\n", "")
40+
public_pem_str = public_pem_str.replace("-----END PUBLIC KEY-----\n", "")
41+
public_pem_str = public_pem_str.replace("\n", "")
42+
return public_pem_str
43+
44+
45+
def create_session_cookie(payload: dict, session_secret_key: str) -> str:
46+
access_token = jwt.encode(payload, PRIVATE_PEM, algorithm="RS256")
47+
refresh_token = "mock_refresh_token"
48+
session_data = {
49+
"access_token": access_token,
50+
"refresh_token": refresh_token,
51+
}
52+
53+
signer = TimestampSigner(session_secret_key)
54+
json_bytes = json.dumps(session_data).encode("utf-8")
55+
base64_bytes = b64encode(json_bytes)
56+
signed_data = signer.sign(base64_bytes)
57+
return signed_data.decode("utf-8")
58+
59+
60+
def mock_keycloak_well_known(responses_mock):
61+
server_url = KEYCLOAK_CONFIG.get("server_url")
62+
realm_name = KEYCLOAK_CONFIG.get("realm_name")
63+
well_known_url = f"{server_url}/realms/{realm_name}/.well-known/openid-configuration"
64+
65+
responses_mock.add(
66+
responses.GET,
67+
well_known_url,
68+
json={
69+
"authorization_endpoint": f"{server_url}/realms/{realm_name}/protocol/openid-connect/auth",
70+
"token_endpoint": f"{server_url}/realms/{realm_name}/protocol/openid-connect/token",
71+
"userinfo_endpoint": f"{server_url}/realms/{realm_name}/protocol/openid-connect/userinfo",
72+
"end_session_endpoint": f"{server_url}/realms/{realm_name}/protocol/openid-connect/logout",
73+
"jwks_uri": f"{server_url}/realms/{realm_name}/protocol/openid-connect/certs",
74+
"issuer": f"{server_url}/realms/{realm_name}",
75+
},
76+
status=200,
77+
content_type="application/json",
78+
)
79+
80+
81+
def mock_keycloak_token_endpoint(responses_mock, access_token: str, refresh_token: str):
82+
server_url = KEYCLOAK_CONFIG.get("server_url")
83+
realm_name = KEYCLOAK_CONFIG.get("realm_name")
84+
token_url = f"{server_url}/realms/{realm_name}/protocol/openid-connect/token"
85+
86+
responses_mock.add(
87+
responses.POST,
88+
token_url,
89+
body=json.dumps(
90+
{
91+
"access_token": access_token,
92+
"refresh_token": refresh_token,
93+
"token_type": "bearer",
94+
"expires_in": 3600,
95+
},
96+
),
97+
status=200,
98+
content_type="application/json",
99+
)
100+
101+
102+
def mock_keycloak_realm(responses_mock):
103+
server_url = KEYCLOAK_CONFIG.get("server_url")
104+
realm_name = KEYCLOAK_CONFIG.get("realm_name")
105+
realm_url = f"{server_url}/realms/{realm_name}"
106+
107+
responses_mock.add(
108+
responses.GET,
109+
realm_url,
110+
json={
111+
"realm": realm_name,
112+
"public_key": get_public_key_pem(PUBLIC_KEY),
113+
"token-service": f"{server_url}/realms/{realm_name}/protocol/openid-connect/token",
114+
"account-service": f"{server_url}/realms/{realm_name}/account",
115+
},
116+
status=200,
117+
content_type="application/json",
118+
)

0 commit comments

Comments
 (0)