diff --git a/docs/resources/realm.md b/docs/resources/realm.md index d3bb7d7a..d71e9183 100644 --- a/docs/resources/realm.md +++ b/docs/resources/realm.md @@ -222,6 +222,11 @@ Each of these attributes are blocks with the following attributes: - `internal_id` - (Computed) When importing realms created outside of this terraform provider, they could use generated arbitrary IDs for the internal realm id. Realms created by this provider always use the realm's name for its internal id. +## Default Client Scopes + +- `default_default_client_scopes` - (Optional) A list of default default client scopes to be used for client definitions. Defaults to `[]` or keycloak's built-in default default client-scopes. +- `default_optional_client_scopes` - (Optional) A list of default optional client scopes to be used for client definitions. Defaults to `[]` or keycloak's built-in default optional client-scopes. + ## Import Realms can be imported using their name. diff --git a/keycloak/realm.go b/keycloak/realm.go index 52658b30..490d4c1c 100644 --- a/keycloak/realm.go +++ b/keycloak/realm.go @@ -75,6 +75,10 @@ type Realm struct { //extra attributes of a realm Attributes map[string]interface{} `json:"attributes"` + // client-scope mapping defaults + DefaultDefaultClientScopes []string `json:"defaultDefaultClientScopes,omitempty"` + DefaultOptionalClientScopes []string `json:"defaultOptionalClientScopes,omitempty"` + BrowserSecurityHeaders BrowserSecurityHeaders `json:"browserSecurityHeaders"` BruteForceProtected bool `json:"bruteForceProtected"` diff --git a/provider/data_source_keycloak_realm.go b/provider/data_source_keycloak_realm.go index fd3a87b3..d967918b 100644 --- a/provider/data_source_keycloak_realm.go +++ b/provider/data_source_keycloak_realm.go @@ -429,7 +429,22 @@ func dataSourceKeycloakRealm() *schema.Resource { Optional: true, Computed: true, }, - + // default default client scopes + "default_default_client_scopes": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + Optional: true, + ForceNew: false, + }, + // default optional client scopes + "default_optional_client_scopes": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + Optional: true, + ForceNew: false, + }, // WebAuthn "web_authn_policy": { Type: schema.TypeList, diff --git a/provider/resource_keycloak_realm.go b/provider/resource_keycloak_realm.go index b7111be1..e5ddc8ae 100644 --- a/provider/resource_keycloak_realm.go +++ b/provider/resource_keycloak_realm.go @@ -524,6 +524,22 @@ func resourceKeycloakRealm() *schema.Resource { Optional: true, }, + // default default client scopes + "default_default_client_scopes": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + ForceNew: false, + }, + + // default optional client scopes + "default_optional_client_scopes": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + ForceNew: false, + }, + // WebAuthn "web_authn_policy": { Type: schema.TypeList, @@ -857,6 +873,22 @@ func getRealmFromData(data *schema.ResourceData) (*keycloak.Realm, error) { } realm.Attributes = attributes + defaultDefaultClientScopes := make([]string, 0) + if v, ok := data.GetOk("default_default_client_scopes"); ok { + for _, defaultDefaultClientScope := range v.(*schema.Set).List() { + defaultDefaultClientScopes = append(defaultDefaultClientScopes, defaultDefaultClientScope.(string)) + } + } + realm.DefaultDefaultClientScopes = defaultDefaultClientScopes + + defaultOptionalClientScopes := make([]string, 0) + if v, ok := data.GetOk("default_optional_client_scopes"); ok { + for _, defaultOptionalClientScope := range v.(*schema.Set).List() { + defaultOptionalClientScopes = append(defaultOptionalClientScopes, defaultOptionalClientScope.(string)) + } + } + realm.DefaultOptionalClientScopes = defaultOptionalClientScopes + //WebAuthn if v, ok := data.GetOk("web_authn_policy"); ok { webAuthnPolicy := v.([]interface{})[0].(map[string]interface{}) @@ -1116,6 +1148,10 @@ func setRealmData(data *schema.ResourceData, realm *keycloak.Realm) { } } data.Set("attributes", attributes) + + // default and optional client scope mappings + data.Set("default_default_client_scopes", realm.DefaultDefaultClientScopes) + data.Set("default_optional_client_scopes", realm.DefaultOptionalClientScopes) } func getBruteForceDetectionSettings(realm *keycloak.Realm) map[string]interface{} { diff --git a/provider/resource_keycloak_realm_test.go b/provider/resource_keycloak_realm_test.go index 121bd157..a087b04a 100644 --- a/provider/resource_keycloak_realm_test.go +++ b/provider/resource_keycloak_realm_test.go @@ -702,6 +702,131 @@ func TestAccKeycloakRealm_internalId(t *testing.T) { }) } +func TestAccKeycloakRealm_default_client_scopes(t *testing.T) { + + realmName := acctest.RandomWithPrefix("tf-acc") + defaultDefaultClientScope := []string{"profile"} + defaultOptionalClientScope := []string{"email", "roles"} + + realm := &keycloak.Realm{ + Realm: realmName, + } + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakRealmDestroy(), + Steps: []resource.TestStep{ + { + ResourceName: "keycloak_realm.realm", + ImportStateId: realmName, + ImportState: true, + Config: testKeycloakRealm_default_client_scopes(realmName, defaultDefaultClientScope, defaultOptionalClientScope), + PreConfig: func() { + err := keycloakClient.NewRealm(realm) + if err != nil { + t.Fatal(err) + } + }, + Check: testAccCheckKeycloakRealm_default_client_scopes(realmName, defaultDefaultClientScope, defaultOptionalClientScope), + }, + }, + }) + + // test empty default client scope configuration + realmName2 := acctest.RandomWithPrefix("tf-acc") + defaultDefaultClientScope2 := []string{} // deliberately empty + defaultOptionalClientScope2 := []string{} // deliberately empty + + realm2 := &keycloak.Realm{ + Realm: realmName2, + } + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakRealmDestroy(), + Steps: []resource.TestStep{ + { + ResourceName: "keycloak_realm.realm", + ImportStateId: realmName2, + ImportState: true, + Config: testKeycloakRealm_default_client_scopes(realmName2, defaultDefaultClientScope2, defaultOptionalClientScope2), + PreConfig: func() { + err := keycloakClient.NewRealm(realm2) + if err != nil { + t.Fatal(err) + } + }, + Check: testAccCheckKeycloakRealm_default_client_scopes(realmName2, defaultDefaultClientScope2, defaultOptionalClientScope2), + }, + }, + }) +} + +func testKeycloakRealm_default_client_scopes(realm string, defaultDefaultClientScopes []string, defaultOptionalClientScopes []string) string { + + defaultDefaultClientScopesString := fmt.Sprintf("%s", arrayOfStringsForTerraformResource(defaultDefaultClientScopes)) + defaultOptionalClientScopesString := fmt.Sprintf("%s", arrayOfStringsForTerraformResource(defaultOptionalClientScopes)) + + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" + enabled = true + default_default_client_scopes = %s + default_optional_client_scopes = %s +} + `, realm, defaultDefaultClientScopesString, defaultOptionalClientScopesString) +} + +func testAccCheckKeycloakRealm_default_client_scopes(resourceName string, defaultDefaultClientScope, defaultOptionalClientScope []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + realm, err := getRealmFromState(s, resourceName) + if err != nil { + return err + } + + if len(defaultDefaultClientScope) == 0 { + if len(realm.DefaultDefaultClientScopes) != 0 { + return fmt.Errorf("expected realm %s to have empty default default client scopes but was %s", realm.Realm, realm.DefaultDefaultClientScopes) + } + } else { + for _, expectedScope := range defaultDefaultClientScope { + found := false + for _, s := range realm.DefaultDefaultClientScopes { + if expectedScope == s { + found = true + break + } + } + if !found { + return fmt.Errorf("expected realm %s to have default default client scopes with value %s but was %s", realm.Realm, defaultDefaultClientScope, realm.DefaultDefaultClientScopes) + } + } + } + + if len(defaultOptionalClientScope) == 0 { + if len(realm.DefaultOptionalClientScopes) != 0 { + return fmt.Errorf("expected realm %s to have empty default optional client scopes but was %s", realm.Realm, realm.DefaultOptionalClientScopes) + } + } else { + for _, expectedScope := range defaultOptionalClientScope { + found := false + for _, s := range realm.DefaultOptionalClientScopes { + if expectedScope == s { + found = true + break + } + } + if !found { + return fmt.Errorf("expected realm %s to have default optional client scopes with value %s but was %s", realm.Realm, defaultOptionalClientScope, realm.DefaultOptionalClientScopes) + } + } + } + + return nil + } +} + func TestAccKeycloakRealm_webauthn(t *testing.T) { realmName := acctest.RandomWithPrefix("tf-acc") realmDisplayName := acctest.RandomWithPrefix("tf-acc")