Repository: mrparkers/terraform-provider-keycloak
Pull request #277 (2020-04-28 07:43:55)

feat: add function to get authentication flows
Added `ListAuthenticationFlows` function to get authentication flows in the realm because I'd like to implement importing authentication flows through [terafformer](https://github.com/GoogleCloudPlatform/terraformer) like #184.


Patch:
diff --git a/docs/resources/keycloak_openid_client.md b/docs/resources/keycloak_openid_client.md
index 501e7e2..4ffac53 100644
--- a/docs/resources/keycloak_openid_client.md
+++ b/docs/resources/keycloak_openid_client.md
@@ -25,6 +25,8 @@ resource "keycloak_openid_client" "openid_client" {
     valid_redirect_uris = [
         "http://localhost:8080/openid-callback"
     ]
+
+    login_theme = "keycloak"
 }
 ```
 
@@ -64,6 +66,7 @@ is set to `true`.
 - `authentication_flow_binding_overrides` - (Optional) - Override realm authentication flow bindings
     - `browers_id` - (Optional) - Browser flow id, (flow needs to exist)
     - `direct_grant_id` - (Optional) - Direct grant flow id (flow needs to exist)
+- `login_theme` - (Optional) - Override realm login theme
 
 ### Attributes Reference
 
diff --git a/docs/resources/keycloak_user.md b/docs/resources/keycloak_user.md
index 2c37622..f3797e1 100644
--- a/docs/resources/keycloak_user.md
+++ b/docs/resources/keycloak_user.md
@@ -33,6 +33,10 @@ resource "keycloak_user" "user_with_initial_password" {
     first_name = "Alice"
     last_name  = "Aliceberg"
 
+    attributes = {
+      foo = "bar"
+    }
+
     initial_password {
       value     = "some password"
       temporary = true
@@ -55,6 +59,11 @@ The following arguments are supported:
 - `email_verified` - (Optional) Whether the email address was validated or not. Default to `false`.
 - `first_name` - (Optional) The user's first name.
 - `last_name` - (Optional) The user's last name.
+- `attributes` - (Optional) A map representing attributes for the user
+- `federated_identity` - (Optional) When specified, the user will be linked to a federated identity provider. Refer to the [federated user example](https://github.com/mrparkers/terraform-provider-keycloak/blob/master/example/federated_user_example.tf) for more details.
+    - `identity_provider` - (Required) The name of the identity provider
+    - `user_id` - (Required) The ID of the user defined in the identity provider
+    - `user_name` - (Required) The user name of the user defined in the identity provider
 
 ### Import
 
diff --git a/example/main.tf b/example/main.tf
index cc0d9b3..098627f 100644
--- a/example/main.tf
+++ b/example/main.tf
@@ -172,6 +172,8 @@ resource "keycloak_openid_client" "test_client" {
   client_secret = "secret"
 
   pkce_code_challenge_method = "plain"
+
+  login_theme = "keycloak"
 }
 
 resource "keycloak_openid_client_scope" "test_default_client_scope" {
diff --git a/keycloak/authentication_flow.go b/keycloak/authentication_flow.go
index 8ce3de3..c926b7b 100644
--- a/keycloak/authentication_flow.go
+++ b/keycloak/authentication_flow.go
@@ -14,6 +14,21 @@ type AuthenticationFlow struct {
 	BuiltIn     bool   `json:"builtIn"`
 }
 
+func (keycloakClient *KeycloakClient) ListAuthenticationFlows(realmId string) ([]*AuthenticationFlow, error) {
+	var authenticationFlows []*AuthenticationFlow
+
+	err := keycloakClient.get(fmt.Sprintf("/realms/%s/authentication/flows", realmId), &authenticationFlows, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	for _, authenticationFlow := range authenticationFlows {
+		authenticationFlow.RealmId = realmId
+	}
+
+	return authenticationFlows, nil
+}
+
 func (keycloakClient *KeycloakClient) NewAuthenticationFlow(authenticationFlow *AuthenticationFlow) error {
 	authenticationFlow.TopLevel = true
 	authenticationFlow.BuiltIn = false
diff --git a/keycloak/openid_client.go b/keycloak/openid_client.go
index 92b4a4f..aa8260b 100644
--- a/keycloak/openid_client.go
+++ b/keycloak/openid_client.go
@@ -57,6 +57,7 @@ type OpenidClientAttributes struct {
 	PkceCodeChallengeMethod             string             `json:"pkce.code.challenge.method"`
 	ExcludeSessionStateFromAuthResponse KeycloakBoolQuoted `json:"exclude.session.state.from.auth.response"`
 	AccessTokenLifespan                 string             `json:"access.token.lifespan"`
+	LoginTheme                          string             `json:"login_theme"`
 }
 
 type OpenidAuthenticationFlowBindingOverrides struct {
@@ -90,6 +91,15 @@ func (keycloakClient *KeycloakClient) ValidateOpenidClient(client *OpenidClient)
 		return fmt.Errorf("validation error: service accounts (client credentials flow) cannot be enabled on public clients")
 	}
 
+	serverInfo, err := keycloakClient.GetServerInfo()
+	if err != nil {
+		return err
+	}
+
+	if client.Attributes.LoginTheme != "" && !serverInfo.ThemeIsInstalled("login", client.Attributes.LoginTheme) {
+		return fmt.Errorf("validation error: theme \"%s\" does not exist on the server", client.Attributes.LoginTheme)
+	}
+
 	return nil
 }
 
diff --git a/provider/data_source_keycloak_openid_client.go b/provider/data_source_keycloak_openid_client.go
index a9e8243..6111e43 100644
--- a/provider/data_source_keycloak_openid_client.go
+++ b/provider/data_source_keycloak_openid_client.go
@@ -121,6 +121,10 @@ func dataSourceKeycloakOpenidClient() *schema.Resource {
 					},
 				},
 			},
+			"login_theme": {
+				Type:     schema.TypeString,
+				Computed: true,
+			},
 		},
 	}
 }
diff --git a/provider/resource_keycloak_openid_client.go b/provider/resource_keycloak_openid_client.go
index 22aed5a..45cef8e 100644
--- a/provider/resource_keycloak_openid_client.go
+++ b/provider/resource_keycloak_openid_client.go
@@ -176,6 +176,10 @@ func resourceKeycloakOpenidClient() *schema.Resource {
 					},
 				},
 			},
+			"login_theme": {
+				Type:     schema.TypeString,
+				Optional: true,
+			},
 		},
 	}
 }
@@ -232,6 +236,7 @@ func getOpenidClientFromData(data *schema.ResourceData) (*keycloak.OpenidClient,
 			PkceCodeChallengeMethod:             data.Get("pkce_code_challenge_method").(string),
 			ExcludeSessionStateFromAuthResponse: keycloak.KeycloakBoolQuoted(data.Get("exclude_session_state_from_auth_response").(bool)),
 			AccessTokenLifespan:                 data.Get("access_token_lifespan").(string),
+			LoginTheme:                          data.Get("login_theme").(string),
 		},
 		ValidRedirectUris: validRedirectUris,
 		WebOrigins:        webOrigins,
@@ -317,6 +322,7 @@ func setOpenidClientData(keycloakClient *keycloak.KeycloakClient, data *schema.R
 	data.Set("full_scope_allowed", client.FullScopeAllowed)
 	data.Set("consent_required", client.ConsentRequired)
 	data.Set("access_token_lifespan", client.Attributes.AccessTokenLifespan)
+	data.Set("login_theme", client.Attributes.LoginTheme)
 
 	if client.AuthorizationServicesEnabled {
 		data.Set("resource_server_id", client.Id)


Test patch:
diff --git a/provider/resource_keycloak_openid_client_test.go b/provider/resource_keycloak_openid_client_test.go
index 755c343..d9b51e9 100644
--- a/provider/resource_keycloak_openid_client_test.go
+++ b/provider/resource_keycloak_openid_client_test.go
@@ -120,6 +120,7 @@ func TestAccKeycloakOpenidClient_accessType(t *testing.T) {
 		},
 	})
 }
+
 func TestAccKeycloakOpenidClient_adminUrl(t *testing.T) {
 	realmName := "terraform-" + acctest.RandString(10)
 	clientId := "terraform-" + acctest.RandString(10)
@@ -483,6 +484,38 @@ func TestAccKeycloakOpenidClient_authenticationFlowBindingOverrides(t *testing.T
 	})
 }
 
+func TestAccKeycloakOpenidClient_loginTheme(t *testing.T) {
+	realmName := "terraform-" + acctest.RandString(10)
+	clientId := "terraform-" + acctest.RandString(10)
+	loginThemeKeycloak := "keycloak"
+	loginThemeBase := "base"
+	loginThemeRandom := "theme-" + acctest.RandString(10)
+
+	resource.Test(t, resource.TestCase{
+		Providers:    testAccProviders,
+		PreCheck:     func() { testAccPreCheck(t) },
+		CheckDestroy: testAccCheckKeycloakOpenidClientDestroy(),
+		Steps: []resource.TestStep{
+			{
+				Config: testKeycloakOpenidClient_loginTheme(realmName, clientId, loginThemeKeycloak),
+				Check:  testAccCheckKeycloakOpenidClientLoginTheme("keycloak_openid_client.client", loginThemeKeycloak),
+			},
+			{
+				Config: testKeycloakOpenidClient_loginTheme(realmName, clientId, loginThemeBase),
+				Check:  testAccCheckKeycloakOpenidClientLoginTheme("keycloak_openid_client.client", loginThemeBase),
+			},
+			{
+				Config:      testKeycloakOpenidClient_loginTheme(realmName, clientId, loginThemeRandom),
+				ExpectError: regexp.MustCompile("validation error: theme \".+\" does not exist on the server"),
+			},
+			{
+				Config: testKeycloakOpenidClient_loginTheme(realmName, clientId, loginThemeKeycloak),
+				Check:  testAccCheckKeycloakOpenidClientLoginTheme("keycloak_openid_client.client", loginThemeKeycloak),
+			},
+		},
+	})
+}
+
 func testAccCheckKeycloakOpenidClientExistsWithCorrectProtocol(resourceName string) resource.TestCheckFunc {
 	return func(s *terraform.State) error {
 		client, err := getOpenidClientFromState(s, resourceName)
@@ -723,6 +756,21 @@ func testAccCheckKeycloakOpenidClientAuthenticationFlowBindingOverrides(resource
 	}
 }
 
+func testAccCheckKeycloakOpenidClientLoginTheme(resourceName string, loginTheme string) resource.TestCheckFunc {
+	return func(s *terraform.State) error {
+		client, err := getOpenidClientFromState(s, resourceName)
+		if err != nil {
+			return err
+		}
+
+		if client.Attributes.LoginTheme != loginTheme {
+			return fmt.Errorf("expected openid client to have login theme set to %s, but got %s", loginTheme, client.Attributes.LoginTheme)
+		}
+
+		return nil
+	}
+}
+
 func getOpenidClientFromState(s *terraform.State, resourceName string) (*keycloak.OpenidClient, error) {
 	keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient)
 
@@ -1072,3 +1120,18 @@ resource "keycloak_openid_client" "client" {
 }
 	`, realm, clientId)
 }
+
+func testKeycloakOpenidClient_loginTheme(realm, clientId, loginTheme string) string {
+	return fmt.Sprintf(`
+resource "keycloak_realm" "realm" {
+	realm = "%s"
+}
+
+resource "keycloak_openid_client" "client" {
+	client_id   = "%s"
+	realm_id    = "${keycloak_realm.realm.id}"
+	access_type = "PUBLIC"
+	login_theme = "%s"
+}
+	`, realm, clientId, loginTheme)
+}