Repository: Nerzal/gocloak
Pull request #398 (2023-01-11 11:43:39)

Add Get/Update Authentication Flow
Add Get/Update methods to Authentication Flow

Fixing #397 

![image](https://user-images.githubusercontent.com/13962066/211797948-b93428fd-281d-4406-91c8-441f7e76758e.png)



Patch:
diff --git a/Dockerfile b/Dockerfile
index fe10a52..9b88331 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM quay.io/keycloak/keycloak:latest
+FROM quay.io/keycloak/keycloak:19.0
 COPY testdata data/import
 WORKDIR /opt/keycloak
 ENV KC_HOSTNAME=localhost
@@ -8,4 +8,4 @@ ENV KEYCLOAK_ADMIN=admin
 ENV KEYCLOAK_ADMIN_PASSWORD=secret
 ENV KC_FEATURES=account-api,account2,authorization,client-policies,impersonation,docker,scripts,upload_scripts
 RUN /opt/keycloak/bin/kc.sh import --file /data/import/gocloak-realm.json
-ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
\ No newline at end of file
+ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
diff --git a/README.md b/README.md
index b1d99fb..301e6ce 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 7e3d213..7756022 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"
@@ -2553,12 +2581,18 @@ func (g *GoCloak) GetUsers(ctx context.Context, token, realm string, params GetU
 }
 
 // GetUsersByRoleName returns all users have a given role
-func (g *GoCloak) GetUsersByRoleName(ctx context.Context, token, realm, roleName string) ([]*User, error) {
+func (g *GoCloak) GetUsersByRoleName(ctx context.Context, token, realm, roleName string, params GetUsersByRoleParams) ([]*User, error) {
 	const errMessage = "could not get users by role name"
 
 	var result []*User
+	queryParams, err := GetQueryParams(params)
+	if err != nil {
+		return nil, errors.Wrap(err, errMessage)
+	}
+
 	resp, err := g.getRequestWithBearerAuth(ctx, token).
 		SetResult(&result).
+		SetQueryParams(queryParams).
 		Get(g.getAdminRealmURL(realm, "roles", roleName, "users"))
 
 	if err := checkForError(resp, err, errMessage); err != nil {
diff --git a/docker-compose.yml b/docker-compose.yml
index 4f02c3e..b422ddb 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"]


Test patch:
diff --git a/client_test.go b/client_test.go
index 87d1464..f4763b8 100644
--- a/client_test.go
+++ b/client_test.go
@@ -3450,10 +3450,20 @@ func Test_GetUsersByRoleName(t *testing.T) {
 		context.Background(),
 		token.AccessToken,
 		cfg.GoCloak.Realm,
-		roleName)
+		roleName,
+		gocloak.GetUsersByRoleParams{})
 	require.NoError(t, err)
 	require.NotEmpty(t, users)
 	require.Equal(t, userID, *users[0].ID)
+
+	users, err = client.GetUsersByRoleName(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		"unknown role",
+		gocloak.GetUsersByRoleParams{})
+	require.Error(t, err, "GetUsersByRoleName no error on unknown role")
+	require.Empty(t, users)
 }
 
 func Test_GetUsersByClientRoleName(t *testing.T) {
@@ -6524,6 +6534,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 +6633,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) {