Skip to content

Commit

Permalink
feat: add fine-grained realm-wide client scope management
Browse files Browse the repository at this point in the history
Signed-off-by: GitHub <[email protected]>
  • Loading branch information
aslafy-z authored Nov 22, 2024
1 parent 1211823 commit a813fd3
Show file tree
Hide file tree
Showing 9 changed files with 385 additions and 2 deletions.
33 changes: 33 additions & 0 deletions docs/resources/openid_default_client_scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
page_title: "keycloak_openid_default_client_scope Resource"
---

# keycloak\_openid\_default\_client\_scope Resource

Allows for creating or removing Keycloak default client scopes from Realm that use the OpenID Connect protocol.

A default client scope will be assigned automatically to each new client.

## Example Usage

```hcl
resource "keycloak_realm" "realm" {
realm = "my-realm"
enabled = true
}
resource "keycloak_openid_client_scope" "openid_client_scope" {
realm_id = keycloak_realm.realm.id
name = "groups"
}
resource "keycloak_openid_default_client_scope" "openid_default_client_scope" {
realm_id = keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.client_scope.id
}
```

## Argument Reference

- `realm_id` - (Required) The realm this client scope belongs to.
- `client_scope_id` - (Required) The client scope to manage.
33 changes: 33 additions & 0 deletions docs/resources/openid_optional_client_scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
page_title: "keycloak_openid_optional_client_scope Resource"
---

# keycloak\_openid\_optional\_client\_scope Resource

Allows for creating or removing Keycloak optional client scopes from Realm that use the OpenID Connect protocol.

A optional client scope will be assigned automatically to each new client.

## Example Usage

```hcl
resource "keycloak_realm" "realm" {
realm = "my-realm"
enabled = true
}
resource "keycloak_openid_client_scope" "openid_client_scope" {
realm_id = keycloak_realm.realm.id
name = "groups"
}
resource "keycloak_openid_optional_client_scope" "openid_optional_client_scope" {
realm_id = keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.client_scope.id
}
```

## Argument Reference

- `realm_id` - (Required) The realm this client scope belongs to.
- `client_scope_id` - (Required) The client scope to manage.
31 changes: 31 additions & 0 deletions keycloak/openid_default_client_scope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package keycloak

import (
"context"
"fmt"
)

func (keycloakClient *KeycloakClient) GetOpenidRealmDefaultClientScope(ctx context.Context, realmId, clientScopeId string) (*OpenidClientScope, error) {
var clientScopes []OpenidClientScope

err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/default-default-client-scopes", realmId), &clientScopes, nil)
if err != nil {
return nil, err
}

for _, clientScope := range clientScopes {
if clientScope.Id == clientScopeId {
return &clientScope, nil
}
}

return nil, err
}

func (keycloakClient *KeycloakClient) PutOpenidRealmDefaultClientScope(ctx context.Context, realmId, clientScopeId string) error {
return keycloakClient.put(ctx, fmt.Sprintf("/realms/%s/default-default-client-scopes/%s", realmId, clientScopeId), nil)
}

func (keycloakClient *KeycloakClient) DeleteOpenidRealmDefaultClientScope(ctx context.Context, realmId, clientScopeId string) error {
return keycloakClient.delete(ctx, fmt.Sprintf("/realms/%s/default-default-client-scopes/%s", realmId, clientScopeId), nil)
}
31 changes: 31 additions & 0 deletions keycloak/openid_optional_client_scope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package keycloak

import (
"context"
"fmt"
)

func (keycloakClient *KeycloakClient) GetOpenidRealmOptionalClientScope(ctx context.Context, realmId, clientScopeId string) (*OpenidClientScope, error) {
var clientScopes []OpenidClientScope

err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/default-optional-client-scopes", realmId), &clientScopes, nil)
if err != nil {
return nil, err
}

for _, clientScope := range clientScopes {
if clientScope.Id == clientScopeId {
return &clientScope, nil
}
}

return nil, err
}

func (keycloakClient *KeycloakClient) PutOpenidRealmOptionalClientScope(ctx context.Context, realmId, clientScopeId string) error {
return keycloakClient.put(ctx, fmt.Sprintf("/realms/%s/default-optional-client-scopes/%s", realmId, clientScopeId), nil)
}

func (keycloakClient *KeycloakClient) DeleteOpenidRealmOptionalClientScope(ctx context.Context, realmId, clientScopeId string) error {
return keycloakClient.delete(ctx, fmt.Sprintf("/realms/%s/default-optional-client-scopes/%s", realmId, clientScopeId), nil)
}
2 changes: 2 additions & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
"keycloak_user_roles": resourceKeycloakUserRoles(),
"keycloak_openid_client": resourceKeycloakOpenidClient(),
"keycloak_openid_client_scope": resourceKeycloakOpenidClientScope(),
"keycloak_openid_default_client_scope": resourceKeycloakOpenidDefaultClientScope(),
"keycloak_openid_optional_client_scope": resourceKeycloakOpenidOptionalClientScope(),
"keycloak_ldap_user_federation": resourceKeycloakLdapUserFederation(),
"keycloak_ldap_user_attribute_mapper": resourceKeycloakLdapUserAttributeMapper(),
"keycloak_ldap_group_mapper": resourceKeycloakLdapGroupMapper(),
Expand Down
5 changes: 3 additions & 2 deletions provider/resource_keycloak_openid_client_scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package provider
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/keycloak/terraform-provider-keycloak/keycloak/types"
"strconv"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/keycloak/terraform-provider-keycloak/keycloak/types"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/keycloak/terraform-provider-keycloak/keycloak"
)
Expand Down
81 changes: 81 additions & 0 deletions provider/resource_keycloak_openid_default_client_scope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package provider

import (
"context"
"fmt"
"strings"

"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 resourceKeycloakOpenidDefaultClientScope() *schema.Resource {
return &schema.Resource{
CreateContext: resourceKeycloakOpenidDefaultClientScopeCreate,
ReadContext: resourceKeycloakOpenidDefaultClientScopesRead,
DeleteContext: resourceKeycloakOpenidDefaultClientScopeDelete,
Importer: &schema.ResourceImporter{
StateContext: resourceKeycloakOpenidDefaultClientScopeImport,
},

Schema: map[string]*schema.Schema{
"realm_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"client_scope_id": {
Type: schema.TypeString,
Required: true,
},
},
}
}

func resourceKeycloakOpenidDefaultClientScopeCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
clientScopeId := data.Get("client_scope_id").(string)

return diag.FromErr(keycloakClient.PutOpenidRealmDefaultClientScope(ctx, realmId, clientScopeId))
}

func resourceKeycloakOpenidDefaultClientScopesRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
clientScopeId := data.Get("client_scope_id").(string)

clientScope, err := keycloakClient.GetOpenidRealmDefaultClientScope(ctx, realmId, clientScopeId)
if err != nil {
return handleNotFoundError(ctx, err, data)
}

data.Set("client_scope_id", clientScope.Id)
data.Set("client_scope_name", clientScope.Name)

return nil
}

func resourceKeycloakOpenidDefaultClientScopeDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
clientScopeId := data.Get("client_scope_id").(string)

return diag.FromErr(keycloakClient.DeleteOpenidRealmDefaultClientScope(ctx, realmId, clientScopeId))
}

func resourceKeycloakOpenidDefaultClientScopeImport(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
parts := strings.Split(d.Id(), "/")
if len(parts) != 2 {
return nil, fmt.Errorf("Invalid import. Supported import formats: {{realmId}}/{{openidClientScopeId}}")
}

d.Set("realm_id", parts[0])
d.SetId(parts[1])

return []*schema.ResourceData{d}, nil
}
90 changes: 90 additions & 0 deletions provider/resource_keycloak_openid_default_client_scope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package provider

import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/terraform"

Check failure on line 5 in provider/resource_keycloak_openid_default_client_scope_test.go

View workflow job for this annotation

GitHub Actions / verify

no required module provides package github.com/hashicorp/terraform-plugin-sdk/terraform; to add it:
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"strings"
"testing"
)

func TestAccKeycloakDataSourceOpenidDefaultClientScope_basic(t *testing.T) {
t.Parallel()
clientId := acctest.RandomWithPrefix("tf-acc")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Steps: []resource.TestStep{
{
Config: testAccKeycloakOpenidDefaultClientScope_basic(clientId),
Check: testAccCheckKeycloakOpenidClientHasDefaultScope("keycloak_openid_default_client_scope"),
},
},
})
}

func testAccCheckKeycloakOpenidClientHasDefaultScope(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {

rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("resource not found: %s", resourceName)
}

realm := rs.Primary.Attributes["realm_id"]
clientScopeId := rs.Primary.Attributes["client_scope_id"]

var client string
if strings.HasPrefix(resourceName, "keycloak_openid_client") {
client = rs.Primary.Attributes["client_id"]
} else {
client = rs.Primary.ID
}

keycloakDefaultClientScopes, err := keycloakClient.GetOpenidClientDefaultScopes(realm, client)

if err != nil {
return err
}

var found = false
for _, keycloakDefaultScope := range keycloakDefaultClientScopes {
if keycloakDefaultScope.Id == clientScopeId {
found = true

break
}
}

if !found {
return fmt.Errorf("default scope %s is not assigned to client", clientScopeId)
}

return nil
}
}

func testAccKeycloakOpenidDefaultClientScope_basic(clientId string) string {
return fmt.Sprintf(`
resource "keycloak_realm" "realm" {
realm = "%s"
enabled = true
}
resource "keycloak_openid_client_scope" "openid_client_scope" {
realm_id = keycloak_realm.realm.id
name = "groups"
}
resource "keycloak_openid_default_client_scope" "openid_default_client_scope" {
realm_id = keycloak_realm.realm.id
client_scope_id = keycloak_openid_client_scope.client_scope.id
}
resource "keycloak_openid_client" "openid_client" {
realm_id = keycloak_realm.realm.id
client_id = "%s"
}
`, testAccRealm.Realm, clientId)
}
Loading

0 comments on commit a813fd3

Please sign in to comment.