-
Notifications
You must be signed in to change notification settings - Fork 330
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add support for configuring default and optional client scopes per re…
…alm via dedicated resources Signed-off-by: Philipp Böhm <[email protected]>
- Loading branch information
Showing
10 changed files
with
663 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
--- | ||
page_title: "keycloak_realm_default_client_scopes Resource" | ||
--- | ||
|
||
# keycloak\_realm\_default\_client\_scopes Resource | ||
|
||
Allows you to manage the set of default client scopes for a Keycloak realm, which are used when new clients are created. | ||
|
||
Note that this resource attempts to be an **authoritative** source over the default client scopes for a Keycloak realm, | ||
so any Keycloak defaults and manual adjustments will be overwritten. | ||
|
||
|
||
## Example Usage | ||
|
||
```hcl | ||
resource "keycloak_realm" "realm" { | ||
realm = "my-realm" | ||
enabled = true | ||
} | ||
resource "keycloak_openid_client_scope" "client_scope" { | ||
realm_id = keycloak_realm.realm.id | ||
name = "test-client-scope" | ||
} | ||
resource "keycloak_realm_default_client_scopes" "default_scopes" { | ||
realm_id = keycloak_realm.realm.id | ||
default_scopes = [ | ||
"profile", | ||
"email", | ||
"roles", | ||
"web-origins", | ||
keycloak_openid_client_scope.client_scope.name, | ||
] | ||
} | ||
``` | ||
|
||
## Argument Reference | ||
|
||
- `realm_id` - (Required) The realm this client and scopes exists in. | ||
- `default_scopes` - (Required) An array of default client scope names that should be used when creating new Keycloak clients. | ||
|
||
## Import | ||
|
||
This resource does not support import. Instead of importing, feel free to create this resource | ||
as if it did not already exist on the server. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
--- | ||
page_title: "keycloak_realm_optional_client_scopes Resource" | ||
--- | ||
|
||
# keycloak\_realm\_optional\_client\_scopes Resource | ||
|
||
Allows you to manage the set of optional client scopes for a Keycloak realm, which are used when new clients are created. | ||
|
||
Note that this resource attempts to be an **authoritative** source over the optional client scopes for a Keycloak realm, | ||
so any Keycloak defaults and manual adjustments will be overwritten. | ||
|
||
|
||
## Example Usage | ||
|
||
```hcl | ||
resource "keycloak_realm" "realm" { | ||
realm = "my-realm" | ||
enabled = true | ||
} | ||
resource "keycloak_openid_client_scope" "client_scope" { | ||
realm_id = keycloak_realm.realm.id | ||
name = "test-client-scope" | ||
} | ||
resource "keycloak_realm_optional_client_scopes" "optional_scopes" { | ||
realm_id = keycloak_realm.realm.id | ||
optional_scopes = [ | ||
"address", | ||
"phone", | ||
"offline_access", | ||
"microprofile-jwt", | ||
keycloak_openid_client_scope.client_scope.name | ||
] | ||
} | ||
``` | ||
|
||
## Argument Reference | ||
|
||
- `realm_id` - (Required) The realm this client and scopes exists in. | ||
- `optional_scopes` - (Required) An array of optional client scope names that should be used when creating new Keycloak clients. | ||
|
||
## Import | ||
|
||
This resource does not support import. Instead of importing, feel free to create this resource | ||
as if it did not already exist on the server. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package keycloak | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
func (keycloakClient *KeycloakClient) getRealmClientScopesOfType(ctx context.Context, realmId, t string) ([]*OpenidClientScope, error) { | ||
var scopes []*OpenidClientScope | ||
|
||
err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/default-%s-client-scopes", realmId, t), &scopes, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return scopes, nil | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) GetRealmDefaultClientScopes(ctx context.Context, realmId string) ([]*OpenidClientScope, error) { | ||
return keycloakClient.getRealmClientScopesOfType(ctx, realmId, "default") | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) GetRealmOptionalClientScopes(ctx context.Context, realmId string) ([]*OpenidClientScope, error) { | ||
return keycloakClient.getRealmClientScopesOfType(ctx, realmId, "optional") | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) resolveClientScopeNamesIntoIds(ctx context.Context, realmId string, scopeNames []string) ([]string, error) { | ||
var scopeIds []string | ||
var clientScopes []OpenidClientScope | ||
|
||
err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/client-scopes", realmId), &clientScopes, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
ScopeNames: | ||
for _, scopeName := range scopeNames { | ||
for _, clientScope := range clientScopes { | ||
if clientScope.Name == scopeName { | ||
scopeIds = append(scopeIds, clientScope.Id) | ||
continue ScopeNames | ||
} | ||
} | ||
|
||
return nil, errors.New(fmt.Sprintf("Client scope with name %s not found in realm %s", scopeName, realmId)) | ||
} | ||
|
||
return scopeIds, nil | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) resolveAndHandleClientScopes(ctx context.Context, realmId string, scopeNames []string, handler func(context.Context, string, string) error) error { | ||
scopeIds, err := keycloakClient.resolveClientScopeNamesIntoIds(ctx, realmId, scopeNames) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, scopeId := range scopeIds { | ||
if err := handler(ctx, realmId, scopeId); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) markClientScopeAs(ctx context.Context, realmId, scopeId, t string) error { | ||
return keycloakClient.put(ctx, fmt.Sprintf("/realms/%s/default-%s-client-scopes/%s", realmId, t, scopeId), nil) | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) MarkClientScopesAsRealmDefault(ctx context.Context, realmId string, scopeNames []string) error { | ||
return keycloakClient.resolveAndHandleClientScopes(ctx, realmId, scopeNames, func(ctx context.Context, realmId, scopeId string) error { | ||
return keycloakClient.markClientScopeAs(ctx, realmId, scopeId, "default") | ||
}) | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) MarkClientScopesAsRealmOptional(ctx context.Context, realmId string, scopeNames []string) error { | ||
return keycloakClient.resolveAndHandleClientScopes(ctx, realmId, scopeNames, func(ctx context.Context, realmId, scopeId string) error { | ||
return keycloakClient.markClientScopeAs(ctx, realmId, scopeId, "optional") | ||
}) | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) unmarkClientScopeAs(ctx context.Context, realmId, scopeId, t string) error { | ||
return keycloakClient.delete(ctx, fmt.Sprintf("/realms/%s/default-%s-client-scopes/%s", realmId, t, scopeId), nil) | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) UnmarkClientScopesAsRealmDefault(ctx context.Context, realmId string, scopeNames []string) error { | ||
return keycloakClient.resolveAndHandleClientScopes(ctx, realmId, scopeNames, func(ctx context.Context, realmId, scopeId string) error { | ||
return keycloakClient.unmarkClientScopeAs(ctx, realmId, scopeId, "default") | ||
}) | ||
} | ||
|
||
func (keycloakClient *KeycloakClient) UnmarkClientScopesAsRealmOptional(ctx context.Context, realmId string, scopeNames []string) error { | ||
return keycloakClient.resolveAndHandleClientScopes(ctx, realmId, scopeNames, func(ctx context.Context, realmId, scopeId string) error { | ||
return keycloakClient.unmarkClientScopeAs(ctx, realmId, scopeId, "optional") | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package provider | ||
|
||
import ( | ||
"context" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
"github.com/keycloak/terraform-provider-keycloak/keycloak" | ||
) | ||
|
||
func resourceKeycloakRealmDefaultClientScopes() *schema.Resource { | ||
return &schema.Resource{ | ||
CreateContext: resourceKeycloakRealmDefaultClientScopesReconcile, | ||
ReadContext: resourceKeycloakRealmDefaultClientScopesRead, | ||
DeleteContext: resourceKeycloakRealmDefaultClientScopesDelete, | ||
UpdateContext: resourceKeycloakRealmDefaultClientScopesReconcile, | ||
Schema: map[string]*schema.Schema{ | ||
"realm_id": { | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
"default_scopes": { | ||
Type: schema.TypeSet, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
Required: true, | ||
Set: schema.HashString, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceKeycloakRealmDefaultClientScopesRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
keycloakClient := meta.(*keycloak.KeycloakClient) | ||
|
||
realmId := data.Get("realm_id").(string) | ||
|
||
defaultClientScopes, err := keycloakClient.GetRealmDefaultClientScopes(ctx, realmId) | ||
if err != nil { | ||
return handleNotFoundError(ctx, err, data) | ||
} | ||
|
||
var scopeNames []string | ||
for _, clientScope := range defaultClientScopes { | ||
scopeNames = append(scopeNames, clientScope.Name) | ||
} | ||
|
||
data.Set("default_scopes", scopeNames) | ||
data.SetId(realmId) | ||
|
||
return nil | ||
} | ||
|
||
func resourceKeycloakRealmDefaultClientScopesReconcile(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
keycloakClient := meta.(*keycloak.KeycloakClient) | ||
|
||
realmId := data.Get("realm_id").(string) | ||
tfDefaultClientScopes := data.Get("default_scopes").(*schema.Set) | ||
|
||
keycloakDefaultClientScopes, err := keycloakClient.GetRealmDefaultClientScopes(ctx, realmId) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
var scopesToUnmark []string | ||
for _, keycloakDefaultClientScope := range keycloakDefaultClientScopes { | ||
// if this scope is a default client scope in keycloak and tf state, no update is required | ||
if tfDefaultClientScopes.Contains(keycloakDefaultClientScope.Name) { | ||
tfDefaultClientScopes.Remove(keycloakDefaultClientScope.Name) | ||
} else { | ||
// if this scope is marked as default in keycloak but not in tf state unmark it | ||
scopesToUnmark = append(scopesToUnmark, keycloakDefaultClientScope.Name) | ||
} | ||
} | ||
|
||
// unmark scopes that aren't in tf state | ||
err = keycloakClient.UnmarkClientScopesAsRealmDefault(ctx, realmId, scopesToUnmark) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
// mark scopes as default that exist in tf state but not in keycloak | ||
err = keycloakClient.MarkClientScopesAsRealmDefault(ctx, realmId, interfaceSliceToStringSlice(tfDefaultClientScopes.List())) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
|
||
data.SetId(realmId) | ||
|
||
return resourceKeycloakRealmDefaultClientScopesRead(ctx, data, meta) | ||
} | ||
|
||
func resourceKeycloakRealmDefaultClientScopesDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
keycloakClient := meta.(*keycloak.KeycloakClient) | ||
|
||
realmId := data.Get("realm_id").(string) | ||
defaultClientScopes := data.Get("default_scopes").(*schema.Set) | ||
|
||
return diag.FromErr(keycloakClient.UnmarkClientScopesAsRealmDefault(ctx, realmId, interfaceSliceToStringSlice(defaultClientScopes.List()))) | ||
} |
Oops, something went wrong.