Repository: Nerzal/gocloak
Pull request #318 (2021-11-10 14:01:56)

Auth flows and executions
Supersedes #256 as the original one seems to be dead?

This is essentially #256 rebased on current main branch, fixed tests.


Patch:
diff --git a/client.go b/client.go
index 8153b9f..1a0637c 100644
--- a/client.go
+++ b/client.go
@@ -2129,6 +2129,81 @@ func (client *gocloak) ClearKeysCache(ctx context.Context, token, realm string)
 	return checkForError(resp, err, errMessage)
 }
 
+//GetAuthenticationFlows get all authentication flows from a realm
+func (client *gocloak) GetAuthenticationFlows(ctx context.Context, token, realm string) ([]*AuthenticationFlowRepresentation, error) {
+	const errMessage = "could not retrieve authentication flows"
+	var result []*AuthenticationFlowRepresentation
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, "authentication", "flows"))
+
+	if err := checkForError(resp, err, errMessage); err != nil {
+		return nil, err
+	}
+	return result, nil
+}
+
+//Create a new Authentication flow in a realm
+func (client *gocloak) CreateAuthenticationFlow(ctx context.Context, token, realm string, flow AuthenticationFlowRepresentation) error {
+	const errMessage = "could not create authentication flows"
+	var result []*AuthenticationFlowRepresentation
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetResult(&result).SetBody(flow).
+		Post(client.getAdminRealmURL(realm, "authentication", "flows"))
+
+	return checkForError(resp, err, errMessage)
+}
+
+//DeleteAuthenticationFlow deletes a flow in a realm with the given ID
+func (client *gocloak) DeleteAuthenticationFlow(ctx context.Context, token, realm, flowID string) error {
+	const errMessage = "could not delete authentication flows"
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		Delete(client.getAdminRealmURL(realm, "authentication", "flows", flowID))
+
+	return checkForError(resp, err, errMessage)
+}
+
+//GetAuthenticationExecutions retrieves all executions of a given flow
+func (client *gocloak) GetAuthenticationExecutions(ctx context.Context, token, realm, flow string) ([]*ModifyAuthenticationExecutionRepresentation, error) {
+	const errMessage = "could not retrieve authentication flows"
+	var result []*ModifyAuthenticationExecutionRepresentation
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, "authentication", "flows", flow, "executions"))
+
+	if err := checkForError(resp, err, errMessage); err != nil {
+		return nil, err
+	}
+	return result, nil
+}
+
+//CreateAuthenticationExecution creates a new execution for the given flow name in the given realm
+func (client *gocloak) CreateAuthenticationExecution(ctx context.Context, token, realm, flow string, execution CreateAuthenticationExecutionRepresentation) error {
+	const errMessage = "could not create authentication execution"
+	resp, err := client.getRequestWithBearerAuth(ctx, token).SetBody(execution).
+		Post(client.getAdminRealmURL(realm, "authentication", "flows", flow, "executions", "execution"))
+
+	return checkForError(resp, err, errMessage)
+}
+
+//UpdateAuthenticationExecution updates an authentication execution for the given flow in the given realm
+func (client *gocloak) UpdateAuthenticationExecution(ctx context.Context, token, realm, flow string, execution ModifyAuthenticationExecutionRepresentation) error {
+	const errMessage = "could not update authentication execution"
+	resp, err := client.getRequestWithBearerAuth(ctx, token).SetBody(execution).
+		Put(client.getAdminRealmURL(realm, "authentication", "flows", flow, "executions"))
+
+	return checkForError(resp, err, errMessage)
+}
+
+// DeleteAuthenticationExecution delete a single execution with the given ID
+func (client *gocloak) DeleteAuthenticationExecution(ctx context.Context, token, realm, executionID string) error {
+	const errMessage = "could not delete authentication execution"
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		Delete(client.getAdminRealmURL(realm, "authentication", "executions", executionID))
+
+	return checkForError(resp, err, errMessage)
+}
+
 // -----
 // Users
 // -----
diff --git a/gocloak.go b/gocloak.go
index 5690da7..cde307e 100644
--- a/gocloak.go
+++ b/gocloak.go
@@ -301,6 +301,20 @@ type GoCloak interface {
 	ClearUserCache(ctx context.Context, token, realm string) error
 	// ClearKeysCache clears realm cache
 	ClearKeysCache(ctx context.Context, token, realm string) error
+	//GetAuthenticationFlows get all authentication flows from a realm
+	GetAuthenticationFlows(ctx context.Context, token, realm string) ([]*AuthenticationFlowRepresentation, error)
+	//Create a new Authentication flow in a realm
+	CreateAuthenticationFlow(ctx context.Context, token, realm string, flow AuthenticationFlowRepresentation) error
+	//DeleteAuthenticationFlow deletes a flow in a realm with the given ID
+	DeleteAuthenticationFlow(ctx context.Context, token, realm, flowID string) error
+	//GetAuthenticationExecutions retrieves all executions of a given flow
+	GetAuthenticationExecutions(ctx context.Context, token, realm, flow string) ([]*ModifyAuthenticationExecutionRepresentation, error)
+	//CreateAuthenticationExecution creates a new execution for the given flow name in the given realm
+	CreateAuthenticationExecution(ctx context.Context, token, realm, flow string, execution CreateAuthenticationExecutionRepresentation) error
+	//UpdateAuthenticationExecution updates an authentication execution for the given flow in the given realm
+	UpdateAuthenticationExecution(ctx context.Context, token, realm, flow string, execution ModifyAuthenticationExecutionRepresentation) error
+	// DeleteAuthenticationExecution delete a single execution with the given ID
+	DeleteAuthenticationExecution(ctx context.Context, token, realm, executionID string) error
 
 	// *** Users ***
 	// CreateUser creates a new user
diff --git a/models.go b/models.go
index 1f6d85e..5cdb6b6 100644
--- a/models.go
+++ b/models.go
@@ -769,6 +769,47 @@ type RealmRepresentation struct {
 	WaitIncrementSeconds                *int                 `json:"waitIncrementSeconds,omitempty"`
 }
 
+// AuthenticationFlowRepresentation represents an authentication flow of a realm
+type AuthenticationFlowRepresentation struct {
+	Alias                    *string                                  `json:"alias,omitempty"`
+	AuthenticationExecutions *[]AuthenticationExecutionRepresentation `json:"authenticationExecutions,omitempty"`
+	BuiltIn                  *bool                                    `json:"builtIn,omitempty"`
+	Description              *string                                  `json:"description,omitempty"`
+	ID                       *string                                  `json:"id,omitempty"`
+	ProviderID               *string                                  `json:"providerId,omitempty"`
+	TopLevel                 *bool                                    `json:"topLevel,omitempty"`
+}
+
+// AuthenticationExecutionRepresentation represents the authentication execution of an AuthenticationFlowRepresentation
+type AuthenticationExecutionRepresentation struct {
+	Authenticator       *string `json:"authenticator,omitempty"`
+	AuthenticatorConfig *string `json:"authenticatorConfig,omitempty"`
+	AuthenticatorFlow   *bool   `json:"authenticatorFlow,omitempty"`
+	AutheticatorFlow    *bool   `json:"autheticatorFlow,omitempty"`
+	FlowAlias           *string `json:"flowAlias,omitempty"`
+	Priority            *int    `json:"priority,omitempty"`
+	Requirement         *string `json:"requirement,omitempty"`
+	UserSetupAllowed    *bool   `json:"userSetupAllowed,omitempty"`
+}
+
+// CreateAuthenticationExecutionRepresentation contains the provider to be used for a new authentication representation
+type CreateAuthenticationExecutionRepresentation struct {
+	Provider *string `json:"provider,omitempty"`
+}
+
+// ModifyAuthenticationExecutionRepresentation is the payload for updating an execution representation
+type ModifyAuthenticationExecutionRepresentation struct {
+	ID                  *string `json:"id,omitempty"`
+	Provider            *string `json:"providerId,omitempty"`
+	AuthenticatorConfig *string `json:"authenticatorConfig,omitempty"`
+	AuthenticatorFlow   *bool   `json:"authenticatorFlow,omitempty"`
+	AutheticatorFlow    *bool   `json:"autheticatorFlow,omitempty"`
+	FlowAlias           *string `json:"flowAlias,omitempty"`
+	Priority            *int    `json:"priority,omitempty"`
+	Requirement         *string `json:"requirement,omitempty"`
+	UserSetupAllowed    *bool   `json:"userSetupAllowed,omitempty"`
+}
+
 // MultiValuedHashMap represents something
 type MultiValuedHashMap struct {
 	Empty      *bool    `json:"empty,omitempty"`


Test patch:
diff --git a/client_test.go b/client_test.go
index 1d72ad4..83ab041 100644
--- a/client_test.go
+++ b/client_test.go
@@ -6130,3 +6130,107 @@ E8go1LcvbfHNyknHu2sptnRq55fHZSHr18vVsQRfDYMG</ds:X509Certificate>
 			expectedVal, expectedKey, actual[expectedKey])
 	}
 }
+
+func TestGocloak_GetAuthenticationFlows(t *testing.T) {
+	t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+	authFlows, err := client.GetAuthenticationFlows(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+	)
+	require.NoError(t, err, "Failed to fetch authentication flows")
+	t.Logf("authentication flows: %+v", authFlows)
+
+	FailRequest(client, nil, 1, 0)
+	_, err = client.GetAuthenticationFlows(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+	)
+	require.Error(t, err)
+}
+
+func TestGocloak_CreateAuthenticationFlowsAndCreateAuthenticationExecution(t *testing.T) {
+	t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+	authExec :=
+		gocloak.CreateAuthenticationExecutionRepresentation{
+			Provider: gocloak.StringP("idp-auto-link"),
+		}
+	authFlow := gocloak.AuthenticationFlowRepresentation{
+		Alias:       gocloak.StringP("testauthflow2"),
+		BuiltIn:     gocloak.BoolP(false),
+		Description: gocloak.StringP("my test description"),
+		TopLevel:    gocloak.BoolP(true),
+		ProviderID:  gocloak.StringP("basic-flow"),
+	}
+
+	err := client.CreateAuthenticationFlow(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		authFlow,
+	)
+	require.NoError(t, err, "Failed to create authentication flow")
+	t.Logf("authentication flows: %+v", authFlow)
+
+	err = client.CreateAuthenticationExecution(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		*authFlow.Alias,
+		authExec,
+	)
+	require.NoError(t, err, "Failed to create authentication execution")
+
+	authExecs, err := client.GetAuthenticationExecutions(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		*authFlow.Alias,
+	)
+
+	t.Logf("authentication executions: %+v", authExecs)
+	require.NoError(t, err, "Failed to get authentication executions")
+
+	authExecs[0].Requirement = gocloak.StringP("ALTERNATIVE")
+	err = client.UpdateAuthenticationExecution(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		*authFlow.Alias,
+		*authExecs[0],
+	)
+	require.NoError(t, err, "Failed to update authentication executions")
+	authExecs, err = client.GetAuthenticationExecutions(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		*authFlow.Alias,
+	)
+	require.NoError(t, err, "Failed to get authentication executions second time")
+	t.Logf("authentication executions after update: %+v", authExecs)
+
+	flows, err := client.GetAuthenticationFlows(context.Background(), token.AccessToken, cfg.GoCloak.Realm)
+	require.NoError(t, err, "Failed to get authentication flows")
+	deleted := false
+	for _, flow := range flows {
+		if flow.Alias != nil && *flow.Alias == "testauthflow2" {
+			err = client.DeleteAuthenticationFlow(
+				context.Background(),
+				token.AccessToken,
+				cfg.GoCloak.Realm,
+				*flow.ID,
+			)
+			require.NoError(t, err, "Failed to delete authentication flow")
+			deleted = true
+			break
+		}
+	}
+	require.True(t, deleted, "Failed to delete authentication flow, no flow was deleted")
+}