Skip to content

Commit

Permalink
Add a default roles at initialisation (#2546)
Browse files Browse the repository at this point in the history
Co-authored-by: Michał Krassowski <5832902+krassowski@users.noreply.github.com>
  • Loading branch information
aktech and krassowski authored Jul 12, 2024
1 parent 2a1b877 commit 4f8fc54
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,11 @@ def validate_scopes(self, role_scopes):
return []

async def _get_roles_with_attributes(self, roles: dict, client_id: str, token: str):
"""This fetches all roles by id to fetch there attributes."""
"""This fetches all roles by id to fetch their attributes."""
roles_rich = []
for role in roles:
# If this takes too much time, which isn't the case right now, we can
# also do multi-threaded requests
# also do multithreaded requests
role_rich = await self._fetch_api(
endpoint=f"roles-by-id/{role['id']}?client={client_id}", token=token
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,32 @@ module "jupyterhub-openid-client" {
"developer" = ["jupyterhub_developer", "dask_gateway_developer"]
"analyst" = ["jupyterhub_developer"]
}
client_roles = [
{
"name" : "allow-app-sharing-role",
"description" : "Allow app sharing for apps created via JupyterHub App Launcher (jhub-apps)",
"groups" : [],
"attributes" : {
# grants permissions to share server
# grants permissions to read other user's names
# grants permissions to read other groups' names
# The later two are required for sharing with a group or user
"scopes" : "shares,read:users:name,read:groups:name"
"component" : "jupyterhub"
}
},
{
"name" : "allow-read-access-to-services-role",
"description" : "Allow read access to services, such that they are visible on the home page e.g. conda-store",
# Adding it to analyst group such that it's applied to every user.
"groups" : ["analyst"],
"attributes" : {
# grants permissions to read services
"scopes" : "read:services",
"component" : "jupyterhub"
}
},
]
callback-url-paths = [
"https://${var.external-url}/hub/oauth_callback",
var.jupyterhub-logout-redirect-url
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ resource "keycloak_role" "main" {
description = each.key
}


data "keycloak_group" "main" {
for_each = var.role_mapping

Expand All @@ -117,3 +116,41 @@ resource "keycloak_group_roles" "group_roles" {

exhaustive = false
}

resource "keycloak_role" "default_client_roles" {
for_each = { for role in var.client_roles : role.name => role }
realm_id = var.realm_id
client_id = keycloak_openid_client.main.id
name = each.value.name
description = each.value.description
attributes = each.value.attributes
}

locals {
group_role_mapping = flatten([
for role_object in var.client_roles : [
for group_name in role_object.groups : {
group : group_name
role_name : role_object.name
}
]
])

client_roles_groups = toset([
for index, value in local.group_role_mapping : value.group
])
}

data "keycloak_group" "client_role_groups" {
for_each = local.client_roles_groups
realm_id = var.realm_id
name = each.value
}

resource "keycloak_group_roles" "assign_roles" {
for_each = { for idx, value in local.group_role_mapping : idx => value }
realm_id = var.realm_id
group_id = data.keycloak_group.client_role_groups[each.value.group].id
role_ids = [keycloak_role.default_client_roles[each.value.role_name].id]
exhaustive = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,14 @@ variable "jupyterlab_profiles_mapper" {
type = bool
default = false
}

variable "client_roles" {
description = "Create roles for the client and assign it to groups"
default = []
type = list(object({
name = string
description = string
groups = optional(list(string))
attributes = map(any)
}))
}
8 changes: 8 additions & 0 deletions tests/tests_deployment/keycloak_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ def create_keycloak_role(client_name: str, role_name: str, scopes: str, componen
)


def get_keycloak_client_roles(client_name):
keycloak_admin = get_keycloak_admin()
client_details = get_keycloak_client_details_by_name(
client_name=client_name, keycloak_admin=keycloak_admin
)
return keycloak_admin.get_client_roles(client_id=client_details["id"])


def delete_client_keycloak_test_roles(client_name):
keycloak_admin = get_keycloak_admin()
client_details = get_keycloak_client_details_by_name(
Expand Down
21 changes: 21 additions & 0 deletions tests/tests_deployment/test_jupyterhub_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from tests.tests_deployment.keycloak_utils import (
assign_keycloak_client_role_to_user,
create_keycloak_role,
get_keycloak_client_roles,
)
from tests.tests_deployment.utils import create_jupyterhub_token, get_jupyterhub_session

Expand All @@ -30,9 +31,29 @@ def test_jupyterhub_loads_roles_from_keycloak():
"grafana_developer",
"manage-account-links",
"view-profile",
# default roles
"allow-read-access-to-services-role",
}


@pytest.mark.filterwarnings("ignore::urllib3.exceptions.InsecureRequestWarning")
def test_default_user_role_scopes():
token_response = create_jupyterhub_token(note="get-default-scopes")
token_scopes = set(token_response.json()["scopes"])
assert "read:services" in token_scopes


@pytest.mark.filterwarnings(
"ignore:.*auto_refresh_token is deprecated:DeprecationWarning"
)
@pytest.mark.filterwarnings("ignore::urllib3.exceptions.InsecureRequestWarning")
def test_check_default_roles_added_in_keycloak():
client_roles = get_keycloak_client_roles(client_name="jupyterhub")
role_names = [role["name"] for role in client_roles]
assert "allow-app-sharing-role" in role_names
assert "allow-read-access-to-services-role" in role_names


@pytest.mark.parametrize(
"component,scopes,expected_scopes_difference",
(
Expand Down

0 comments on commit 4f8fc54

Please sign in to comment.