Skip to content

Commit ca87487

Browse files
authored
fix: redirect after email actions (#139)
* fix: redirect after email actions * chore: add condition in case the realm name is not yet present in the accountinfo object * fix: update Dockerfile base image and clean up realm subroutine by removing unused repository manifest * feat: add baseDomain to subroutine for dynamic redirect URI construction
1 parent 30cdbfa commit ca87487

File tree

7 files changed

+26
-103
lines changed

7 files changed

+26
-103
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Build the manager binary
2-
FROM golang:1.25.3-bookworm AS builder
2+
FROM golang:1.25 AS builder
33
ARG TARGETOS
44
ARG TARGETARCH
55

cmd/operator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
kcpcorev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1"
1313
"github.com/kcp-dev/logicalcluster/v3"
1414
"github.com/kcp-dev/multicluster-provider/apiexport"
15+
openfgav1 "github.com/openfga/api/proto/openfga/v1"
1516
accountsv1alpha1 "github.com/platform-mesh/account-operator/api/v1alpha1"
1617
"google.golang.org/grpc"
1718
"google.golang.org/grpc/credentials/insecure"
@@ -28,7 +29,6 @@ import (
2829
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
2930
mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager"
3031

31-
openfgav1 "github.com/openfga/api/proto/openfga/v1"
3232
platformeshcontext "github.com/platform-mesh/golang-commons/context"
3333
"github.com/platform-mesh/golang-commons/logger"
3434
"github.com/platform-mesh/golang-commons/sentry"

internal/subroutine/invite/subroutine.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const (
3131

3232
type subroutine struct {
3333
keycloakBaseURL string
34+
baseDomain string
3435
keycloak *http.Client
3536
mgr mcmanager.Manager
3637
}
@@ -62,6 +63,7 @@ func New(ctx context.Context, cfg *config.Config, mgr mcmanager.Manager, pwd str
6263

6364
return &subroutine{
6465
keycloakBaseURL: cfg.Invite.KeycloakBaseURL,
66+
baseDomain: cfg.BaseDomain,
6567
mgr: mgr,
6668
keycloak: config.Client(ctx, token),
6769
}, nil
@@ -110,6 +112,10 @@ func (s *subroutine) Process(ctx context.Context, instance runtimeobject.Runtime
110112
}
111113

112114
realm := accountInfo.Spec.Organization.Name
115+
if realm == "" {
116+
log.Error().Msg("Organization name is empty in AccountInfo")
117+
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("organization name is empty in AccountInfo"), true, false)
118+
}
113119

114120
res, err := s.keycloak.Get(fmt.Sprintf("%s/admin/realms/%s/users?%s", s.keycloakBaseURL, realm, v.Encode()))
115121
if err != nil { // coverage-ignore
@@ -175,8 +181,13 @@ func (s *subroutine) Process(ctx context.Context, instance runtimeobject.Runtime
175181

176182
log.Debug().Str("email", invite.Spec.Email).Str("id", newUser.ID).Msg("User created")
177183

178-
req, err := http.NewRequestWithContext(ctx, http.MethodPut, fmt.Sprintf("%s/admin/realms/%s/users/%s/execute-actions-email", s.keycloakBaseURL, realm, newUser.ID), http.NoBody)
179-
if err != nil {
184+
queryParams := url.Values{
185+
"redirect_uri": {"https://%s.%s", realm, s.baseDomain},
186+
"client_id": {realm},
187+
}
188+
189+
req, err := http.NewRequestWithContext(ctx, http.MethodPut, fmt.Sprintf("%s/admin/realms/%s/users/%s/execute-actions-email?%s", s.keycloakBaseURL, realm, newUser.ID, queryParams.Encode()), http.NoBody)
190+
if err != nil { // coverage-ignore
180191
return ctrl.Result{}, errors.NewOperatorError(err, true, true)
181192
}
182193

internal/subroutine/invite/subroutine_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ func TestSubroutineProcess(t *testing.T) {
224224
KeycloakBaseURL: srv.URL,
225225
KeycloakClientID: "admin-cli",
226226
},
227+
BaseDomain: "portal.dev.local:8443",
227228
}, mgr, "password")
228229
assert.NoError(t, err)
229230

internal/subroutine/manifests/organizationIdp/repository.yaml

Lines changed: 0 additions & 10 deletions
This file was deleted.

internal/subroutine/realm.go

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ import (
2424
)
2525

2626
var (
27-
//go:embed manifests/organizationIdp/repository.yaml
28-
repository string
2927

3028
//go:embed manifests/organizationIdp/helmrelease.yaml
3129
helmRelease string
@@ -62,14 +60,6 @@ func (r *realmSubroutine) Finalize(ctx context.Context, instance runtimeobject.R
6260
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get workspace path"), true, false)
6361
}
6462

65-
ociObj, err := unstructuredFromString(repository, nil, log)
66-
if err != nil {
67-
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to load OCI repository manifest: %w", err), true, true)
68-
}
69-
if err := r.k8s.Delete(ctx, &ociObj); err != nil {
70-
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to delete OCI repository: %w", err), true, true)
71-
}
72-
7363
helmObj, err := unstructuredFromString(helmRelease, nil, log)
7464
if err != nil {
7565
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to load HelmRelease manifest: %w", err), true, true)
@@ -100,7 +90,7 @@ func (r *realmSubroutine) Process(ctx context.Context, instance runtimeobject.Ru
10090
"client": map[string]any{
10191
"name": workspaceName,
10292
"displayName": workspaceName,
103-
"validRedirectUris": append(r.cfg.IDP.AdditionalRedirectURLs, fmt.Sprintf("https://%s.%s/callback*", workspaceName, r.baseDomain)),
93+
"validRedirectUris": append(r.cfg.IDP.AdditionalRedirectURLs, fmt.Sprintf("https://%s.%s/*", workspaceName, r.baseDomain)),
10494
},
10595
"organization": map[string]any{
10696
"domain": "example.com", // TODO: change
@@ -151,11 +141,6 @@ func (r *realmSubroutine) Process(ctx context.Context, instance runtimeobject.Ru
151141
values := apiextensionsv1.JSON{Raw: marshalledPatch}
152142
releaseName := fmt.Sprintf("%s-idp", workspaceName)
153143

154-
err = applyManifestWithMergedValues(ctx, repository, r.k8s, nil)
155-
if err != nil {
156-
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to create OCI repository: %w", err), true, true)
157-
}
158-
159144
err = applyReleaseWithValues(ctx, helmRelease, r.k8s, values, releaseName)
160145
if err != nil {
161146
return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to create HelmRelease: %w", err), true, true)
@@ -234,18 +219,3 @@ func ReplaceTemplate(templateData map[string]string, templateBytes []byte) ([]by
234219
}
235220
return result.Bytes(), nil
236221
}
237-
238-
func applyManifestWithMergedValues(ctx context.Context, manifest string, k8sClient client.Client, templateData map[string]string) error {
239-
log := logger.LoadLoggerFromContext(ctx)
240-
241-
obj, err := unstructuredFromString(manifest, templateData, log)
242-
if err != nil {
243-
return err
244-
}
245-
246-
err = k8sClient.Patch(ctx, &obj, client.Apply, client.FieldOwner("security-operator"))
247-
if err != nil {
248-
return errors.Wrap(err, "Failed to apply manifest (%s/%s)", obj.GetKind(), obj.GetName())
249-
}
250-
return nil
251-
}

internal/subroutine/realm_test.go

Lines changed: 9 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,6 @@ import (
1919
)
2020

2121
const (
22-
repoYAML = `
23-
apiVersion: v1
24-
kind: Config
25-
metadata:
26-
name: repo-min
27-
spec:
28-
foo: bar
29-
`
3022
helmReleaseYAML = `
3123
apiVersion: helm.toolkit.fluxcd.io/v2beta1
3224
kind: HelmRelease
@@ -100,24 +92,6 @@ spec:
10092
},
10193
true,
10294
},
103-
{"manifest: invalid YAML", "manifest", "this: is: : invalid: yaml", nil, true},
104-
{
105-
"manifest: patch error wrapped",
106-
"manifest",
107-
trim(`
108-
apiVersion: v1
109-
kind: Config
110-
metadata:
111-
name: test-manifest
112-
spec:
113-
foo: bar
114-
`),
115-
func(m *mocks.MockClient) {
116-
m.EXPECT().Patch(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
117-
Return(errors.New("simulated patch error for manifest")).Once()
118-
},
119-
true,
120-
},
12195
}
12296

12397
for _, tc := range cases {
@@ -133,13 +107,6 @@ spec:
133107
} else {
134108
require.NoError(t, err)
135109
}
136-
case "manifest":
137-
err := applyManifestWithMergedValues(ctx, tc.content, clientMock, nil)
138-
if tc.expectErr {
139-
require.Error(t, err)
140-
} else {
141-
require.NoError(t, err)
142-
}
143110
default:
144111
t.Fatalf("unknown call type %q", tc.call)
145112
}
@@ -148,20 +115,13 @@ spec:
148115
}
149116

150117
func TestRealmSubroutine_ProcessAndFinalize(t *testing.T) {
151-
origRepo, origHR := repository, helmRelease
152-
defer func() { repository, helmRelease = origRepo, origHR }()
118+
origHR := helmRelease
119+
defer func() { helmRelease = origHR }()
153120

154121
t.Run("Process", func(t *testing.T) {
155122
t.Run("success create repo then helmrelease", func(t *testing.T) {
156123
t.Parallel()
157124
clientMock := newClientMock(t, func(m *mocks.MockClient) {
158-
m.EXPECT().Patch(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
159-
RunAndReturn(func(_ context.Context, obj client.Object, _ client.Patch, _ ...client.PatchOption) error {
160-
_, ok := obj.(*unstructured.Unstructured)
161-
require.True(t, ok)
162-
return nil
163-
}).Once()
164-
165125
m.EXPECT().Patch(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
166126
RunAndReturn(func(_ context.Context, obj client.Object, _ client.Patch, _ ...client.PatchOption) error {
167127
hr := obj.(*unstructured.Unstructured)
@@ -174,7 +134,7 @@ func TestRealmSubroutine_ProcessAndFinalize(t *testing.T) {
174134
}).Once()
175135
})
176136

177-
repository, helmRelease = trim(repoYAML), trim(helmReleaseYAML)
137+
helmRelease = trim(helmReleaseYAML)
178138

179139
rs := NewRealmSubroutine(clientMock, &config.Config{}, baseDomain)
180140
lc := &kcpv1alpha1.LogicalCluster{}
@@ -188,7 +148,7 @@ func TestRealmSubroutine_ProcessAndFinalize(t *testing.T) {
188148
t.Run("success create with SMTP config", func(t *testing.T) {
189149
t.Parallel()
190150
clientMock := newClientMock(t, func(m *mocks.MockClient) {
191-
m.EXPECT().Patch(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Twice()
151+
m.EXPECT().Patch(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
192152
})
193153

194154
cfg := &config.Config{}
@@ -205,14 +165,14 @@ func TestRealmSubroutine_ProcessAndFinalize(t *testing.T) {
205165
require.Equal(t, ctrl.Result{}, res)
206166
})
207167

208-
t.Run("oci repository apply fails", func(t *testing.T) {
168+
t.Run("helmrelease apply fails", func(t *testing.T) {
209169
t.Parallel()
210170
clientMock := newClientMock(t, func(m *mocks.MockClient) {
211171
m.EXPECT().Patch(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
212-
Return(errors.New("simulated patch failure for OCI repo")).Once()
172+
Return(errors.New("simulated patch failure for HelmRelease")).Once()
213173
})
214174

215-
repository, helmRelease = trim(repoYAML), trim(helmReleaseYAML)
175+
helmRelease = trim(helmReleaseYAML)
216176
rs := NewRealmSubroutine(clientMock, &config.Config{}, baseDomain)
217177
lc := &kcpv1alpha1.LogicalCluster{}
218178
lc.Annotations = map[string]string{"kcp.io/path": "root:orgs:test"}
@@ -250,26 +210,17 @@ func TestRealmSubroutine_ProcessAndFinalize(t *testing.T) {
250210
expectErr bool
251211
expectedResult ctrl.Result
252212
}{
253-
{
254-
"OCI delete error",
255-
func(m *mocks.MockClient) {
256-
m.EXPECT().Delete(mock.Anything, mock.Anything).Return(errors.New("failed to delete oci repository")).Once()
257-
},
258-
true,
259-
ctrl.Result{},
260-
},
261213
{
262214
"HelmRelease delete error",
263215
func(m *mocks.MockClient) {
264-
m.EXPECT().Delete(mock.Anything, mock.Anything).Return(nil).Once()
265216
m.EXPECT().Delete(mock.Anything, mock.Anything).Return(errors.New("failed to delete helmRelease")).Once()
266217
},
267218
true,
268219
ctrl.Result{},
269220
},
270221
{
271-
"Both deletes succeed",
272-
func(m *mocks.MockClient) { m.EXPECT().Delete(mock.Anything, mock.Anything).Return(nil).Twice() },
222+
"Delete succeeds",
223+
func(m *mocks.MockClient) { m.EXPECT().Delete(mock.Anything, mock.Anything).Return(nil).Once() },
273224
false,
274225
ctrl.Result{},
275226
},

0 commit comments

Comments
 (0)