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 14 commits into
base: main
Choose a base branch
from
Open
2 changes: 2 additions & 0 deletions docs/resources/realm_user_profile.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ resource "keycloak_realm_user_profile" "userprofile" {

attribute {
name = "field2"
multivalued = true

validator {
name = "options"
Expand Down Expand Up @@ -101,6 +102,7 @@ resource "keycloak_realm_user_profile" "userprofile" {
- `display_name` - (Optional) The display name of the attribute.
- `multi_valued` - (Optional) If the attribute supports multiple values. Defaults to `false`.
- `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 @@ -1130,7 +1141,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 @@ -32,6 +32,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 @@ -55,6 +55,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 @@ -156,6 +160,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["multivalued"].(bool); ok {
Expand Down Expand Up @@ -343,6 +348,7 @@ func getRealmUserProfileAttributeData(attr *keycloak.RealmUserProfileAttribute)
attributeData["multi_valued"] = attr.MultiValued

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: 15 additions & 1 deletion provider/resource_keycloak_required_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
"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"

Check failure on line 11 in provider/resource_keycloak_required_action.go

View workflow job for this annotation

GitHub Actions / verify

no required module provides package github.com/mrparkers/terraform-provider-keycloak/keycloak; to add it:

Check failure on line 11 in provider/resource_keycloak_required_action.go

View workflow job for this annotation

GitHub Actions / Analyze (go)

no required module provides package github.com/mrparkers/terraform-provider-keycloak/keycloak; to add it:
"github.com/keycloak/terraform-provider-keycloak/keycloak"
"strings"
)
Expand Down Expand Up @@ -47,11 +50,21 @@
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 +73,7 @@
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 +87,7 @@
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