You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

274 lines
9.4 KiB

  1. """Test module for KeycloakOpenID."""
  2. from unittest import mock
  3. import pytest
  4. from keycloak.authorization import Authorization
  5. from keycloak.authorization.permission import Permission
  6. from keycloak.authorization.policy import Policy
  7. from keycloak.authorization.role import Role
  8. from keycloak.connection import ConnectionManager
  9. from keycloak.exceptions import (
  10. KeycloakAuthenticationError,
  11. KeycloakDeprecationError,
  12. KeycloakRPTNotFound,
  13. )
  14. from keycloak.keycloak_admin import KeycloakAdmin
  15. from keycloak.keycloak_openid import KeycloakOpenID
  16. def test_keycloak_openid_init(env):
  17. """Test KeycloakOpenId's init method."""
  18. oid = KeycloakOpenID(
  19. server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
  20. realm_name="master",
  21. client_id="admin-cli",
  22. )
  23. assert oid.client_id == "admin-cli"
  24. assert oid.client_secret_key is None
  25. assert oid.realm_name == "master"
  26. assert isinstance(oid.connection, ConnectionManager)
  27. assert isinstance(oid.authorization, Authorization)
  28. def test_well_known(oid: KeycloakOpenID):
  29. """Test the well_known method."""
  30. res = oid.well_known()
  31. assert res is not None
  32. assert res != dict()
  33. for key in [
  34. "acr_values_supported",
  35. "authorization_encryption_alg_values_supported",
  36. "authorization_encryption_enc_values_supported",
  37. "authorization_endpoint",
  38. "authorization_signing_alg_values_supported",
  39. "backchannel_authentication_endpoint",
  40. "backchannel_authentication_request_signing_alg_values_supported",
  41. "backchannel_logout_session_supported",
  42. "backchannel_logout_supported",
  43. "backchannel_token_delivery_modes_supported",
  44. "check_session_iframe",
  45. "claim_types_supported",
  46. "claims_parameter_supported",
  47. "claims_supported",
  48. "code_challenge_methods_supported",
  49. "device_authorization_endpoint",
  50. "end_session_endpoint",
  51. "frontchannel_logout_session_supported",
  52. "frontchannel_logout_supported",
  53. "grant_types_supported",
  54. "id_token_encryption_alg_values_supported",
  55. "id_token_encryption_enc_values_supported",
  56. "id_token_signing_alg_values_supported",
  57. "introspection_endpoint",
  58. "introspection_endpoint_auth_methods_supported",
  59. "introspection_endpoint_auth_signing_alg_values_supported",
  60. "issuer",
  61. "jwks_uri",
  62. "mtls_endpoint_aliases",
  63. "pushed_authorization_request_endpoint",
  64. "registration_endpoint",
  65. "request_object_encryption_alg_values_supported",
  66. "request_object_encryption_enc_values_supported",
  67. "request_object_signing_alg_values_supported",
  68. "request_parameter_supported",
  69. "request_uri_parameter_supported",
  70. "require_pushed_authorization_requests",
  71. "require_request_uri_registration",
  72. "response_modes_supported",
  73. "response_types_supported",
  74. "revocation_endpoint",
  75. "revocation_endpoint_auth_methods_supported",
  76. "revocation_endpoint_auth_signing_alg_values_supported",
  77. "scopes_supported",
  78. "subject_types_supported",
  79. "tls_client_certificate_bound_access_tokens",
  80. "token_endpoint",
  81. "token_endpoint_auth_methods_supported",
  82. "token_endpoint_auth_signing_alg_values_supported",
  83. "userinfo_encryption_alg_values_supported",
  84. "userinfo_encryption_enc_values_supported",
  85. "userinfo_endpoint",
  86. "userinfo_signing_alg_values_supported",
  87. ]:
  88. assert key in res
  89. def test_auth_url(env, oid: KeycloakOpenID):
  90. """Test the auth_url method."""
  91. res = oid.auth_url(redirect_uri="http://test.test/*")
  92. assert (
  93. res
  94. == f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}/realms/{oid.realm_name}"
  95. + f"/protocol/openid-connect/auth?client_id={oid.client_id}&response_type=code"
  96. + "&redirect_uri=http://test.test/*"
  97. )
  98. def test_token(oid_with_credentials: tuple[KeycloakOpenID, str, str]):
  99. """Test the token method."""
  100. oid, username, password = oid_with_credentials
  101. token = oid.token(username=username, password=password)
  102. assert token == {
  103. "access_token": mock.ANY,
  104. "expires_in": 300,
  105. "not-before-policy": 0,
  106. "refresh_expires_in": 1800,
  107. "refresh_token": mock.ANY,
  108. "scope": mock.ANY,
  109. "session_state": mock.ANY,
  110. "token_type": "Bearer",
  111. }
  112. # Test with dummy totp
  113. token = oid.token(username=username, password=password, totp="123456")
  114. assert token == {
  115. "access_token": mock.ANY,
  116. "expires_in": 300,
  117. "not-before-policy": 0,
  118. "refresh_expires_in": 1800,
  119. "refresh_token": mock.ANY,
  120. "scope": mock.ANY,
  121. "session_state": mock.ANY,
  122. "token_type": "Bearer",
  123. }
  124. # Test with extra param
  125. token = oid.token(username=username, password=password, extra_param="foo")
  126. assert token == {
  127. "access_token": mock.ANY,
  128. "expires_in": 300,
  129. "not-before-policy": 0,
  130. "refresh_expires_in": 1800,
  131. "refresh_token": mock.ANY,
  132. "scope": mock.ANY,
  133. "session_state": mock.ANY,
  134. "token_type": "Bearer",
  135. }
  136. def test_exchange_token(
  137. oid_with_credentials: tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin
  138. ):
  139. """Test the exchange token method."""
  140. # Verify existing user
  141. oid, username, password = oid_with_credentials
  142. # Allow impersonation
  143. admin.realm_name = oid.realm_name
  144. admin.assign_client_role(
  145. user_id=admin.get_user_id(username=username),
  146. client_id=admin.get_client_id(client_name="realm-management"),
  147. roles=[
  148. admin.get_client_role(
  149. client_id=admin.get_client_id(client_name="realm-management"),
  150. role_name="impersonation",
  151. )
  152. ],
  153. )
  154. token = oid.token(username=username, password=password)
  155. assert oid.userinfo(token=token["access_token"]) == {
  156. "email": f"{username}@test.test",
  157. "email_verified": False,
  158. "preferred_username": username,
  159. "sub": mock.ANY,
  160. }
  161. # Exchange token with the new user
  162. new_token = oid.exchange_token(
  163. token=token["access_token"],
  164. client_id=oid.client_id,
  165. audience=oid.client_id,
  166. subject=username,
  167. )
  168. assert oid.userinfo(token=new_token["access_token"]) == {
  169. "email": f"{username}@test.test",
  170. "email_verified": False,
  171. "preferred_username": username,
  172. "sub": mock.ANY,
  173. }
  174. assert token != new_token
  175. def test_logout(oid_with_credentials):
  176. """Test logout."""
  177. oid, username, password = oid_with_credentials
  178. token = oid.token(username=username, password=password)
  179. assert oid.userinfo(token=token["access_token"]) != dict()
  180. assert oid.logout(refresh_token=token["refresh_token"]) == dict()
  181. with pytest.raises(KeycloakAuthenticationError):
  182. oid.userinfo(token=token["access_token"])
  183. def test_certs(oid: KeycloakOpenID):
  184. """Test certificates."""
  185. assert len(oid.certs()["keys"]) == 2
  186. def test_public_key(oid: KeycloakOpenID):
  187. """Test public key."""
  188. assert oid.public_key() is not None
  189. def test_entitlement(
  190. oid_with_credentials_authz: tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin
  191. ):
  192. """Test entitlement."""
  193. oid, username, password = oid_with_credentials_authz
  194. token = oid.token(username=username, password=password)
  195. resource_server_id = admin.get_client_authz_resources(
  196. client_id=admin.get_client_id(oid.client_id)
  197. )[0]["_id"]
  198. with pytest.raises(KeycloakDeprecationError):
  199. oid.entitlement(token=token["access_token"], resource_server_id=resource_server_id)
  200. def test_introspect(oid_with_credentials: tuple[KeycloakOpenID, str, str]):
  201. """Test introspect."""
  202. oid, username, password = oid_with_credentials
  203. token = oid.token(username=username, password=password)
  204. assert oid.introspect(token=token["access_token"])["active"]
  205. assert oid.introspect(
  206. token=token["access_token"], rpt="some", token_type_hint="requesting_party_token"
  207. ) == {"active": False}
  208. with pytest.raises(KeycloakRPTNotFound):
  209. oid.introspect(token=token["access_token"], token_type_hint="requesting_party_token")
  210. def test_decode_token(oid_with_credentials: tuple[KeycloakOpenID, str, str]):
  211. """Test decode token."""
  212. oid, username, password = oid_with_credentials
  213. token = oid.token(username=username, password=password)
  214. assert (
  215. oid.decode_token(
  216. token=token["access_token"],
  217. key="-----BEGIN PUBLIC KEY-----\n" + oid.public_key() + "\n-----END PUBLIC KEY-----",
  218. options={"verify_aud": False},
  219. )["preferred_username"]
  220. == username
  221. )
  222. def test_load_authorization_config(
  223. oid_with_credentials_authz: tuple[KeycloakOpenID, str, str], admin: KeycloakAdmin
  224. ):
  225. """Test load authorization config."""
  226. oid, username, password = oid_with_credentials_authz
  227. oid.load_authorization_config(path="tests/data/authz_settings.json")
  228. assert "test-authz-rb-policy" in oid.authorization.policies
  229. assert isinstance(oid.authorization.policies["test-authz-rb-policy"], Policy)
  230. assert len(oid.authorization.policies["test-authz-rb-policy"].roles) == 1
  231. assert isinstance(oid.authorization.policies["test-authz-rb-policy"].roles[0], Role)
  232. assert len(oid.authorization.policies["test-authz-rb-policy"].permissions) == 2
  233. assert isinstance(
  234. oid.authorization.policies["test-authz-rb-policy"].permissions[0], Permission
  235. )