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.

477 lines
13 KiB

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