Skip to content

Commit 5e27be8

Browse files
committed
feat: Add childRequirement for KeycloakAuthFlow (#82)
1 parent 7b70159 commit 5e27be8

File tree

8 files changed

+108
-4
lines changed

8 files changed

+108
-4
lines changed

api/v1/keycloakauthflow_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ type KeycloakAuthFlowSpec struct {
4545
// ChildType is type for auth flow if it has a parent, available options: basic-flow, form-flow
4646
// +optional
4747
ChildType string `json:"childType,omitempty"`
48+
49+
// ChildRequirement is requirement for child execution. Available options: REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL.
50+
// +optional
51+
ChildRequirement string `json:"childRequirement,omitempty"`
4852
}
4953

5054
// AuthenticationExecution defines keycloak authentication execution.

config/crd/bases/v1.edp.epam.com_keycloakauthflows.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ spec:
9191
builtIn:
9292
description: BuiltIn is true if this is built-in auth flow.
9393
type: boolean
94+
childRequirement:
95+
description: 'ChildRequirement is requirement for child execution.
96+
Available options: REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL.'
97+
type: string
9498
childType:
9599
description: 'ChildType is type for auth flow if it has a parent,
96100
available options: basic-flow, form-flow'

controllers/keycloakauthflow/keycloakauthflow_controller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ func authFlowSpecToAdapterAuthFlow(spec *keycloakApi.KeycloakAuthFlowSpec) *adap
198198
AuthenticationExecutions: make([]adapter.AuthenticationExecution, 0, len(spec.AuthenticationExecutions)),
199199
ParentName: spec.ParentName,
200200
ChildType: spec.ChildType,
201+
ChildRequirement: spec.ChildRequirement,
201202
}
202203

203204
for _, ae := range spec.AuthenticationExecutions {

controllers/keycloakauthflow/keycloakrauthflow_controller_integration_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,56 @@ var _ = Describe("KeycloakAuthFlow controller", Ordered, func() {
125125
g.Expect(createdAuthFlow.Status.Value).Should(ContainSubstring("unable to sync auth flow"))
126126
}).WithTimeout(time.Second * 10).WithPolling(time.Second).Should(Succeed())
127127
})
128+
It("Should create child KeycloakAuthFlow", func() {
129+
By("Creating a parent KeycloakAuthFlow")
130+
parentAuthFlow := &keycloakApi.KeycloakAuthFlow{
131+
ObjectMeta: metav1.ObjectMeta{
132+
Name: "test-auth-flow-parent",
133+
Namespace: ns,
134+
},
135+
Spec: keycloakApi.KeycloakAuthFlowSpec{
136+
RealmRef: common.RealmRef{
137+
Kind: keycloakApi.KeycloakRealmKind,
138+
Name: KeycloakRealmCR,
139+
},
140+
Alias: "test-auth-flow-parent",
141+
Description: "test-auth-flow-parent",
142+
ProviderID: "basic-flow",
143+
TopLevel: true,
144+
},
145+
}
146+
Expect(k8sClient.Create(ctx, parentAuthFlow)).Should(Succeed())
147+
Eventually(func(g Gomega) {
148+
createdParentAuthFlow := &keycloakApi.KeycloakAuthFlow{}
149+
err := k8sClient.Get(ctx, types.NamespacedName{Name: createdParentAuthFlow.Name, Namespace: ns}, createdParentAuthFlow)
150+
g.Expect(err).ShouldNot(HaveOccurred())
151+
g.Expect(createdParentAuthFlow.Status.Value).Should(Equal(helper.StatusOK))
152+
}).WithTimeout(time.Second * 20).WithPolling(time.Second).Should(Succeed())
153+
By("Creating a child KeycloakAuthFlow")
154+
childAuthFlow := &keycloakApi.KeycloakAuthFlow{
155+
ObjectMeta: metav1.ObjectMeta{
156+
Name: "test-auth-flow-child",
157+
Namespace: ns,
158+
},
159+
Spec: keycloakApi.KeycloakAuthFlowSpec{
160+
RealmRef: common.RealmRef{
161+
Kind: keycloakApi.KeycloakRealmKind,
162+
Name: KeycloakRealmCR,
163+
},
164+
Alias: "test-auth-flow-child",
165+
Description: "test-auth-flow-child",
166+
ProviderID: "basic-flow",
167+
ParentName: parentAuthFlow.Name,
168+
ChildType: "basic-flow",
169+
ChildRequirement: "REQUIRED",
170+
},
171+
}
172+
Expect(k8sClient.Create(ctx, childAuthFlow)).Should(Succeed())
173+
Eventually(func(g Gomega) {
174+
createdChildAuthFlow := &keycloakApi.KeycloakAuthFlow{}
175+
err := k8sClient.Get(ctx, types.NamespacedName{Name: createdChildAuthFlow.Name, Namespace: ns}, createdChildAuthFlow)
176+
g.Expect(err).ShouldNot(HaveOccurred())
177+
g.Expect(createdChildAuthFlow.Status.Value).Should(Equal(helper.StatusOK))
178+
}).WithTimeout(time.Second * 20).WithPolling(time.Second).Should(Succeed())
179+
})
128180
})

deploy-templates/crds/v1.edp.epam.com_keycloakauthflows.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ spec:
9191
builtIn:
9292
description: BuiltIn is true if this is built-in auth flow.
9393
type: boolean
94+
childRequirement:
95+
description: 'ChildRequirement is requirement for child execution.
96+
Available options: REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL.'
97+
type: string
9498
childType:
9599
description: 'ChildType is type for auth flow if it has a parent,
96100
available options: basic-flow, form-flow'

docs/api.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,13 @@ KeycloakAuthFlowSpec defines the desired state of KeycloakAuthFlow.
890890
AuthenticationExecutions is list of authentication executions for this auth flow.<br/>
891891
</td>
892892
<td>false</td>
893+
</tr><tr>
894+
<td><b>childRequirement</b></td>
895+
<td>string</td>
896+
<td>
897+
ChildRequirement is requirement for child execution. Available options: REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL.<br/>
898+
</td>
899+
<td>false</td>
893900
</tr><tr>
894901
<td><b>childType</b></td>
895902
<td>string</td>

pkg/client/keycloak/adapter/gocloak_adapter_auth_flow.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"github.com/pkg/errors"
1414
)
1515

16+
var errAuthFlowNotFound = NotFoundError("auth flow not found")
17+
1618
type KeycloakAuthFlow struct {
1719
ID string `json:"id,omitempty"`
1820
Alias string `json:"alias"`
@@ -22,6 +24,7 @@ type KeycloakAuthFlow struct {
2224
BuiltIn bool `json:"builtIn"`
2325
ParentName string `json:"-"`
2426
ChildType string `json:"-"`
27+
ChildRequirement string `json:"-"`
2528
AuthenticationExecutions []AuthenticationExecution `json:"-"`
2629
}
2730

@@ -172,6 +175,20 @@ func (a GoCloakAdapter) syncBaseAuthFlow(realmName string, flow *KeycloakAuthFlo
172175

173176
authFlowID = id
174177
} else {
178+
if flow.ParentName != "" && flow.ChildRequirement != "" {
179+
exec, err := a.getFlowExecution(realmName, flow)
180+
if err != nil {
181+
return "", err
182+
}
183+
184+
// We cant set child flow requirement during creation, so we need to update it.
185+
exec.Requirement = flow.ChildRequirement
186+
187+
if err := a.updateFlowExecution(realmName, flow.ParentName, exec); err != nil {
188+
return "", err
189+
}
190+
}
191+
175192
if err := a.clearFlowExecutions(realmName, flow.Alias); err != nil {
176193
return "", errors.Wrap(err, "unable to clear flow executions")
177194
}
@@ -269,7 +286,7 @@ func (a GoCloakAdapter) getFlowExecutionID(realmName string, flow *KeycloakAuthF
269286
}
270287
}
271288

272-
return "", NotFoundError("auth flow not found")
289+
return "", errAuthFlowNotFound
273290
}
274291

275292
func (a GoCloakAdapter) getAuthFlowID(realmName string, flow *KeycloakAuthFlow) (string, error) {
@@ -285,7 +302,7 @@ func (a GoCloakAdapter) getAuthFlowID(realmName string, flow *KeycloakAuthFlow)
285302
}
286303
}
287304

288-
return "", NotFoundError("auth flow not found")
305+
return "", errAuthFlowNotFound
289306
}
290307

291308
flows, err := a.getRealmAuthFlows(realmName)
@@ -299,7 +316,22 @@ func (a GoCloakAdapter) getAuthFlowID(realmName string, flow *KeycloakAuthFlow)
299316
}
300317
}
301318

302-
return "", NotFoundError("auth flow not found")
319+
return "", errAuthFlowNotFound
320+
}
321+
322+
func (a GoCloakAdapter) getFlowExecution(realmName string, flow *KeycloakAuthFlow) (*FlowExecution, error) {
323+
execs, err := a.getFlowExecutions(realmName, flow.ParentName)
324+
if err != nil {
325+
return nil, fmt.Errorf("unable to get auth flow executions: %w", err)
326+
}
327+
328+
for i := range execs {
329+
if execs[i].DisplayName == flow.Alias {
330+
return &execs[i], nil
331+
}
332+
}
333+
334+
return nil, errAuthFlowNotFound
303335
}
304336

305337
func (a GoCloakAdapter) getRealmAuthFlows(realmName string) ([]KeycloakAuthFlow, error) {

pkg/client/keycloak/adapter/gocloak_adapter_auth_flow_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ func (e *ExecFlowTestSuite) TestGetAuthFlowID() {
233233
id, err := e.adapter.getAuthFlowID(e.realmName, &flow)
234234

235235
assert.NoError(e.T(), err)
236-
assert.Equal(e.T(), id, flowID)
236+
assert.Equal(e.T(), flowID, id)
237237
}
238238

239239
func (e *ExecFlowTestSuite) TestSetRealmBrowserFlow() {

0 commit comments

Comments
 (0)