Skip to content

Commit

Permalink
adds support for flows, subflows and executions (#215)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomrutsaert authored Feb 20, 2020
1 parent fb8e19f commit 1305ac4
Show file tree
Hide file tree
Showing 12 changed files with 1,609 additions and 0 deletions.
52 changes: 52 additions & 0 deletions example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -643,3 +643,55 @@ resource "keycloak_openid_client_service_account_role" "read_token" {
service_account_user_id = "${keycloak_openid_client.test_client_auth.service_account_user_id}"
role = "read-token"
}

resource "keycloak_authentication_flow" "browser-copy-flow" {
alias = "browserCopyFlow"
realm_id = "${keycloak_realm.test.id}"
description = "browser based authentication"
}

resource "keycloak_authentication_execution" "browser-copy-cookie" {
realm_id = "${keycloak_realm.test.id}"
parent_flow_alias = "${keycloak_authentication_flow.browser-copy-flow.alias}"
authenticator = "auth-cookie"
requirement = "ALTERNATIVE"
depends_on = ["keycloak_authentication_execution.browser-copy-kerberos"]
}

resource "keycloak_authentication_execution" "browser-copy-kerberos" {
realm_id = "${keycloak_realm.test.id}"
parent_flow_alias = "${keycloak_authentication_flow.browser-copy-flow.alias}"
authenticator = "auth-spnego"
requirement = "DISABLED"
}

resource "keycloak_authentication_execution" "browser-copy-idp-redirect" {
realm_id = "${keycloak_realm.test.id}"
parent_flow_alias = "${keycloak_authentication_flow.browser-copy-flow.alias}"
authenticator = "identity-provider-redirector"
requirement = "ALTERNATIVE"
depends_on = ["keycloak_authentication_execution.browser-copy-cookie"]
}

resource "keycloak_authentication_subflow" "browser-copy-flow-forms" {
realm_id = "${keycloak_realm.test.id}"
parent_flow_alias = "${keycloak_authentication_flow.browser-copy-flow.alias}"
alias = "browser-copy-flow-forms"
requirement = "ALTERNATIVE"
depends_on = ["keycloak_authentication_execution.browser-copy-idp-redirect"]
}

resource "keycloak_authentication_execution" "browser-copy-auth-username-password-form" {
realm_id = "${keycloak_realm.test.id}"
parent_flow_alias = "${keycloak_authentication_subflow.browser-copy-flow-forms.alias}"
authenticator = "auth-username-password-form"
requirement = "REQUIRED"
}

resource "keycloak_authentication_execution" "browser-copy-otp" {
realm_id = "${keycloak_realm.test.id}"
parent_flow_alias = "${keycloak_authentication_subflow.browser-copy-flow-forms.alias}"
authenticator = "auth-otp-form"
requirement = "REQUIRED"
depends_on = ["keycloak_authentication_execution.browser-copy-auth-username-password-form"]
}
144 changes: 144 additions & 0 deletions keycloak/authentication_execution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package keycloak

import (
"fmt"
)

// this is only used when creating an execution on a flow.
// other fields can be provided to the API but they are ignored
// POST /realms/${realmId}/authentication/flows/${flowAlias}/executions/execution
type authenticationExecutionCreate struct {
Provider string `json:"provider"` //authenticator of the execution
}

type authenticationExecutionRequirementUpdate struct {
RealmId string `json:"-"`
ParentFlowAlias string `json:"-"`
Id string `json:"id"`
Requirement string `json:"requirement"`
}

// this type is returned by GET /realms/${realmId}/authentication/flows/${flowAlias}/executions
type AuthenticationExecution struct {
Id string `json:"id"`
RealmId string `json:"-"`
ParentFlowAlias string `json:"-"`
Authenticator string `json:"authenticator"` //can be any authenticator from GET realms/{realm}/authentication/authenticator-providers OR GET realms/{realm}/authentication/client-authenticator-providers OR GET realms/{realm}/authentication/form-action-providers
AuthenticationConfig string `json:"authenticationConfig"`
AuthenticationFlow bool `json:"authenticationFlow"`
FlowId string `json:"flowId"`
ParentFlowId string `json:"parentFlow"`
Priority int `json:"priority"`
Requirement string `json:"requirement"`
}

// another model is used for GET /realms/${realmId}/authentication/executions/${executionId}, but I am going to try to avoid using this API
type AuthenticationExecutionInfo struct {
Id string `json:"id"`
RealmId string `json:"-"`
ParentFlowAlias string `json:"-"`
Alias string `json:"alias"`
AuthenticationConfig string `json:"authenticationConfig"`
AuthenticationFlow bool `json:"authenticationFlow"`
Configurable bool `json:"configurable"`
FlowId string `json:"flowId"`
Index int `json:"index"`
Level int `json:"level"`
ProviderId string `json:"providerId"`
Requirement string `json:"requirement"`
}

type AuthenticationExecutionList []*AuthenticationExecutionInfo

func (list AuthenticationExecutionList) Len() int {
return len(list)
}

func (list AuthenticationExecutionList) Less(i, j int) bool {
return list[i].Index < list[j].Index
}

func (list AuthenticationExecutionList) Swap(i, j int) {
list[i], list[j] = list[j], list[i]
}

func (keycloakClient *KeycloakClient) ListAuthenticationExecutions(realmId, parentFlowAlias string) (AuthenticationExecutionList, error) {
var authenticationExecutions []*AuthenticationExecutionInfo

err := keycloakClient.get(fmt.Sprintf("/realms/%s/authentication/flows/%s/executions", realmId, parentFlowAlias), &authenticationExecutions, nil)
if err != nil {
return nil, err
}

return authenticationExecutions, err
}

func (keycloakClient *KeycloakClient) NewAuthenticationExecution(execution *AuthenticationExecution) error {
_, location, err := keycloakClient.post(fmt.Sprintf("/realms/%s/authentication/flows/%s/executions/execution", execution.RealmId, execution.ParentFlowAlias), &authenticationExecutionCreate{Provider: execution.Authenticator})
if err != nil {
return err
}

execution.Id = getIdFromLocationHeader(location)

err = keycloakClient.UpdateAuthenticationExecution(execution)
if err != nil {
return err
}

return nil
}

func (keycloakClient *KeycloakClient) GetAuthenticationExecution(realmId, parentFlowAlias, id string) (*AuthenticationExecution, error) {
var authenticationExecution AuthenticationExecution

err := keycloakClient.get(fmt.Sprintf("/realms/%s/authentication/executions/%s", realmId, id), &authenticationExecution, nil)
if err != nil {
return nil, err
}

authenticationExecution.RealmId = realmId
authenticationExecution.ParentFlowAlias = parentFlowAlias

return &authenticationExecution, nil
}

func (keycloakClient *KeycloakClient) UpdateAuthenticationExecution(execution *AuthenticationExecution) error {
authenticationExecutionUpdateRequirement := &authenticationExecutionRequirementUpdate{
RealmId: execution.RealmId,
ParentFlowAlias: execution.ParentFlowAlias,
Id: execution.Id,
Requirement: execution.Requirement,
}
return keycloakClient.UpdateAuthenticationExecutionRequirement(authenticationExecutionUpdateRequirement)
}

func (keycloakClient *KeycloakClient) UpdateAuthenticationExecutionRequirement(executionRequirementUpdate *authenticationExecutionRequirementUpdate) error {
return keycloakClient.put(fmt.Sprintf("/realms/%s/authentication/flows/%s/executions", executionRequirementUpdate.RealmId, executionRequirementUpdate.ParentFlowAlias), executionRequirementUpdate)
}

func (keycloakClient *KeycloakClient) DeleteAuthenticationExecution(realmId, id string) error {
err := keycloakClient.delete(fmt.Sprintf("/realms/%s/authentication/executions/%s", realmId, id), nil)
if err != nil {
// For whatever reason, this fails sometimes with a 500 during acceptance tests. try again
return keycloakClient.delete(fmt.Sprintf("/realms/%s/authentication/executions/%s", realmId, id), nil)
}

return nil
}

func (keycloakClient *KeycloakClient) RaiseAuthenticationExecutionPriority(realmId, id string) error {
_, _, err := keycloakClient.post(fmt.Sprintf("/realms/%s/authentication/executions/%s/raise-priority", realmId, id), nil)
if err != nil {
return err
}
return nil
}

func (keycloakClient *KeycloakClient) LowerAuthenticationExecutionPriority(realmId, id string) error {
_, _, err := keycloakClient.post(fmt.Sprintf("/realms/%s/authentication/executions/%s/lower-priority", realmId, id), nil)
if err != nil {
return err
}
return nil
}
55 changes: 55 additions & 0 deletions keycloak/authentication_flow.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package keycloak

import (
"fmt"
)

type AuthenticationFlow struct {
Id string `json:"id,omitempty"`
RealmId string `json:"-"`
Alias string `json:"alias"`
Description string `json:"description"`
ProviderId string `json:"providerId"` // "basic-flow" or "client-flow"
TopLevel bool `json:"topLevel"`
BuiltIn bool `json:"builtIn"`
}

func (keycloakClient *KeycloakClient) NewAuthenticationFlow(authenticationFlow *AuthenticationFlow) error {
authenticationFlow.TopLevel = true
authenticationFlow.BuiltIn = false

_, location, err := keycloakClient.post(fmt.Sprintf("/realms/%s/authentication/flows", authenticationFlow.RealmId), authenticationFlow)
if err != nil {
return err
}
authenticationFlow.Id = getIdFromLocationHeader(location)

return nil
}

func (keycloakClient *KeycloakClient) GetAuthenticationFlow(realmId, id string) (*AuthenticationFlow, error) {
var authenticationFlow AuthenticationFlow
err := keycloakClient.get(fmt.Sprintf("/realms/%s/authentication/flows/%s", realmId, id), &authenticationFlow, nil)
if err != nil {
return nil, err
}

authenticationFlow.RealmId = realmId
return &authenticationFlow, nil
}

func (keycloakClient *KeycloakClient) UpdateAuthenticationFlow(authenticationFlow *AuthenticationFlow) error {
authenticationFlow.TopLevel = true
authenticationFlow.BuiltIn = false

return keycloakClient.put(fmt.Sprintf("/realms/%s/authentication/flows/%s", authenticationFlow.RealmId, authenticationFlow.Id), authenticationFlow)
}

func (keycloakClient *KeycloakClient) DeleteAuthenticationFlow(realmId, id string) error {
err := keycloakClient.delete(fmt.Sprintf("/realms/%s/authentication/flows/%s", realmId, id), nil)
if err != nil {
// For whatever reason, this fails sometimes with a 500 during acceptance tests. try again
return keycloakClient.delete(fmt.Sprintf("/realms/%s/authentication/flows/%s", realmId, id), nil)
}
return nil
}
Loading

0 comments on commit 1305ac4

Please sign in to comment.