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

Role Scopes: Allow specifying client scope instead of client #253

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion example/roles.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ resource "keycloak_openid_client" "pet_api" {
access_type = "BEARER-ONLY"
}

// Optional client scope for mapping additional client role
resource "keycloak_openid_client_scope" "extended_pet_details" {
realm_id = "${keycloak_realm.roles_example.id}"
name = "extended-pet-details"
description = "Optional scope offering additional information when getting pets"
}

resource "keycloak_role" "pet_api_create_pet" {
name = "create-pet"
realm_id = "${keycloak_realm.roles_example.id}"
Expand Down Expand Up @@ -43,6 +50,20 @@ resource "keycloak_role" "pet_api_delete_pet" {
description = "Ability to delete a pet"
}

resource "keycloak_role" "pet_api_read_pet_details" {
name = "read-pet-with-details"
realm_id = "${keycloak_realm.roles_example.id}"
client_id = "${keycloak_openid_client.pet_api.id}"
description = "Ability to read / list pets with further details"
}

// Map a role from the "pet_api" api client to the "extended_pet_details" client scope
resource "keycloak_generic_client_role_mapper" "pet_api_read_pet_details_role_mapping" {
realm_id = "${keycloak_realm.roles_example.id}"
client_scope_id = "${keycloak_openid_client_scope.extended_pet_details.id}"
role_id = "${keycloak_role.pet_api_read_pet_details.id}"
}

resource "keycloak_role" "pet_api_admin" {
name = "admin"
realm_id = "${keycloak_realm.roles_example.id}"
Expand Down Expand Up @@ -76,6 +97,18 @@ resource "keycloak_openid_client" "pet_app" {
valid_redirect_uris = [
"http://localhost:5555/openid-callback",
]

// disable full scope, roles are assigned via keycloak_generic_client_role_mapper
full_scope_allowed = false
}

resource "keycloak_openid_client_optional_scopes" "pet_app_optional_scopes" {
realm_id = "${keycloak_realm.roles_example.id}"
client_id = "${keycloak_openid_client.pet_app.id}"

optional_scopes = [
"${keycloak_openid_client_scope.extended_pet_details.name}"
]
}

// The app will always need access to the API, so this audience should be used regardless of auth type
Expand All @@ -96,13 +129,37 @@ resource "keycloak_openid_hardcoded_role_protocol_mapper" "pet_app_pet_api_read_
role_id = "${keycloak_role.pet_api_read_pet.id}"
}

// Map a role from the "pet_api" api client to the "pet_app" consumer client
// Map all roles from the "pet_api" api client to the "pet_app" consumer client, read_pet_details comes via client scope
resource "keycloak_generic_client_role_mapper" "pet_app_pet_api_read_role_mapping" {
realm_id = "${keycloak_realm.roles_example.id}"
client_id = "${keycloak_openid_client.pet_app.id}"
role_id = "${keycloak_role.pet_api_read_pet.id}"
}

resource "keycloak_generic_client_role_mapper" "pet_app_pet_api_delete_role_mapping" {
realm_id = "${keycloak_realm.roles_example.id}"
client_id = "${keycloak_openid_client.pet_app.id}"
role_id = "${keycloak_role.pet_api_delete_pet.id}"
}

resource "keycloak_generic_client_role_mapper" "pet_app_pet_api_create_role_mapping" {
realm_id = "${keycloak_realm.roles_example.id}"
client_id = "${keycloak_openid_client.pet_app.id}"
role_id = "${keycloak_role.pet_api_create_pet.id}"
}

resource "keycloak_generic_client_role_mapper" "pet_app_pet_api_update_role_mapping" {
realm_id = "${keycloak_realm.roles_example.id}"
client_id = "${keycloak_openid_client.pet_app.id}"
role_id = "${keycloak_role.pet_api_update_pet.id}"
}

resource "keycloak_generic_client_role_mapper" "pet_app_pet_api_admin_role_mapping" {
realm_id = "${keycloak_realm.roles_example.id}"
client_id = "${keycloak_openid_client.pet_app.id}"
role_id = "${keycloak_role.pet_api_admin.id}"
}

// Users and groups

resource "keycloak_group" "pet_api_base" {
Expand Down Expand Up @@ -133,6 +190,7 @@ resource "keycloak_group_roles" "admin_roles" {

role_ids = [
"${keycloak_role.pet_api_read_pet.id}",
"${keycloak_role.pet_api_read_pet_details.id}",
"${keycloak_role.pet_api_delete_pet.id}",
"${keycloak_role.pet_api_create_pet.id}",
"${data.keycloak_role.realm_offline_access.id}",
Expand All @@ -145,6 +203,7 @@ resource "keycloak_group_roles" "front_desk_roles" {

role_ids = [
"${keycloak_role.pet_api_read_pet.id}",
"${keycloak_role.pet_api_read_pet_details.id}",
"${keycloak_role.pet_api_create_pet.id}",
"${data.keycloak_role.realm_offline_access.id}",
]
Expand Down
20 changes: 12 additions & 8 deletions keycloak/role_scope_mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import (
"fmt"
)

func roleScopeMappingUrl(realmId, clientId string, role *Role) string {
return fmt.Sprintf("/realms/%s/clients/%s/scope-mappings/clients/%s", realmId, clientId, role.ClientId)
func roleScopeMappingUrl(realmId, clientId string, clientScopeId string, role *Role) string {
if clientId != "" {
return fmt.Sprintf("/realms/%s/clients/%s/scope-mappings/clients/%s", realmId, clientId, role.ClientId)
} else {
return fmt.Sprintf("/realms/%s/client-scopes/%s/scope-mappings/clients/%s", realmId, clientScopeId, role.ClientId)
}
}

func (keycloakClient *KeycloakClient) CreateRoleScopeMapping(realmId string, clientId string, role *Role) error {
roleUrl := roleScopeMappingUrl(realmId, clientId, role)
func (keycloakClient *KeycloakClient) CreateRoleScopeMapping(realmId string, clientId string, clientScopeId string, role *Role) error {
roleUrl := roleScopeMappingUrl(realmId, clientId, clientScopeId, role)

_, _, err := keycloakClient.post(roleUrl, []Role{*role})
if err != nil {
Expand All @@ -19,8 +23,8 @@ func (keycloakClient *KeycloakClient) CreateRoleScopeMapping(realmId string, cli
return nil
}

func (keycloakClient *KeycloakClient) GetRoleScopeMapping(realmId string, clientId string, role *Role) (*Role, error) {
roleUrl := roleScopeMappingUrl(realmId, clientId, role)
func (keycloakClient *KeycloakClient) GetRoleScopeMapping(realmId string, clientId string, clientScopeId string, role *Role) (*Role, error) {
roleUrl := roleScopeMappingUrl(realmId, clientId, clientScopeId, role)
var roles []Role

err := keycloakClient.get(roleUrl, &roles, nil)
Expand All @@ -37,7 +41,7 @@ func (keycloakClient *KeycloakClient) GetRoleScopeMapping(realmId string, client
return nil, nil
}

func (keycloakClient *KeycloakClient) DeleteRoleScopeMapping(realmId string, clientId string, role *Role) error {
roleUrl := roleScopeMappingUrl(realmId, clientId, role)
func (keycloakClient *KeycloakClient) DeleteRoleScopeMapping(realmId string, clientId string, clientScopeId string, role *Role) error {
roleUrl := roleScopeMappingUrl(realmId, clientId, clientScopeId, role)
return keycloakClient.delete(roleUrl, nil)
}
44 changes: 31 additions & 13 deletions provider/resource_keycloak_generic_client_role_mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,30 @@ func resourceKeycloakGenericClientRoleMapper() *schema.Resource {

Schema: map[string]*schema.Schema{
"realm_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The realm id where the associated client or client scope exists.",
},
"client_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "The destination client of the client role. Cannot be used at the same time as client_scope_id.",
ConflictsWith: []string{"client_scope_id"},
},
"client_scope_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "The destination client scope of the client role. Cannot be used at the same time as client_id.",
ConflictsWith: []string{"client_id"},
},
"role_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Id of the role to assign",
},
},
}
Expand All @@ -38,19 +49,24 @@ func resourceKeycloakGenericClientRoleMapperCreate(data *schema.ResourceData, me

realmId := data.Get("realm_id").(string)
clientId := data.Get("client_id").(string)
clientScopeId := data.Get("client_scope_id").(string)
roleId := data.Get("role_id").(string)

role, err := keycloakClient.GetRole(realmId, roleId)
if err != nil {
return err
}

err = keycloakClient.CreateRoleScopeMapping(realmId, clientId, role)
err = keycloakClient.CreateRoleScopeMapping(realmId, clientId, clientScopeId, role)
if err != nil {
return err
}

data.SetId(fmt.Sprintf("%s/client/%s/scope-mappings/%s/%s", realmId, clientId, role.ClientId, role.Id))
if clientId != "" {
data.SetId(fmt.Sprintf("%s/client/%s/scope-mappings/%s/%s", realmId, clientId, role.ClientId, role.Id))
} else {
data.SetId(fmt.Sprintf("%s/client-scope/%s/scope-mappings/%s/%s", realmId, clientScopeId, role.ClientId, role.Id))
}

return resourceKeycloakGenericClientRoleMapperRead(data, meta)
}
Expand All @@ -60,14 +76,15 @@ func resourceKeycloakGenericClientRoleMapperRead(data *schema.ResourceData, meta

realmId := data.Get("realm_id").(string)
clientId := data.Get("client_id").(string)
clientScopeId := data.Get("client_scope_id").(string)
roleId := data.Get("role_id").(string)

role, err := keycloakClient.GetRole(realmId, roleId)
if err != nil {
return err
}

mappedRole, err := keycloakClient.GetRoleScopeMapping(realmId, clientId, role)
mappedRole, err := keycloakClient.GetRoleScopeMapping(realmId, clientId, clientScopeId, role)

if mappedRole == nil {
data.SetId("")
Expand All @@ -81,12 +98,13 @@ func resourceKeycloakGenericClientRoleMapperDelete(data *schema.ResourceData, me

realmId := data.Get("realm_id").(string)
clientId := data.Get("client_id").(string)
clientScopeId := data.Get("client_scope_id").(string)
roleId := data.Get("role_id").(string)

role, err := keycloakClient.GetRole(realmId, roleId)
if err != nil {
return err
}

return keycloakClient.DeleteRoleScopeMapping(realmId, clientId, role)
return keycloakClient.DeleteRoleScopeMapping(realmId, clientId, clientScopeId, role)
}
Loading