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.

346 lines
10 KiB

  1. """Fixtures for tests."""
  2. import os
  3. import uuid
  4. import pytest
  5. from keycloak import KeycloakAdmin, KeycloakOpenID
  6. class KeycloakTestEnv(object):
  7. """Wrapper for test Keycloak connection configuration.
  8. :param host: Hostname
  9. :type host: str
  10. :param port: Port
  11. :type port: str
  12. :param username: Admin username
  13. :type username: str
  14. :param password: Admin password
  15. :type password: str
  16. """
  17. def __init__(
  18. self,
  19. host: str = os.environ["KEYCLOAK_HOST"],
  20. port: str = os.environ["KEYCLOAK_PORT"],
  21. username: str = os.environ["KEYCLOAK_ADMIN"],
  22. password: str = os.environ["KEYCLOAK_ADMIN_PASSWORD"],
  23. ):
  24. """Init method."""
  25. self.KEYCLOAK_HOST = host
  26. self.KEYCLOAK_PORT = port
  27. self.KEYCLOAK_ADMIN = username
  28. self.KEYCLOAK_ADMIN_PASSWORD = password
  29. @property
  30. def KEYCLOAK_HOST(self):
  31. """Hostname getter."""
  32. return self._KEYCLOAK_HOST
  33. @KEYCLOAK_HOST.setter
  34. def KEYCLOAK_HOST(self, value: str):
  35. """Hostname setter."""
  36. self._KEYCLOAK_HOST = value
  37. @property
  38. def KEYCLOAK_PORT(self):
  39. """Port getter."""
  40. return self._KEYCLOAK_PORT
  41. @KEYCLOAK_PORT.setter
  42. def KEYCLOAK_PORT(self, value: str):
  43. """Port setter."""
  44. self._KEYCLOAK_PORT = value
  45. @property
  46. def KEYCLOAK_ADMIN(self):
  47. """Admin username getter."""
  48. return self._KEYCLOAK_ADMIN
  49. @KEYCLOAK_ADMIN.setter
  50. def KEYCLOAK_ADMIN(self, value: str):
  51. """Admin username setter."""
  52. self._KEYCLOAK_ADMIN = value
  53. @property
  54. def KEYCLOAK_ADMIN_PASSWORD(self):
  55. """Admin password getter."""
  56. return self._KEYCLOAK_ADMIN_PASSWORD
  57. @KEYCLOAK_ADMIN_PASSWORD.setter
  58. def KEYCLOAK_ADMIN_PASSWORD(self, value: str):
  59. """Admin password setter."""
  60. self._KEYCLOAK_ADMIN_PASSWORD = value
  61. @pytest.fixture
  62. def env():
  63. """Fixture for getting the test environment configuration object."""
  64. return KeycloakTestEnv()
  65. @pytest.fixture
  66. def admin(env: KeycloakTestEnv):
  67. """Fixture for initialized KeycloakAdmin class."""
  68. return KeycloakAdmin(
  69. server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
  70. username=env.KEYCLOAK_ADMIN,
  71. password=env.KEYCLOAK_ADMIN_PASSWORD,
  72. )
  73. @pytest.fixture
  74. def oid(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin):
  75. """Fixture for initialized KeycloakOpenID class."""
  76. # Set the realm
  77. admin.realm_name = realm
  78. # Create client
  79. client = str(uuid.uuid4())
  80. client_id = admin.create_client(
  81. payload={
  82. "name": client,
  83. "clientId": client,
  84. "enabled": True,
  85. "publicClient": True,
  86. "protocol": "openid-connect",
  87. }
  88. )
  89. # Return OID
  90. yield KeycloakOpenID(
  91. server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
  92. realm_name=realm,
  93. client_id=client,
  94. )
  95. # Cleanup
  96. admin.delete_client(client_id=client_id)
  97. @pytest.fixture
  98. def oid_with_credentials(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin):
  99. """Fixture for an initialized KeycloakOpenID class and a random user credentials."""
  100. # Set the realm
  101. admin.realm_name = realm
  102. # Create client
  103. client = str(uuid.uuid4())
  104. secret = str(uuid.uuid4())
  105. client_id = admin.create_client(
  106. payload={
  107. "name": client,
  108. "clientId": client,
  109. "enabled": True,
  110. "publicClient": False,
  111. "protocol": "openid-connect",
  112. "secret": secret,
  113. "clientAuthenticatorType": "client-secret",
  114. }
  115. )
  116. # Create user
  117. username = str(uuid.uuid4())
  118. password = str(uuid.uuid4())
  119. user_id = admin.create_user(
  120. payload={
  121. "username": username,
  122. "email": f"{username}@test.test",
  123. "enabled": True,
  124. "credentials": [{"type": "password", "value": password}],
  125. }
  126. )
  127. yield (
  128. KeycloakOpenID(
  129. server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
  130. realm_name=realm,
  131. client_id=client,
  132. client_secret_key=secret,
  133. ),
  134. username,
  135. password,
  136. )
  137. # Cleanup
  138. admin.delete_client(client_id=client_id)
  139. admin.delete_user(user_id=user_id)
  140. @pytest.fixture
  141. def oid_with_credentials_authz(env: KeycloakTestEnv, realm: str, admin: KeycloakAdmin):
  142. """Fixture for an initialized KeycloakOpenID class and a random user credentials."""
  143. # Set the realm
  144. admin.realm_name = realm
  145. # Create client
  146. client = str(uuid.uuid4())
  147. secret = str(uuid.uuid4())
  148. client_id = admin.create_client(
  149. payload={
  150. "name": client,
  151. "clientId": client,
  152. "enabled": True,
  153. "publicClient": False,
  154. "protocol": "openid-connect",
  155. "secret": secret,
  156. "clientAuthenticatorType": "client-secret",
  157. "authorizationServicesEnabled": True,
  158. "serviceAccountsEnabled": True,
  159. }
  160. )
  161. admin.create_client_authz_role_based_policy(
  162. client_id=client_id,
  163. payload={
  164. "name": "test-authz-rb-policy",
  165. "roles": [{"id": admin.get_realm_role(role_name="offline_access")["id"]}],
  166. },
  167. )
  168. # Create user
  169. username = str(uuid.uuid4())
  170. password = str(uuid.uuid4())
  171. user_id = admin.create_user(
  172. payload={
  173. "username": username,
  174. "email": f"{username}@test.test",
  175. "enabled": True,
  176. "credentials": [{"type": "password", "value": password}],
  177. }
  178. )
  179. yield (
  180. KeycloakOpenID(
  181. server_url=f"http://{env.KEYCLOAK_HOST}:{env.KEYCLOAK_PORT}",
  182. realm_name=realm,
  183. client_id=client,
  184. client_secret_key=secret,
  185. ),
  186. username,
  187. password,
  188. )
  189. # Cleanup
  190. admin.delete_client(client_id=client_id)
  191. admin.delete_user(user_id=user_id)
  192. @pytest.fixture
  193. def realm(admin: KeycloakAdmin) -> str:
  194. """Fixture for a new random realm."""
  195. realm_name = str(uuid.uuid4())
  196. admin.create_realm(payload={"realm": realm_name, "enabled": True})
  197. yield realm_name
  198. admin.delete_realm(realm_name=realm_name)
  199. @pytest.fixture
  200. def user(admin: KeycloakAdmin, realm: str) -> str:
  201. """Fixture for a new random user."""
  202. admin.realm_name = realm
  203. username = str(uuid.uuid4())
  204. user_id = admin.create_user(payload={"username": username, "email": f"{username}@test.test"})
  205. yield user_id
  206. admin.delete_user(user_id=user_id)
  207. @pytest.fixture
  208. def group(admin: KeycloakAdmin, realm: str) -> str:
  209. """Fixture for a new random group."""
  210. admin.realm_name = realm
  211. group_name = str(uuid.uuid4())
  212. group_id = admin.create_group(payload={"name": group_name})
  213. yield group_id
  214. admin.delete_group(group_id=group_id)
  215. @pytest.fixture
  216. def client(admin: KeycloakAdmin, realm: str) -> str:
  217. """Fixture for a new random client."""
  218. admin.realm_name = realm
  219. client = str(uuid.uuid4())
  220. client_id = admin.create_client(payload={"name": client, "clientId": client})
  221. yield client_id
  222. admin.delete_client(client_id=client_id)
  223. @pytest.fixture
  224. def client_role(admin: KeycloakAdmin, realm: str, client: str) -> str:
  225. """Fixture for a new random client role."""
  226. admin.realm_name = realm
  227. role = str(uuid.uuid4())
  228. admin.create_client_role(client, {"name": role, "composite": False})
  229. yield role
  230. admin.delete_client_role(client, role)
  231. @pytest.fixture
  232. def composite_client_role(admin: KeycloakAdmin, realm: str, client: str, client_role: str) -> str:
  233. """Fixture for a new random composite client role."""
  234. admin.realm_name = realm
  235. role = str(uuid.uuid4())
  236. admin.create_client_role(client, {"name": role, "composite": True})
  237. role_repr = admin.get_client_role(client, client_role)
  238. admin.add_composite_client_roles_to_role(client, role, roles=[role_repr])
  239. yield role
  240. admin.delete_client_role(client, role)
  241. @pytest.fixture
  242. def selfsigned_cert():
  243. """Generates self signed certificate for a hostname, and optional IP addresses."""
  244. from cryptography import x509
  245. from cryptography.x509.oid import NameOID
  246. from cryptography.hazmat.primitives import hashes
  247. from cryptography.hazmat.backends import default_backend
  248. from cryptography.hazmat.primitives import serialization
  249. from cryptography.hazmat.primitives.asymmetric import rsa
  250. from datetime import datetime, timedelta
  251. import ipaddress
  252. hostname = "testcert"
  253. ip_addresses = None
  254. key = None
  255. # Generate our key
  256. if key is None:
  257. key = rsa.generate_private_key(
  258. public_exponent=65537,
  259. key_size=2048,
  260. backend=default_backend(),
  261. )
  262. name = x509.Name([
  263. x509.NameAttribute(NameOID.COMMON_NAME, hostname)
  264. ])
  265. # best practice seem to be to include the hostname in the SAN, which *SHOULD* mean COMMON_NAME is ignored.
  266. alt_names = [x509.DNSName(hostname)]
  267. # allow addressing by IP, for when you don't have real DNS (common in most testing scenarios
  268. if ip_addresses:
  269. for addr in ip_addresses:
  270. # openssl wants DNSnames for ips...
  271. alt_names.append(x509.DNSName(addr))
  272. # ... whereas golang's crypto/tls is stricter, and needs IPAddresses
  273. # note: older versions of cryptography do not understand ip_address objects
  274. alt_names.append(x509.IPAddress(ipaddress.ip_address(addr)))
  275. san = x509.SubjectAlternativeName(alt_names)
  276. # path_len=0 means this cert can only sign itself, not other certs.
  277. basic_contraints = x509.BasicConstraints(ca=True, path_length=0)
  278. now = datetime.utcnow()
  279. cert = (
  280. x509.CertificateBuilder()
  281. .subject_name(name)
  282. .issuer_name(name)
  283. .public_key(key.public_key())
  284. .serial_number(1000)
  285. .not_valid_before(now)
  286. .not_valid_after(now + timedelta(days=10*365))
  287. .add_extension(basic_contraints, False)
  288. .add_extension(san, False)
  289. .sign(key, hashes.SHA256(), default_backend())
  290. )
  291. cert_pem = cert.public_bytes(encoding=serialization.Encoding.PEM)
  292. key_pem = key.private_bytes(
  293. encoding=serialization.Encoding.PEM,
  294. format=serialization.PrivateFormat.TraditionalOpenSSL,
  295. encryption_algorithm=serialization.NoEncryption(),
  296. )
  297. return cert_pem, key_pem