diff --git a/README.md b/README.md index b1d99fb8..301e6ce4 100644 --- a/README.md +++ b/README.md @@ -342,7 +342,14 @@ type GoCloak interface { MoveCredentialBehind(ctx context.Context, token, realm, userID, credentialID, newPreviousCredentialID string) error MoveCredentialToFirst(ctx context.Context, token, realm, userID, credentialID string) error - // *** Identity Providers *** +// *** Authentication Flows *** +GetAuthenticationFlows(ctx context.Context, token, realm string) ([]*AuthenticationFlowRepresentation, error) +GetAuthenticationFlow(ctx context.Context, token, realm string, authenticationFlowID string) (*AuthenticationFlowRepresentation, error) +CreateAuthenticationFlow(ctx context.Context, token, realm string, flow AuthenticationFlowRepresentation) error +UpdateAuthenticationFlow(ctx context.Context, token, realm string, flow AuthenticationFlowRepresentation, authenticationFlowID string) (*AuthenticationFlowRepresentation, error) +DeleteAuthenticationFlow(ctx context.Context, token, realm, flowID string) error + +// *** Identity Providers *** CreateIdentityProvider(ctx context.Context, token, realm string, providerRep IdentityProviderRepresentation) (string, error) GetIdentityProvider(ctx context.Context, token, realm, alias string) (*IdentityProviderRepresentation, error) diff --git a/client.go b/client.go index 7e3d213c..44d3c271 100644 --- a/client.go +++ b/client.go @@ -2365,6 +2365,20 @@ func (g *GoCloak) GetAuthenticationFlows(ctx context.Context, token, realm strin return result, nil } +// GetAuthenticationFlow get an authentication flow with the given ID +func (g *GoCloak) GetAuthenticationFlow(ctx context.Context, token, realm string, authenticationFlowID string) (*AuthenticationFlowRepresentation, error) { + const errMessage = "could not retrieve authentication flows" + var result *AuthenticationFlowRepresentation + resp, err := g.getRequestWithBearerAuth(ctx, token). + SetResult(&result). + Get(g.getAdminRealmURL(realm, "authentication", "flows", authenticationFlowID)) + + if err := checkForError(resp, err, errMessage); err != nil { + return nil, err + } + return result, nil +} + // CreateAuthenticationFlow creates a new Authentication flow in a realm func (g *GoCloak) CreateAuthenticationFlow(ctx context.Context, token, realm string, flow AuthenticationFlowRepresentation) error { const errMessage = "could not create authentication flows" @@ -2376,6 +2390,20 @@ func (g *GoCloak) CreateAuthenticationFlow(ctx context.Context, token, realm str return checkForError(resp, err, errMessage) } +// UpdateAuthenticationFlow a given Authentication Flow +func (g *GoCloak) UpdateAuthenticationFlow(ctx context.Context, token, realm string, flow AuthenticationFlowRepresentation, authenticationFlowID string) (*AuthenticationFlowRepresentation, error) { + const errMessage = "could not create authentication flows" + var result *AuthenticationFlowRepresentation + resp, err := g.getRequestWithBearerAuth(ctx, token). + SetResult(&result).SetBody(flow). + Put(g.getAdminRealmURL(realm, "authentication", "flows", authenticationFlowID)) + + if err = checkForError(resp, err, errMessage); err != nil { + return nil, err + } + return result, nil +} + // DeleteAuthenticationFlow deletes a flow in a realm with the given ID func (g *GoCloak) DeleteAuthenticationFlow(ctx context.Context, token, realm, flowID string) error { const errMessage = "could not delete authentication flows" diff --git a/client_test.go b/client_test.go index 87d14648..256587e1 100644 --- a/client_test.go +++ b/client_test.go @@ -6524,6 +6524,7 @@ func TestGocloak_CreateAuthenticationFlowsAndCreateAuthenticationExecutionAndFlo Description: gocloak.StringP("my test description"), TopLevel: gocloak.BoolP(true), ProviderID: gocloak.StringP("basic-flow"), + ID: gocloak.StringP("testauthflow2id"), } authExecFlow := gocloak.CreateAuthenticationExecutionFlowRepresentation{ @@ -6622,23 +6623,34 @@ func TestGocloak_CreateAuthenticationFlowsAndCreateAuthenticationExecutionAndFlo require.True(t, execDeleted, "Failed to delete authentication execution, no execution was deleted") require.True(t, execFlowFound, "Failed to find authentication execution flow") - 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") + authFlow.Description = gocloak.StringP("my-new-description") + _, err = client.UpdateAuthenticationFlow( + context.Background(), + token.AccessToken, + cfg.GoCloak.Realm, + authFlow, + *authFlow.ID, + ) + + require.NoError(t, err, "Failed to update authentication flow") + t.Logf("updated authentication flow: %+v", authFlow) + + retrievedAuthFlow, err := client.GetAuthenticationFlow( + context.Background(), + token.AccessToken, + cfg.GoCloak.Realm, + *authFlow.ID, + ) + require.NoError(t, err, "Failed to fetch authentication flow") + t.Logf("retrieved authentication flow: %+v", retrievedAuthFlow) + require.Equal(t, "my-new-description", gocloak.PString(retrievedAuthFlow.Description)) + err = client.DeleteAuthenticationFlow( + context.Background(), + token.AccessToken, + cfg.GoCloak.Realm, + *retrievedAuthFlow.ID, + ) + require.NoError(t, err, "Failed to delete authentication flow") } func TestGocloak_CreateAndGetRequiredAction(t *testing.T) { diff --git a/docker-compose.yml b/docker-compose.yml index 4f02c3e4..b422ddbb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,8 +9,14 @@ services: KEYCLOAK_PASSWORD: secret KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: secret + KC_HEALTH_ENABLED: "true" ports: - "8080:8080" + healthcheck: + test: curl --fail --silent http://localhost:8080/health/ready 2>&1 || exit 1 + interval: 10s + timeout: 10s + retries: 5 volumes: - ./testdata/gocloak-realm.json:/opt/keycloak/data/import/gocloak-realm.json entrypoint: ["/opt/keycloak/bin/kc.sh", "start-dev --features=preview --import-realm"]