Skip to content
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

Add a default roles at initialisation #2546

Merged
merged 13 commits into from
Jul 12, 2024
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
Loading