-
Notifications
You must be signed in to change notification settings - Fork 13
Allow pushing user-allocation membership to Keycloak #249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| #!/bin/bash | ||
|
|
||
| set -xe | ||
|
|
||
| sudo docker run -d --name keycloak \ | ||
| -e KEYCLOAK_ADMIN=admin \ | ||
| -e KEYCLOAK_ADMIN_PASSWORD=nomoresecret \ | ||
| -p 8080:8080 \ | ||
| -p 8443:8443 \ | ||
| quay.io/keycloak/keycloak:25.0 start-dev |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,3 +8,4 @@ python-keystoneclient | |
| python-novaclient | ||
| python-neutronclient | ||
| python-swiftclient | ||
| requests | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| import os | ||
| import functools | ||
|
|
||
| import requests | ||
|
|
||
|
|
||
| class KeyCloakAPIClient: | ||
| def __init__(self): | ||
| self.base_url = os.getenv("KEYCLOAK_BASE_URL") | ||
| self.realm = os.getenv("KEYCLOAK_REALM") | ||
| self.admin_user = os.getenv("KEYCLOAK_ADMIN_USER") | ||
| self.admin_password = os.getenv("KEYCLOAK_ADMIN_PASSWORD") | ||
| self.client_id = os.getenv("KEYCLOAK_CLIENT_ID", "admin-cli") | ||
|
|
||
| self.token_url = ( | ||
| f"{self.base_url}/realms/{self.realm}/protocol/openid-connect/token" | ||
| ) | ||
|
|
||
| @functools.cached_property | ||
| def api_client(self): | ||
| params = { | ||
| "grant_type": "password", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See if you can use the client credentials flow instead of admin password here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the current version of Keycloak being used for the integration test ( |
||
| "client_id": self.client_id, | ||
| "username": self.admin_user, | ||
| "password": self.admin_password, | ||
| "scope": "openid", | ||
| } | ||
| r = requests.post(self.token_url, data=params).json() | ||
| headers = { | ||
| "Authorization": ("Bearer %s" % r["access_token"]), | ||
| "Content-Type": "application/json", | ||
| } | ||
| session = requests.session() | ||
| session.headers.update(headers) | ||
| return session | ||
|
|
||
| def create_group(self, group_name): | ||
| url = f"{self.base_url}/admin/realms/{self.realm}/groups" | ||
| payload = {"name": group_name} | ||
| response = self.api_client.post(url, json=payload) | ||
|
|
||
| # If group already exists, ignore and move on | ||
| if response.status_code not in (201, 409): | ||
| response.raise_for_status() | ||
|
|
||
| def create_user(self, cf_username): | ||
| """Helper function to create user in Keycloak, for testing purposes only""" | ||
| url = f"{self.base_url}/admin/realms/{self.realm}/users" | ||
| payload = { | ||
| "username": cf_username, | ||
| "enabled": True, | ||
| "email": cf_username, | ||
| } | ||
| r = self.api_client.post(url, json=payload) | ||
| r.raise_for_status() | ||
|
|
||
| def get_group_id(self, group_name) -> str | None: | ||
| """Return None if group not found""" | ||
| query = f"search={group_name}&exact=true" | ||
| url = f"{self.base_url}/admin/realms/{self.realm}/groups?{query}" | ||
| r = self.api_client.get(url).json() | ||
| return r[0]["id"] if r else None | ||
|
|
||
| def get_user_id(self, cf_username) -> str | None: | ||
| """Return None if user not found""" | ||
| # TODO (Quan): Confirm that Coldfront usernames map to Keycloak emails, not email, or something else? | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| query = f"email={cf_username}&exact=true" | ||
| url = f"{self.base_url}/admin/realms/{self.realm}/users?{query}" | ||
| r = self.api_client.get(url).json() | ||
| return r[0]["id"] if r else None | ||
|
|
||
| def add_user_to_group(self, user_id, group_id): | ||
| url = f"{self.base_url}/admin/realms/{self.realm}/users/{user_id}/groups/{group_id}" | ||
| r = self.api_client.put(url) | ||
| r.raise_for_status() | ||
|
|
||
| def remove_user_from_group(self, user_id, group_id): | ||
| url = f"{self.base_url}/admin/realms/{self.realm}/users/{user_id}/groups/{group_id}" | ||
| r = self.api_client.delete(url) | ||
| r.raise_for_status() | ||
|
|
||
| def get_user_groups(self, user_id) -> list[str]: | ||
| url = f"{self.base_url}/admin/realms/{self.realm}/users/{user_id}/groups" | ||
| r = self.api_client.get(url) | ||
| r.raise_for_status() | ||
| return [group["name"] for group in r.json()] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@knikolla @naved001 The Openstack functional tests are failing because the plugin tries to add the
coldfront-swift-inituser to Openstack projects. The user is added to the cluster proejct, but since they're not registered on Keycloak, they're not added to the Keycloak group. This causesremove_role_from_user()insrc/coldfront_plugin_cloud/base.pyto raise an 404 error when it uses the Keycloak API to add a non-existant user to a group.This can be resolved if we allow
remove_role_from_user()to ignore if the user is not found, which was the agreed behavior forassign_role_on_user(). Is that acceptable?