Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cmd/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ var (

type NewLogicalClusterClientFunc func(clusterKey logicalcluster.Name) (client.Client, error)


func logicalClusterClientFromKey(config *rest.Config, log *logger.Logger) NewLogicalClusterClientFunc {
return func(clusterKey logicalcluster.Name) (client.Client, error) {
cfg := rest.CopyConfig(config)
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Config struct {
DomainCALookup bool `mapstructure:"domain-ca-lookup" default:"false"`
MigrateAuthorizationModels bool `mapstructure:"migrate-authorization-models" default:"false"`
HttpClientTimeoutSeconds int `mapstructure:"http-client-timeout-seconds" default:"30"`
SetDefaultPassword bool `mapstructure:"set-default-password" default:"false"`
IDP struct {
// SMTP settings
SMTPServer string `mapstructure:"idp-smtp-server"`
Expand Down
45 changes: 33 additions & 12 deletions internal/subroutine/invite/subroutine.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,30 @@ import (
const (
RequiredActionVerifyEmail string = "VERIFY_EMAIL"
RequiredActionUpdatePassword string = "UPDATE_PASSWORD"
UserDefaultPasswordType string = "password"
UserDefaultPasswordValue string = "password"
)

type subroutine struct {
keycloakBaseURL string
baseDomain string
keycloak *http.Client
mgr mcmanager.Manager
keycloakBaseURL string
baseDomain string
keycloak *http.Client
mgr mcmanager.Manager
setDefaultPassword bool
}

type keycloakUser struct {
ID string `json:"id,omitempty"`
Email string `json:"email,omitempty"`
RequiredActions []string `json:"requiredActions,omitempty"`
Enabled bool `json:"enabled,omitempty"`
ID string `json:"id,omitempty"`
Email string `json:"email,omitempty"`
RequiredActions []string `json:"requiredActions,omitempty"`
Enabled bool `json:"enabled,omitempty"`
Credentials []keycloakCredential `json:"credentials,omitempty"`
}

type keycloakCredential struct {
Type string `json:"type"`
Value string `json:"value"`
Temporary bool `json:"temporary"`
}

type keycloakClient struct {
Expand All @@ -64,10 +74,11 @@ func New(ctx context.Context, cfg *config.Config, mgr mcmanager.Manager) (*subro
httpClient := cCfg.Client(ctx)

return &subroutine{
keycloakBaseURL: cfg.Invite.KeycloakBaseURL,
baseDomain: cfg.BaseDomain,
mgr: mgr,
keycloak: httpClient,
keycloakBaseURL: cfg.Invite.KeycloakBaseURL,
baseDomain: cfg.BaseDomain,
mgr: mgr,
keycloak: httpClient,
setDefaultPassword: cfg.SetDefaultPassword,
}, nil
}

Expand Down Expand Up @@ -181,6 +192,16 @@ func (s *subroutine) Process(ctx context.Context, instance runtimeobject.Runtime
Enabled: true,
}

if s.setDefaultPassword {
newUser.Credentials = []keycloakCredential{
{
Type: UserDefaultPasswordType,
Value: UserDefaultPasswordValue,
Temporary: true,
},
}
}

var buffer bytes.Buffer
if err = json.NewEncoder(&buffer).Encode(&newUser); err != nil { // coverage-ignore
return ctrl.Result{}, errors.NewOperatorError(err, true, true)
Expand Down
94 changes: 92 additions & 2 deletions internal/subroutine/invite/subroutine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,95 @@ func TestSubroutineProcess(t *testing.T) {
testCases := []struct {
desc string
obj runtimeobject.RuntimeObject
config *config.Config
setupK8sMocks func(m *mocks.MockClient)
setupKeycloakMocks func(mux *http.ServeMux)
expectErr bool
}{
{
desc: "user created with default password",
obj: &v1alpha1.Invite{
Spec: v1alpha1.InviteSpec{
Email: "password@acme.corp",
},
},
config: &config.Config{
SetDefaultPassword: true,
},
setupK8sMocks: func(m *mocks.MockClient) {
m.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything).
RunAndReturn(func(ctx context.Context, nn types.NamespacedName, o client.Object, opts ...client.GetOption) error {
accountInfo := &accountsv1alpha1.AccountInfo{
Spec: accountsv1alpha1.AccountInfoSpec{
Organization: accountsv1alpha1.AccountLocation{
Name: "acme",
},
},
}
*o.(*accountsv1alpha1.AccountInfo) = *accountInfo
return nil
}).Once()

m.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "acme"}, mock.AnythingOfType("*v1alpha1.IdentityProviderConfiguration"), mock.Anything).
RunAndReturn(func(ctx context.Context, nn types.NamespacedName, o client.Object, opts ...client.GetOption) error {
idp := &v1alpha1.IdentityProviderConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: "acme",
},
Status: v1alpha1.IdentityProviderConfigurationStatus{
ManagedClients: map[string]v1alpha1.ManagedClient{
"acme": {
ClientID: "acme",
},
},
},
}
*o.(*v1alpha1.IdentityProviderConfiguration) = *idp
return nil
}).Once()
},
setupKeycloakMocks: func(mux *http.ServeMux) {
users := []map[string]any{}
mux.HandleFunc("GET /admin/realms/acme/users", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(&users)
assert.NoError(t, err)
})
mux.HandleFunc("POST /admin/realms/acme/users", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")

var body map[string]any
err := json.NewDecoder(r.Body).Decode(&body)
assert.NoError(t, err)

credentials, ok := body["credentials"].([]any)
assert.True(t, ok)
assert.Len(t, credentials, 1)
cred := credentials[0].(map[string]any)
assert.Equal(t, "password", cred["type"])
assert.Equal(t, "password", cred["value"])
assert.Equal(t, true, cred["temporary"])

w.WriteHeader(http.StatusCreated)
body["id"] = "1234"
users = append(users, body)
})
mux.HandleFunc("GET /admin/realms/acme/clients", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
clients := []map[string]any{
{"id": "client-uuid", "clientId": "acme"},
}
err := json.NewEncoder(w).Encode(&clients)
assert.NoError(t, err)
})
mux.HandleFunc("PUT /admin/realms/acme/users/{id}/execute-actions-email", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNoContent)
})
},
},
{
desc: "user created and invitation email sent",
obj: &v1alpha1.Invite{
Expand Down Expand Up @@ -416,13 +501,18 @@ func TestSubroutineProcess(t *testing.T) {
test.setupKeycloakMocks(mux)
}

s, err := invite.New(ctx, &config.Config{
cfg := &config.Config{
Invite: config.InviteConfig{
KeycloakBaseURL: srv.URL,
KeycloakClientID: "security-operator",
},
BaseDomain: "portal.dev.local:8443",
}, mgr)
}
if test.config != nil {
cfg.SetDefaultPassword = test.config.SetDefaultPassword
}

s, err := invite.New(ctx, cfg, mgr)
assert.NoError(t, err)

l := testlogger.New()
Expand Down
8 changes: 4 additions & 4 deletions internal/subroutine/workspace_authorization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ func TestWorkspaceAuthSubroutine_Process(t *testing.T) {
Annotations: map[string]string{},
},
},
cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"},
setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) {},
cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"},
setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) {},
expectError: true,
expectedResult: ctrl.Result{},
},
Expand All @@ -211,8 +211,8 @@ func TestWorkspaceAuthSubroutine_Process(t *testing.T) {
},
},
},
cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"},
setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) {},
cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"},
setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) {},
expectError: true,
expectedResult: ctrl.Result{},
},
Expand Down
Loading