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.

238 lines
8.0 KiB

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