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

Feature/keycloak_required_action config values #996

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions docs/resources/realm_user_profile.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ resource "keycloak_realm_user_profile" "userprofile" {

attribute {
name = "field2"
multivalued = true

validator {
name = "options"
Expand Down Expand Up @@ -104,6 +105,7 @@ resource "keycloak_realm_user_profile" "userprofile" {
- `name` - (Required) The name of the attribute.
- `display_name` - (Optional) The display name of the attribute.
- `group` - (Optional) The group that the attribute belong to.
- `multivalued` - (Optional) The attribute can contain multiple values. Defaults to `false`.
- `enabled_when_scope` - (Optional) A list of scopes. The attribute will only be enabled when these scopes are requested by clients.
- `required_for_roles` - (Optional) A list of roles for which the attribute will be required.
- `required_for_scopes` - (Optional) A list of scopes for which the attribute will be required.
Expand Down
9 changes: 7 additions & 2 deletions docs/resources/required_action.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ resource "keycloak_realm" "realm" {

resource "keycloak_required_action" "required_action" {
realm_id = keycloak_realm.realm.realm
alias = "webauthn-register"
alias = "UPDATE_PASSWORD"
enabled = true
name = "Webauthn Register"
name = "Update Password"

config = {
max_auth_age = "600"
}
}
```

Expand All @@ -33,6 +37,7 @@ resource "keycloak_required_action" "required_action" {
- `enabled` - (Optional) When `false`, the required action is not enabled for new users. Defaults to `false`.
- `default_action` - (Optional) When `true`, the required action is set as the default action for new users. Defaults to `false`.
- `priority`- (Optional) The priority of the required action.
- `config`- (Optional) The configuration. Keys are specific to each configurable required action and not checked when applying.

## Import

Expand Down
42 changes: 27 additions & 15 deletions example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,17 @@ resource "keycloak_required_action" "custom-terms-and-conditions" {
name = "Custom Terms and Conditions"
}

resource "keycloak_required_action" "update-password" {
realm_id = keycloak_realm.test.realm
alias = "UPDATE_PASSWORD"
default_action = true
enabled = true
name = "Update Password"

config {
max_auth_age = "600"
}
}
resource "keycloak_required_action" "custom-configured_totp" {
realm_id = keycloak_realm.test.realm
alias = "CONFIGURE_TOTP"
Expand Down Expand Up @@ -427,25 +438,25 @@ resource "keycloak_ldap_full_name_mapper" "full_name_mapper" {
}

resource "keycloak_ldap_custom_mapper" "custom_mapper" {
name = "custom-mapper"
realm_id = keycloak_ldap_user_federation.openldap.realm_id
ldap_user_federation_id = keycloak_ldap_user_federation.openldap.id
name = "custom-mapper"
realm_id = keycloak_ldap_user_federation.openldap.realm_id
ldap_user_federation_id = keycloak_ldap_user_federation.openldap.id

provider_id = "msad-user-account-control-mapper"
provider_type = "org.keycloak.storage.ldap.mappers.LDAPStorageMapper"
provider_id = "msad-user-account-control-mapper"
provider_type = "org.keycloak.storage.ldap.mappers.LDAPStorageMapper"
}

resource "keycloak_ldap_custom_mapper" "custom_mapper_with_config" {
name = "custom-mapper-with-config"
realm_id = keycloak_ldap_user_federation.openldap.realm_id
ldap_user_federation_id = keycloak_ldap_user_federation.openldap.id
name = "custom-mapper-with-config"
realm_id = keycloak_ldap_user_federation.openldap.realm_id
ldap_user_federation_id = keycloak_ldap_user_federation.openldap.id

provider_id = "user-attribute-ldap-mapper"
provider_type = "org.keycloak.storage.ldap.mappers.LDAPStorageMapper"
config = {
"user.model.attribute" = "username"
"ldap.attribute" = "cn"
}
provider_id = "user-attribute-ldap-mapper"
provider_type = "org.keycloak.storage.ldap.mappers.LDAPStorageMapper"
config = {
"user.model.attribute" = "username"
"ldap.attribute" = "cn"
}
}


Expand Down Expand Up @@ -1128,7 +1139,8 @@ resource "keycloak_realm_user_profile" "userprofile" {
}

attribute {
name = "field2"
name = "field2"
multivalued = true
}

group {
Expand Down
1 change: 1 addition & 0 deletions keycloak/realm_user_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type RealmUserProfileAttribute struct {
Required *RealmUserProfileRequired `json:"required,omitempty"`
Selector *RealmUserProfileSelector `json:"selector,omitempty"`
Validations map[string]RealmUserProfileValidationConfig `json:"validations,omitempty"`
Multivalued bool `json:"multivalued,omitempty"`
}

type RealmUserProfileGroup struct {
Expand Down
23 changes: 11 additions & 12 deletions keycloak/required_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,30 @@ import (
)

type RequiredAction struct {
Id string `json:"-"`
RealmId string `json:"-"`
Alias string `json:"alias"`
Name string `json:"name"`
ProviderId string `json:"providerId"`
Enabled bool `json:"enabled"`
DefaultAction bool `json:"defaultAction"`
Priority int `json:"priority"`
Config map[string][]string `json:"config"`
Id string `json:"-"`
RealmId string `json:"-"`
Alias string `json:"alias"`
Name string `json:"name"`
ProviderId string `json:"providerId"`
Enabled bool `json:"enabled"`
DefaultAction bool `json:"defaultAction"`
Priority int `json:"priority"`
Config map[string]string `json:"config"`
}

func (requiredActions *RequiredAction) getConfig(val string) string {
if len(requiredActions.Config[val]) == 0 {
return ""
}
return requiredActions.Config[val][0]
return requiredActions.Config[val]
}

func (requiredActions *RequiredAction) getConfigOk(val string) (string, bool) {
if v, ok := requiredActions.Config[val]; ok {
return v[0], true
return v, true
}
return "", false
}

func (keycloakClient *KeycloakClient) GetRequiredActions(ctx context.Context, realmId string) ([]*RequiredAction, error) {
var requiredActions []*RequiredAction

Expand Down
6 changes: 6 additions & 0 deletions provider/resource_keycloak_realm_user_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ func resourceKeycloakRealmUserProfile() *schema.Resource {
Type: schema.TypeString,
Optional: true,
},
"multivalued": {
Type: schema.TypeBool,
Optional: true,
},
"enabled_when_scope": {
Type: schema.TypeSet,
Optional: true,
Expand Down Expand Up @@ -134,6 +138,7 @@ func getRealmUserProfileAttributeFromData(m map[string]interface{}) *keycloak.Re
Name: m["name"].(string),
DisplayName: m["display_name"].(string),
Group: m["group"].(string),
Multivalued: m["multivalued"].(bool),
}

if v, ok := m["permissions"]; ok && len(v.([]interface{})) > 0 {
Expand Down Expand Up @@ -303,6 +308,7 @@ func getRealmUserProfileAttributeData(attr *keycloak.RealmUserProfileAttribute)

attributeData["display_name"] = attr.DisplayName
attributeData["group"] = attr.Group
attributeData["multivalued"] = attr.Multivalued
if attr.Selector != nil && len(attr.Selector.Scopes) != 0 {
attributeData["enabled_when_scope"] = attr.Selector.Scopes
}
Expand Down
16 changes: 14 additions & 2 deletions provider/resource_keycloak_required_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"context"
"errors"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mrparkers/terraform-provider-keycloak/keycloak"
"strings"
)

func resourceKeycloakRequiredAction() *schema.Resource {
Expand Down Expand Up @@ -47,11 +48,21 @@ func resourceKeycloakRequiredAction() *schema.Resource {
Optional: true,
Computed: true,
},
"config": {
Type: schema.TypeMap,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
},
}
}

func getRequiredActionFromData(data *schema.ResourceData) (*keycloak.RequiredAction, error) {
config := make(map[string]string)
for key, value := range data.Get("config").(map[string]interface{}) {
config[key] = value.(string)
}

action := &keycloak.RequiredAction{
Id: fmt.Sprintf("%s/%s", data.Get("realm_id").(string), data.Get("alias").(string)),
RealmId: data.Get("realm_id").(string),
Expand All @@ -60,7 +71,7 @@ func getRequiredActionFromData(data *schema.ResourceData) (*keycloak.RequiredAct
Enabled: data.Get("enabled").(bool),
DefaultAction: data.Get("default_action").(bool),
Priority: data.Get("priority").(int),
Config: make(map[string][]string),
Config: config,
}

return action, nil
Expand All @@ -74,6 +85,7 @@ func setRequiredActionData(data *schema.ResourceData, action *keycloak.RequiredA
data.Set("enabled", action.Enabled)
data.Set("default_action", action.DefaultAction)
data.Set("priority", action.Priority)
data.Set("config", action.Config)
}

func resourceKeycloakRequiredActionsCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
Expand Down
43 changes: 43 additions & 0 deletions provider/resource_keycloak_required_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ func TestAccKeycloakRequiredAction_basic(t *testing.T) {
})
}

func TestAccKeycloakRequiredAction_withConfig(t *testing.T) {
realmName := acctest.RandomWithPrefix("tf-acc")
requiredActionAlias := "UPDATE_PASSWORD"
maxAuthAgeConfig := "3600"

resource.Test(t, resource.TestCase{
ProviderFactories: testAccProviderFactories,
PreCheck: func() { testAccPreCheck(t) },
Steps: []resource.TestStep{
{
Config: testKeycloakRequiredAction_withConfig(realmName, requiredActionAlias, 37, maxAuthAgeConfig),
Check: resource.ComposeTestCheckFunc(
testAccCheckKeycloakRequiresActionExists(realmName, requiredActionAlias),
resource.TestCheckResourceAttr("keycloak_required_action.required_action", "realm_id", realmName),
resource.TestCheckResourceAttr("keycloak_required_action.required_action", "config.%", "1"),
resource.TestCheckResourceAttr("keycloak_required_action.required_action", "config.max_auth_age", maxAuthAgeConfig),
),
},
},
})
}

func TestAccKeycloakRequiredAction_unregisteredAction(t *testing.T) {
realmName := acctest.RandomWithPrefix("tf-acc")
requiredActionAlias := "webauthn-register"
Expand Down Expand Up @@ -128,6 +150,27 @@ resource "keycloak_required_action" "required_action" {
`, realm, requiredActionAlias, priority)
}

func testKeycloakRequiredAction_withConfig(realm, requiredActionAlias string, priority int, maxAuthAgeConfig string) string {
return fmt.Sprintf(`
resource "keycloak_realm" "realm" {
realm = "%s"
}

resource "keycloak_required_action" "required_action" {
realm_id = "${keycloak_realm.realm.realm}"
alias = "%s"
default_action = true
enabled = true
name = "My required Action"
priority = %d

config = {
max_auth_age = "%s"
}
}
`, realm, requiredActionAlias, priority, maxAuthAgeConfig)
}

func testKeycloakRequiredAction_import(realm, requiredActionAlias string) string {
return fmt.Sprintf(`
resource "keycloak_realm" "realm" {
Expand Down
Loading