From 039b8a997a2cac11aa510abd4a1ae8ee9958d35a Mon Sep 17 00:00:00 2001 From: sispeo <42068883+fperot74@users.noreply.github.com> Date: Tue, 3 Mar 2020 08:58:01 +0100 Subject: [PATCH] [CLOUDTRUST-2294] Add endpoint to manage back-office configuration --- api/management/api.go | 33 ++++ api/management/api_test.go | 52 +++++++ api/management/swagger-api_management.yaml | 81 ++++++++++ cmd/keycloakb/keycloak_bridge.go | 16 +- internal/dto/conf.go | 4 + internal/keycloakb/configdbinstrumenting.go | 32 +++- .../keycloakb/configdbinstrumenting_test.go | 97 ++++++++---- internal/keycloakb/configdbmodule.go | 77 +++++++++- internal/keycloakb/configdbmodule_test.go | 89 ++++++++++- internal/keycloakb/mock_test.go | 2 +- pkg/account/authorization.go | 5 +- pkg/account/component.go | 17 +-- pkg/management/authorization.go | 110 +++++++++----- pkg/management/authorization_test.go | 23 +++ pkg/management/component.go | 141 ++++++++++++++++-- pkg/management/component_test.go | 109 +++++++++++++- pkg/management/endpoint.go | 49 +++++- pkg/management/endpoint_test.go | 83 ++++++----- pkg/management/http.go | 1 + 19 files changed, 876 insertions(+), 145 deletions(-) create mode 100644 internal/dto/conf.go diff --git a/api/management/api.go b/api/management/api.go index 4b6cbf66..1dccb8c7 100644 --- a/api/management/api.go +++ b/api/management/api.go @@ -1,8 +1,11 @@ package management_api import ( + "encoding/json" "strconv" + errorhandler "github.com/cloudtrust/common-service/errors" + "github.com/cloudtrust/common-service/configuration" "github.com/cloudtrust/common-service/validation" internal "github.com/cloudtrust/keycloak-bridge/internal/messages" @@ -123,6 +126,17 @@ type RealmCustomConfiguration struct { RedirectSuccessfulRegistrationURL *string `json:"redirect_successful_registration_url"` } +// BackOffice configuration keys +const ( + BOConfKeyCustomers = "customers" + BOConfKeyTeams = "teams" +) + +var allowedBoConfKeys = map[string]bool{BOConfKeyCustomers: true, BOConfKeyTeams: true} + +// BackOfficeConfiguration type +type BackOfficeConfiguration map[string]map[string][]string + // FederatedIdentityRepresentation struct type FederatedIdentityRepresentation struct { UserID *string `json:"userID,omitempty"` @@ -380,6 +394,25 @@ func ConvertToKCFedID(fedID FederatedIdentityRepresentation) kc.FederatedIdentit // Validators +// NewBackOfficeConfigurationFromJSON creates and validates a new BackOfficeConfiguration from a JSON value +func NewBackOfficeConfigurationFromJSON(confJSON string) (BackOfficeConfiguration, error) { + var boConf BackOfficeConfiguration + var err = json.Unmarshal([]byte(confJSON), &boConf) + if err != nil { + return BackOfficeConfiguration{}, errorhandler.CreateBadRequestError(errorhandler.MsgErrInvalidQueryParam + ".body") + } + + var validator = validation.NewParameterValidator() + for _, realmConf := range boConf { + for keyBoConf, valueBoConf := range realmConf { + validator = validator.ValidateParameterIn("body.userType", &keyBoConf, allowedBoConfKeys, true). + ValidateParameterNotNil("body.allowedGroups", &valueBoConf) + } + } + + return boConf, validator.Status() +} + // Validate is a validator for UserRepresentation func (user UserRepresentation) Validate() error { var v = validation.NewParameterValidator(). diff --git a/api/management/api_test.go b/api/management/api_test.go index 83fd1067..20499d5e 100644 --- a/api/management/api_test.go +++ b/api/management/api_test.go @@ -266,6 +266,58 @@ func TestConvertRequiredAction(t *testing.T) { assert.Equal(t, raKc.DefaultAction, ConvertRequiredAction(&raKc).DefaultAction) } +func TestFederatedIdentityRepresentation(t *testing.T) { + var userID = "id" + var username = "uname" + var fir = FederatedIdentityRepresentation{UserID: &userID, Username: &username} + + t.Run("ConvertToKCFedID", func(t *testing.T) { + var res = ConvertToKCFedID(fir) + assert.Equal(t, userID, *res.UserId) + assert.Equal(t, username, *res.UserName) + }) + + t.Run("Validate - fails", func(t *testing.T) { + assert.NotNil(t, fir.Validate()) + }) + t.Run("Validate - success", func(t *testing.T) { + var uid = "abcdefgh-1234-1234-1234-1234abcd5678" + fir.UserID = &uid + assert.Nil(t, fir.Validate()) + }) +} + +func TestNewBackOfficeConfigurationFromJSON(t *testing.T) { + t.Run("Invalid JSON", func(t *testing.T) { + var _, err = NewBackOfficeConfigurationFromJSON(`{"shop":{"shelves":{"articles":{"books": [1, 2, 3], "chairs": [4, 5, 6]}}}}`) + assert.NotNil(t, err) + }) + t.Run("Valid JSON", func(t *testing.T) { + var boConf, err = NewBackOfficeConfigurationFromJSON(` + { + "realm1": { + "customers": [ "group1", "group2", "group4" ], + "teams": [ "group1", "group3" ] + } + } + `) + assert.Nil(t, err) + assert.Len(t, boConf["realm1"], 2) + assert.Len(t, boConf["realm1"]["customers"], 3) + assert.Len(t, boConf["realm1"]["teams"], 2) + }) + t.Run("Inalid configuration", func(t *testing.T) { + var _, err = NewBackOfficeConfigurationFromJSON(` + { + "not-a-valid-value": { + "my-realm": [] + } + } + `) + assert.NotNil(t, err) + }) +} + func TestValidateUserRepresentation(t *testing.T) { { user := createValidUserRepresentation() diff --git a/api/management/swagger-api_management.yaml b/api/management/swagger-api_management.yaml index 4d035791..21a3576f 100644 --- a/api/management/swagger-api_management.yaml +++ b/api/management/swagger-api_management.yaml @@ -968,6 +968,77 @@ paths: description: successful operation 400: description: invalid information provided (invalid client identifier or redirect URI not allowed for this client) + /realms/{realm}/backoffice-configuration: + get: + tags: + - Configuration + summary: Get the current backoffice configuration. Result is an aggregation of configurations from all the user's groups. + parameters: + - name: realm + in: path + description: realm name (not id!) + required: true + schema: + type: string + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/BackOfficeConfiguration' + /realms/{realm}/backoffice-configuration/groups: + get: + tags: + - Configuration + summary: Get the backoffice configuration for the given realmID/groupName + parameters: + - name: realm + in: path + description: realm name (not id!) + required: true + schema: + type: string + - name: groupName + in: query + description: group name + required: true + schema: + type: string + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/BackOfficeConfiguration' + put: + tags: + - Configuration + summary: Update the backoffice configuration for the given realmID/groupName + parameters: + - name: realm + in: path + description: realm name (not id!) + required: true + schema: + type: string + - name: groupName + in: query + description: group name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/BackOfficeConfiguration' + responses: + 200: + description: successful operation + 400: + description: invalid information provided /realms/{realm}/users/{userID}/federated-identity/{provider}: post: tags: @@ -1174,6 +1245,16 @@ components: type: string redirect_successful_registration_url: type: string + BackOfficeConfiguration: + type: object + additionalProperties: + type: object + items: + type: object + additionalProperties: + type: array + items: + type: string FederatedIdentity: type: object properties: diff --git a/cmd/keycloakb/keycloak_bridge.go b/cmd/keycloakb/keycloak_bridge.go index 3be0adb6..3aca519c 100644 --- a/cmd/keycloakb/keycloak_bridge.go +++ b/cmd/keycloakb/keycloak_bridge.go @@ -585,6 +585,10 @@ func main() { GetRealmCustomConfiguration: prepareEndpoint(management.MakeGetRealmCustomConfigurationEndpoint(keycloakComponent), "get_realm_custom_config_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]), UpdateRealmCustomConfiguration: prepareEndpoint(management.MakeUpdateRealmCustomConfigurationEndpoint(keycloakComponent), "update_realm_custom_config_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]), + GetRealmBackOfficeConfiguration: prepareEndpoint(management.MakeGetRealmBackOfficeConfigurationEndpoint(keycloakComponent), "get_realm_back_office_config_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]), + UpdateRealmBackOfficeConfiguration: prepareEndpoint(management.MakeUpdateRealmBackOfficeConfigurationEndpoint(keycloakComponent), "update_realm_back_office_config_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]), + GetUserRealmBackOfficeConfiguration: prepareEndpoint(management.MakeGetUserRealmBackOfficeConfigurationEndpoint(keycloakComponent), "get_user_realm_back_office_config_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]), + CreateShadowUser: prepareEndpoint(management.MakeCreateShadowUserEndpoint(keycloakComponent), "create_shadow_user_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]), } } @@ -598,7 +602,7 @@ func main() { eventsDBModule := configureEventsDbModule(baseEventsDBModule, influxMetrics, accountLogger, tracer) // module for retrieving the custom configuration - var configDBModule account.ConfigurationDBModule + var configDBModule keycloakb.ConfigurationDBModule { configDBModule = keycloakb.NewConfigurationDBModule(configurationRoDBConn, accountLogger) configDBModule = keycloakb.MakeConfigurationDBModuleInstrumentingMW(influxMetrics.NewHistogram("configDB_module"))(configDBModule) @@ -832,6 +836,10 @@ func main() { var getRealmCustomConfigurationHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.GetRealmCustomConfiguration) var updateRealmCustomConfigurationHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.UpdateRealmCustomConfiguration) + var getRealmBackOfficeConfigurationHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.GetRealmBackOfficeConfiguration) + var updateRealmBackOfficeConfigurationHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.UpdateRealmBackOfficeConfiguration) + var getUserRealmBackOfficeConfigurationHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.GetUserRealmBackOfficeConfiguration) + var createShadowUserHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.CreateShadowUser) // KYC handlers @@ -893,10 +901,14 @@ func main() { managementSubroute.Path("/realms/{realm}/groups/{groupID}/authorizations").Methods("GET").Handler(getAuthorizationsHandler) managementSubroute.Path("/realms/{realm}/groups/{groupID}/authorizations").Methods("PUT").Handler(updateAuthorizationsHandler) - // custom configuration par realm + // custom configuration per realm managementSubroute.Path("/realms/{realm}/configuration").Methods("GET").Handler(getRealmCustomConfigurationHandler) managementSubroute.Path("/realms/{realm}/configuration").Methods("PUT").Handler(updateRealmCustomConfigurationHandler) + managementSubroute.Path("/realms/{realm}/backoffice-configuration/groups").Methods("GET").Handler(getRealmBackOfficeConfigurationHandler) + managementSubroute.Path("/realms/{realm}/backoffice-configuration/groups").Methods("PUT").Handler(updateRealmBackOfficeConfigurationHandler) + managementSubroute.Path("/realms/{realm}/backoffice-configuration").Methods("GET").Handler(getUserRealmBackOfficeConfigurationHandler) + // brokering - shadow users managementSubroute.Path("/realms/{realm}/users/{userID}/federated-identity/{provider}").Methods("POST").Handler(createShadowUserHandler) diff --git a/internal/dto/conf.go b/internal/dto/conf.go new file mode 100644 index 00000000..479a17d8 --- /dev/null +++ b/internal/dto/conf.go @@ -0,0 +1,4 @@ +package dto + +// BackOfficeConfiguration definition +type BackOfficeConfiguration map[string]map[string][]string diff --git a/internal/keycloakb/configdbinstrumenting.go b/internal/keycloakb/configdbinstrumenting.go index e417b97c..faebdfc4 100644 --- a/internal/keycloakb/configdbinstrumenting.go +++ b/internal/keycloakb/configdbinstrumenting.go @@ -8,6 +8,7 @@ import ( "github.com/cloudtrust/common-service/configuration" "github.com/cloudtrust/common-service/database" cm "github.com/cloudtrust/common-service/metrics" + "github.com/cloudtrust/keycloak-bridge/internal/dto" ) // Instrumenting middleware at module level. @@ -19,8 +20,11 @@ type configDBModuleInstrumentingMW struct { // ConfigurationDBModule is the interface of the configuration module. type ConfigurationDBModule interface { NewTransaction(context context.Context) (database.Transaction, error) - StoreOrUpdate(context.Context, string, configuration.RealmConfiguration) error + StoreOrUpdateConfiguration(context.Context, string, configuration.RealmConfiguration) error GetConfiguration(context.Context, string) (configuration.RealmConfiguration, error) + GetBackOfficeConfiguration(context.Context, string, []string) (dto.BackOfficeConfiguration, error) + DeleteBackOfficeConfiguration(context.Context, string, string, string, *string, *string) error + InsertBackOfficeConfiguration(context.Context, string, string, string, string, []string) error GetAuthorizations(context context.Context, realmID string, groupName string) ([]configuration.Authorization, error) CreateAuthorization(context context.Context, authz configuration.Authorization) error DeleteAuthorizations(context context.Context, realmID string, groupName string) error @@ -46,11 +50,11 @@ func (m *configDBModuleInstrumentingMW) NewTransaction(ctx context.Context) (dat } // configDBModuleInstrumentingMW implements Module. -func (m *configDBModuleInstrumentingMW) StoreOrUpdate(ctx context.Context, realmName string, config configuration.RealmConfiguration) error { +func (m *configDBModuleInstrumentingMW) StoreOrUpdateConfiguration(ctx context.Context, realmName string, config configuration.RealmConfiguration) error { defer func(begin time.Time) { m.h.With("correlation_id", ctx.Value(cs.CtContextCorrelationID).(string)).Observe(time.Since(begin).Seconds()) }(time.Now()) - return m.next.StoreOrUpdate(ctx, realmName, config) + return m.next.StoreOrUpdateConfiguration(ctx, realmName, config) } // configDBModuleInstrumentingMW implements Module. @@ -61,6 +65,28 @@ func (m *configDBModuleInstrumentingMW) GetConfiguration(ctx context.Context, re return m.next.GetConfiguration(ctx, realmName) } +// configDBModuleInstrumentingMW implements Module. +func (m *configDBModuleInstrumentingMW) GetBackOfficeConfiguration(ctx context.Context, realmName string, groupNames []string) (dto.BackOfficeConfiguration, error) { + defer func(begin time.Time) { + m.h.With("correlation_id", ctx.Value(cs.CtContextCorrelationID).(string)).Observe(time.Since(begin).Seconds()) + }(time.Now()) + return m.next.GetBackOfficeConfiguration(ctx, realmName, groupNames) +} + +func (m *configDBModuleInstrumentingMW) DeleteBackOfficeConfiguration(ctx context.Context, realmID string, groupName string, confType string, targetRealmID *string, targetGroupName *string) error { + defer func(begin time.Time) { + m.h.With("correlation_id", ctx.Value(cs.CtContextCorrelationID).(string)).Observe(time.Since(begin).Seconds()) + }(time.Now()) + return m.next.DeleteBackOfficeConfiguration(ctx, realmID, groupName, confType, targetRealmID, targetGroupName) +} + +func (m *configDBModuleInstrumentingMW) InsertBackOfficeConfiguration(ctx context.Context, realmID, groupName, confType, targetRealmID string, targetGroupNames []string) error { + defer func(begin time.Time) { + m.h.With("correlation_id", ctx.Value(cs.CtContextCorrelationID).(string)).Observe(time.Since(begin).Seconds()) + }(time.Now()) + return m.next.InsertBackOfficeConfiguration(ctx, realmID, groupName, confType, targetRealmID, targetGroupNames) +} + // configDBModuleInstrumentingMW implements Module. func (m *configDBModuleInstrumentingMW) GetAuthorizations(ctx context.Context, realmID string, groupID string) ([]configuration.Authorization, error) { defer func(begin time.Time) { diff --git a/internal/keycloakb/configdbinstrumenting_test.go b/internal/keycloakb/configdbinstrumenting_test.go index 472fc0ec..8aee5685 100644 --- a/internal/keycloakb/configdbinstrumenting_test.go +++ b/internal/keycloakb/configdbinstrumenting_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/cloudtrust/keycloak-bridge/internal/dto" + cs "github.com/cloudtrust/common-service" "github.com/cloudtrust/common-service/configuration" @@ -27,30 +29,75 @@ func TestComponentInstrumentingMW(t *testing.T) { rand.Seed(time.Now().UnixNano()) var corrID = strconv.FormatUint(rand.Uint64(), 10) var ctx = context.WithValue(context.Background(), cs.CtContextCorrelationID, corrID) + var realmID = "realmID" + var groupNames = []string{"group1", "group2", "group3"} + var groupName = groupNames[0] + var confType = "customers" + + t.Run("Get configuration", func(t *testing.T) { + mockComponent.EXPECT().GetConfiguration(ctx, realmID).Return(configuration.RealmConfiguration{}, nil).Times(1) + mockHistogram.EXPECT().With("correlation_id", corrID).Return(mockHistogram).Times(1) + mockHistogram.EXPECT().Observe(gomock.Any()).Return().Times(1) + m.GetConfiguration(ctx, realmID) + }) + t.Run("Get configuration without correlation ID", func(t *testing.T) { + mockComponent.EXPECT().GetConfiguration(context.Background(), "realmID").Return(configuration.RealmConfiguration{}, nil).Times(1) + var f = func() { + m.GetConfiguration(context.Background(), "realmID") + } + assert.Panics(t, f) + }) + + t.Run("Update configuration", func(t *testing.T) { + mockComponent.EXPECT().StoreOrUpdateConfiguration(ctx, "realmID", configuration.RealmConfiguration{}).Return(nil).Times(1) + mockHistogram.EXPECT().With("correlation_id", corrID).Return(mockHistogram).Times(1) + mockHistogram.EXPECT().Observe(gomock.Any()).Return().Times(1) + m.StoreOrUpdateConfiguration(ctx, "realmID", configuration.RealmConfiguration{}) + }) + t.Run("Update configuration without correlation ID", func(t *testing.T) { + mockComponent.EXPECT().StoreOrUpdateConfiguration(context.Background(), "realmID", configuration.RealmConfiguration{}).Return(nil).Times(1) + var f = func() { + m.StoreOrUpdateConfiguration(context.Background(), "realmID", configuration.RealmConfiguration{}) + } + assert.Panics(t, f) + }) + + t.Run("Get Back-office configuration", func(t *testing.T) { + mockComponent.EXPECT().GetBackOfficeConfiguration(ctx, realmID, groupNames).Return(dto.BackOfficeConfiguration{}, nil) + mockHistogram.EXPECT().With("correlation_id", corrID).Return(mockHistogram).Times(1) + mockHistogram.EXPECT().Observe(gomock.Any()).Return().Times(1) + m.GetBackOfficeConfiguration(ctx, realmID, groupNames) + }) + t.Run("Get Back-office configuration without correlation ID", func(t *testing.T) { + mockComponent.EXPECT().GetBackOfficeConfiguration(context.Background(), realmID, groupNames).Return(dto.BackOfficeConfiguration{}, nil) + assert.Panics(t, func() { + m.GetBackOfficeConfiguration(context.Background(), realmID, groupNames) + }) + }) + + t.Run("Delete Back-office configuration", func(t *testing.T) { + mockComponent.EXPECT().DeleteBackOfficeConfiguration(ctx, realmID, groupName, confType, nil, nil).Return(nil) + mockHistogram.EXPECT().With("correlation_id", corrID).Return(mockHistogram).Times(1) + mockHistogram.EXPECT().Observe(gomock.Any()).Return().Times(1) + m.DeleteBackOfficeConfiguration(ctx, realmID, groupName, confType, nil, nil) + }) + t.Run("Delete Back-office configuration without correlation ID", func(t *testing.T) { + mockComponent.EXPECT().DeleteBackOfficeConfiguration(context.Background(), realmID, groupName, confType, nil, nil).Return(nil) + assert.Panics(t, func() { + m.DeleteBackOfficeConfiguration(context.Background(), realmID, groupName, confType, nil, nil) + }) + }) - // Get configuration. - mockComponent.EXPECT().GetConfiguration(ctx, "realmID").Return(configuration.RealmConfiguration{}, nil).Times(1) - mockHistogram.EXPECT().With("correlation_id", corrID).Return(mockHistogram).Times(1) - mockHistogram.EXPECT().Observe(gomock.Any()).Return().Times(1) - m.GetConfiguration(ctx, "realmID") - - // Get configuration without correlation ID. - mockComponent.EXPECT().GetConfiguration(context.Background(), "realmID").Return(configuration.RealmConfiguration{}, nil).Times(1) - var f = func() { - m.GetConfiguration(context.Background(), "realmID") - } - assert.Panics(t, f) - - // Update configuration. - mockComponent.EXPECT().StoreOrUpdate(ctx, "realmID", configuration.RealmConfiguration{}).Return(nil).Times(1) - mockHistogram.EXPECT().With("correlation_id", corrID).Return(mockHistogram).Times(1) - mockHistogram.EXPECT().Observe(gomock.Any()).Return().Times(1) - m.StoreOrUpdate(ctx, "realmID", configuration.RealmConfiguration{}) - - // Update configuration without correlation ID. - mockComponent.EXPECT().StoreOrUpdate(context.Background(), "realmID", configuration.RealmConfiguration{}).Return(nil).Times(1) - f = func() { - m.StoreOrUpdate(context.Background(), "realmID", configuration.RealmConfiguration{}) - } - assert.Panics(t, f) + t.Run("Insert Back-office configuration", func(t *testing.T) { + mockComponent.EXPECT().InsertBackOfficeConfiguration(ctx, realmID, groupName, confType, realmID, groupNames).Return(nil) + mockHistogram.EXPECT().With("correlation_id", corrID).Return(mockHistogram).Times(1) + mockHistogram.EXPECT().Observe(gomock.Any()).Return().Times(1) + m.InsertBackOfficeConfiguration(ctx, realmID, groupName, confType, realmID, groupNames) + }) + t.Run("Insert Back-office configuration without correlation ID", func(t *testing.T) { + mockComponent.EXPECT().InsertBackOfficeConfiguration(context.Background(), realmID, groupName, confType, realmID, groupNames).Return(nil) + assert.Panics(t, func() { + m.InsertBackOfficeConfiguration(context.Background(), realmID, groupName, confType, realmID, groupNames) + }) + }) } diff --git a/internal/keycloakb/configdbmodule.go b/internal/keycloakb/configdbmodule.go index e624c411..729ae692 100644 --- a/internal/keycloakb/configdbmodule.go +++ b/internal/keycloakb/configdbmodule.go @@ -4,12 +4,14 @@ import ( "context" "database/sql" "encoding/json" + "strings" "github.com/cloudtrust/common-service/configuration" "github.com/cloudtrust/common-service/database" "github.com/cloudtrust/common-service/database/sqltypes" errorhandler "github.com/cloudtrust/common-service/errors" "github.com/cloudtrust/common-service/log" + "github.com/cloudtrust/keycloak-bridge/internal/dto" msg "github.com/cloudtrust/keycloak-bridge/internal/messages" ) @@ -17,6 +19,23 @@ const ( updateConfigStmt = `INSERT INTO realm_configuration (realm_id, configuration) VALUES (?, ?) ON DUPLICATE KEY UPDATE configuration = ?;` + selectBOConfigStmt = ` + SELECT distinct target_realm_id, target_type, target_group_name + FROM backoffice_configuration + WHERE realm_id=? AND group_name IN (???) + ` + insertBOConfigStmt = ` + INSERT INTO backoffice_configuration (realm_id, group_name, target_realm_id, target_type, target_group_name) + VALUES (?,?,?,?,?) + ` + deleteBOConfigStmt = ` + DELETE FROM backoffice_configuration + WHERE realm_id=? + AND group_name=? + AND target_realm_id=? + AND (? IS NULL OR target_type=?) + AND (? IS NULL OR target_group_name=?) + ` selectAuthzStmt = `SELECT realm_id, group_name, action, target_realm_id, target_group_name FROM authorizations WHERE realm_id = ? AND group_name = ?;` createAuthzStmt = `INSERT INTO authorizations (realm_id, group_name, action, target_realm_id, target_group_name) VALUES (?, ?, ?, ?, ?);` @@ -44,7 +63,7 @@ func NewConfigurationDBModule(db sqltypes.CloudtrustDB, logger log.Logger) Confi } } -func (c *configurationDBModule) StoreOrUpdate(context context.Context, realmID string, config configuration.RealmConfiguration) error { +func (c *configurationDBModule) StoreOrUpdateConfiguration(context context.Context, realmID string, config configuration.RealmConfiguration) error { // transform customConfig object into JSON string configJSON, err := json.Marshal(config) if err != nil { @@ -68,6 +87,62 @@ func (c *configurationDBModule) GetConfiguration(ctx context.Context, realmID st return config, err } +func (c *configurationDBModule) GetBackOfficeConfiguration(ctx context.Context, realmID string, groupNames []string) (dto.BackOfficeConfiguration, error) { + var sqlRequest = strings.Replace(selectBOConfigStmt, "???", "?"+strings.Repeat(",?", len(groupNames)-1), 1) + var args = []interface{}{realmID} + for _, grp := range groupNames { + args = append(args, grp) + } + + var rows, err = c.db.Query(sqlRequest, args...) + if err != nil { + c.logger.Warn(ctx, "msg", "Can't get back-office configuration", "error", err.Error(), "realmID", realmID, "groups", strings.Join(groupNames, ",")) + return nil, err + } + + var res = make(dto.BackOfficeConfiguration) + for rows.Next() { + var targetRealmID, targetType, targetGroupName string + err = rows.Scan(&targetRealmID, &targetType, &targetGroupName) + if err != nil { + c.logger.Warn(ctx, "msg", "Can't get row from back-office configuration", "error", err.Error(), "realmID", realmID, "groups", strings.Join(groupNames, ",")) + return nil, err + } + if _, ok := res[targetRealmID]; !ok { + res[targetRealmID] = make(map[string][]string) + } + if realmSubConf, ok := res[targetRealmID][targetType]; !ok { + res[targetRealmID][targetType] = []string{targetGroupName} + } else { + res[targetRealmID][targetType] = append(realmSubConf, targetGroupName) + } + } + + return res, nil +} + +func (c *configurationDBModule) DeleteBackOfficeConfiguration(ctx context.Context, realmID, groupName, targetRealmID string, targetType *string, targetGroupName *string) error { + var _, err = c.db.Exec(deleteBOConfigStmt, realmID, groupName, targetRealmID, targetType, targetType, targetGroupName, targetGroupName) + if err != nil { + c.logger.Warn(ctx, "msg", "Can't delete back-office configuration", "error", err.Error(), "realmName", realmID, "group", groupName, + "targetRealmName", targetRealmID, "targetType", targetType, "group", targetGroupName) + return err + } + return nil +} + +func (c *configurationDBModule) InsertBackOfficeConfiguration(ctx context.Context, realmID, groupName, targetRealmID, targetType string, targetGroupNames []string) error { + for _, targetGroupName := range targetGroupNames { + var _, err = c.db.Exec(insertBOConfigStmt, realmID, groupName, targetRealmID, targetType, targetGroupName) + if err != nil { + c.logger.Warn(ctx, "msg", "Can't insert into back-office configuration", "error", err.Error(), "realmID", realmID, "groupName", groupName, + "targetRealmName", targetRealmID, "targetType", targetType, "group", targetGroupName) + return err + } + } + return nil +} + func (c *configurationDBModule) GetAuthorizations(ctx context.Context, realmID string, groupName string) ([]configuration.Authorization, error) { // Get Authorizations from DB rows, err := c.db.Query(selectAuthzStmt, realmID, groupName) diff --git a/internal/keycloakb/configdbmodule_test.go b/internal/keycloakb/configdbmodule_test.go index c4243628..7d8909e7 100644 --- a/internal/keycloakb/configdbmodule_test.go +++ b/internal/keycloakb/configdbmodule_test.go @@ -25,7 +25,7 @@ func TestConfigurationDBModule(t *testing.T) { mockDB.EXPECT().Exec(gomock.Any(), "realmId", gomock.Any(), gomock.Any()).Return(nil, nil).Times(1) var configDBModule = NewConfigurationDBModule(mockDB, mockLogger) - var err = configDBModule.StoreOrUpdate(context.Background(), "realmId", configuration.RealmConfiguration{}) + var err = configDBModule.StoreOrUpdateConfiguration(context.Background(), "realmId", configuration.RealmConfiguration{}) assert.Nil(t, err) } @@ -48,8 +48,7 @@ func TestGetConfiguration(t *testing.T) { var jsonBytes, _ = json.Marshal(expectedResult) var json = string(jsonBytes) mockDB.EXPECT().QueryRow(gomock.Any(), realmID).Return(mockSQLRow) - mockSQLRow.EXPECT().Scan(gomock.Any()).DoAndReturn(func(dest ...interface{}) error { - var ptrJSON = dest[0].(*string) + mockSQLRow.EXPECT().Scan(gomock.Any()).DoAndReturn(func(ptrJSON *string) error { *ptrJSON = json return nil }) @@ -75,3 +74,87 @@ func TestGetConfiguration(t *testing.T) { assert.Equal(t, expectedError, err) } } + +func TestBackOfficeConfiguration(t *testing.T) { + var mockCtrl = gomock.NewController(t) + defer mockCtrl.Finish() + + var mockDB = mock.NewCloudtrustDB(mockCtrl) + var mockSQLRows = mock.NewSQLRows(mockCtrl) + var mockLogger = log.NewNopLogger() + + var configDBModule = NewConfigurationDBModule(mockDB, mockLogger) + var expectedError = errors.New("error") + var realmID = "my-realm" + var groupName = "my-group" + var confType = "customers" + var targetRealmID = "the-realm" + var targetGroupName = "the-group" + var groupNames = []string{"group1", "group2"} + var ctx = context.TODO() + + t.Run("GET-SQL query fails", func(t *testing.T) { + mockDB.EXPECT().Query(gomock.Any(), gomock.Any()).Return(nil, expectedError) + var _, err = configDBModule.GetBackOfficeConfiguration(ctx, realmID, groupNames) + assert.Equal(t, expectedError, err) + }) + t.Run("GET-SQLRows is empty", func(t *testing.T) { + mockDB.EXPECT().Query(gomock.Any(), gomock.Any()).Return(mockSQLRows, nil) + mockSQLRows.EXPECT().Next().Return(false) + var conf, err = configDBModule.GetBackOfficeConfiguration(ctx, realmID, groupNames) + assert.Nil(t, err) + assert.Len(t, conf, 0) + }) + t.Run("GET-Scan fails", func(t *testing.T) { + mockDB.EXPECT().Query(gomock.Any(), gomock.Any()).Return(mockSQLRows, nil) + mockSQLRows.EXPECT().Next().Return(true) + mockSQLRows.EXPECT().Scan(gomock.Any()).Return(expectedError) + var _, err = configDBModule.GetBackOfficeConfiguration(ctx, realmID, groupNames) + assert.Equal(t, expectedError, err) + }) + t.Run("GET-Scan ok", func(t *testing.T) { + gomock.InOrder( + mockDB.EXPECT().Query(gomock.Any(), gomock.Any()).Return(mockSQLRows, nil), + mockSQLRows.EXPECT().Next().Return(true), + mockSQLRows.EXPECT().Scan(gomock.Any()).DoAndReturn(func(confType *string, realm *string, group *string) error { + *confType = "a" + *realm = "b" + *group = "c" + return nil + }), + mockSQLRows.EXPECT().Next().Return(true), + mockSQLRows.EXPECT().Scan(gomock.Any()).DoAndReturn(func(confType *string, realm *string, group *string) error { + *confType = "a" + *realm = "b" + *group = "d" + return nil + }), + mockSQLRows.EXPECT().Next().Return(false), + ) + var conf, err = configDBModule.GetBackOfficeConfiguration(ctx, realmID, groupNames) + assert.Nil(t, err) + assert.Equal(t, []string{"c", "d"}, conf["a"]["b"]) + }) + + t.Run("DELETE-Fails", func(t *testing.T) { + mockDB.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil, expectedError) + var err = configDBModule.DeleteBackOfficeConfiguration(ctx, realmID, groupName, confType, &targetRealmID, &targetGroupName) + assert.Equal(t, expectedError, err) + }) + t.Run("DELETE-Success", func(t *testing.T) { + mockDB.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil, nil) + var err = configDBModule.DeleteBackOfficeConfiguration(ctx, realmID, groupName, confType, &targetRealmID, &targetGroupName) + assert.Nil(t, err) + }) + + t.Run("INSERT-Fails", func(t *testing.T) { + mockDB.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil, expectedError) + var err = configDBModule.InsertBackOfficeConfiguration(ctx, realmID, groupName, confType, targetRealmID, groupNames) + assert.Equal(t, expectedError, err) + }) + t.Run("INSERT-Success", func(t *testing.T) { + mockDB.EXPECT().Exec(gomock.Any(), gomock.Any()).Return(nil, nil).Times(len(groupNames)) + var err = configDBModule.InsertBackOfficeConfiguration(ctx, realmID, groupName, confType, targetRealmID, groupNames) + assert.Nil(t, err) + }) +} diff --git a/internal/keycloakb/mock_test.go b/internal/keycloakb/mock_test.go index 087b0364..0e889414 100644 --- a/internal/keycloakb/mock_test.go +++ b/internal/keycloakb/mock_test.go @@ -3,4 +3,4 @@ package keycloakb //go:generate mockgen -destination=./mock/instrumenting.go -package=mock -mock_names=Histogram=Histogram github.com/cloudtrust/common-service/metrics Histogram //go:generate mockgen -destination=./mock/configdbinstrumenting.go -package=mock -mock_names=ConfigurationDBModule=ConfigurationDBModule github.com/cloudtrust/keycloak-bridge/internal/keycloakb ConfigurationDBModule //go:generate mockgen -destination=./mock/keycloak_client.go -package=mock -mock_names=KeycloakClient=KeycloakClient github.com/cloudtrust/keycloak-bridge/internal/keycloakb KeycloakClient -//go:generate mockgen -destination=./mock/sqltypes.go -package=mock -mock_names=CloudtrustDB=CloudtrustDB,SQLRow=SQLRow github.com/cloudtrust/common-service/database/sqltypes CloudtrustDB,SQLRow +//go:generate mockgen -destination=./mock/sqltypes.go -package=mock -mock_names=CloudtrustDB=CloudtrustDB,SQLRow=SQLRow,SQLRows=SQLRows github.com/cloudtrust/common-service/database/sqltypes CloudtrustDB,SQLRow,SQLRows diff --git a/pkg/account/authorization.go b/pkg/account/authorization.go index 3e32e6bb..32067945 100644 --- a/pkg/account/authorization.go +++ b/pkg/account/authorization.go @@ -11,6 +11,7 @@ import ( "github.com/cloudtrust/common-service/log" api "github.com/cloudtrust/keycloak-bridge/api/account" + "github.com/cloudtrust/keycloak-bridge/internal/keycloakb" ) // Creates constants for API method names @@ -30,12 +31,12 @@ const ( // Tracking middleware at component level. type authorizationComponentMW struct { logger log.Logger - configDBModule ConfigurationDBModule + configDBModule keycloakb.ConfigurationDBModule next Component } // MakeAuthorizationAccountComponentMW checks authorization and return an error if the action is not allowed. -func MakeAuthorizationAccountComponentMW(logger log.Logger, configDBModule ConfigurationDBModule) func(Component) Component { +func MakeAuthorizationAccountComponentMW(logger log.Logger, configDBModule keycloakb.ConfigurationDBModule) func(Component) Component { return func(next Component) Component { return &authorizationComponentMW{ logger: logger, diff --git a/pkg/account/component.go b/pkg/account/component.go index b13b5f0f..1f348431 100644 --- a/pkg/account/component.go +++ b/pkg/account/component.go @@ -8,11 +8,11 @@ import ( "strings" cs "github.com/cloudtrust/common-service" - "github.com/cloudtrust/common-service/configuration" "github.com/cloudtrust/common-service/database" errorhandler "github.com/cloudtrust/common-service/errors" api "github.com/cloudtrust/keycloak-bridge/api/account" "github.com/cloudtrust/keycloak-bridge/internal/dto" + "github.com/cloudtrust/keycloak-bridge/internal/keycloakb" internal "github.com/cloudtrust/keycloak-bridge/internal/keycloakb" kc "github.com/cloudtrust/keycloak-client" ) @@ -54,17 +54,6 @@ type Component interface { SendVerifyPhoneNumber(ctx context.Context) error } -// ConfigurationDBModule is the interface of the configuration module. -type ConfigurationDBModule interface { - NewTransaction(context context.Context) (database.Transaction, error) - StoreOrUpdate(context.Context, string, configuration.RealmConfiguration) error - GetConfiguration(context.Context, string) (configuration.RealmConfiguration, error) - GetAuthorizations(context context.Context, realmID string, groupID string) ([]configuration.Authorization, error) - CreateAuthorization(context context.Context, authz configuration.Authorization) error - DeleteAuthorizations(context context.Context, realmID string, groupID string) error - DeleteAllAuthorizationsWithGroup(context context.Context, realmID, groupName string) error -} - // UsersDBModule is the minimum required interface to access the users database type UsersDBModule interface { StoreOrUpdateUser(ctx context.Context, realm string, user dto.DBUser) error @@ -75,13 +64,13 @@ type UsersDBModule interface { type component struct { keycloakAccountClient KeycloakAccountClient eventDBModule database.EventsDBModule - configDBModule ConfigurationDBModule + configDBModule keycloakb.ConfigurationDBModule usersDBModule UsersDBModule logger internal.Logger } // NewComponent returns the self-service component. -func NewComponent(keycloakAccountClient KeycloakAccountClient, eventDBModule database.EventsDBModule, configDBModule ConfigurationDBModule, usersDBModule UsersDBModule, logger internal.Logger) Component { +func NewComponent(keycloakAccountClient KeycloakAccountClient, eventDBModule database.EventsDBModule, configDBModule keycloakb.ConfigurationDBModule, usersDBModule UsersDBModule, logger internal.Logger) Component { return &component{ keycloakAccountClient: keycloakAccountClient, eventDBModule: eventDBModule, diff --git a/pkg/management/authorization.go b/pkg/management/authorization.go index 9a4c090c..ac1d1cf3 100644 --- a/pkg/management/authorization.go +++ b/pkg/management/authorization.go @@ -22,43 +22,46 @@ func newAction(as string, scope security.Scope) security.Action { // Creates constants for API method names var ( - MGMTGetActions = newAction("MGMT_GetActions", security.ScopeGlobal) - MGMTGetRealms = newAction("MGMT_GetRealms", security.ScopeGlobal) - MGMTGetRealm = newAction("MGMT_GetRealm", security.ScopeRealm) - MGMTGetClient = newAction("MGMT_GetClient", security.ScopeRealm) - MGMTGetClients = newAction("MGMT_GetClients", security.ScopeRealm) - MGMTGetRequiredActions = newAction("MGMT_GetRequiredActions", security.ScopeRealm) - MGMTDeleteUser = newAction("MGMT_DeleteUser", security.ScopeGroup) - MGMTGetUser = newAction("MGMT_GetUser", security.ScopeGroup) - MGMTUpdateUser = newAction("MGMT_UpdateUser", security.ScopeGroup) - MGMTGetUsers = newAction("MGMT_GetUsers", security.ScopeGroup) - MGMTCreateUser = newAction("MGMT_CreateUser", security.ScopeGroup) - MGMTGetUserAccountStatus = newAction("MGMT_GetUserAccountStatus", security.ScopeGroup) - MGMTGetRolesOfUser = newAction("MGMT_GetRolesOfUser", security.ScopeGroup) - MGMTGetGroupsOfUser = newAction("MGMT_GetGroupsOfUser", security.ScopeGroup) - MGMTSetTrustIDGroups = newAction("MGMT_SetTrustIDGroups", security.ScopeGroup) - MGMTGetClientRolesForUser = newAction("MGMT_GetClientRolesForUser", security.ScopeGroup) - MGMTAddClientRolesToUser = newAction("MGMT_AddClientRolesToUser", security.ScopeGroup) - MGMTResetPassword = newAction("MGMT_ResetPassword", security.ScopeGroup) - MGMTExecuteActionsEmail = newAction("MGMT_ExecuteActionsEmail", security.ScopeGroup) - MGMTSendNewEnrolmentCode = newAction("MGMT_SendNewEnrolmentCode", security.ScopeGroup) - MGMTSendReminderEmail = newAction("MGMT_SendReminderEmail", security.ScopeGroup) - MGMTResetSmsCounter = newAction("MGMT_ResetSmsCounter", security.ScopeGroup) - MGMTCreateRecoveryCode = newAction("MGMT_CreateRecoveryCode", security.ScopeGroup) - MGMTGetCredentialsForUser = newAction("MGMT_GetCredentialsForUser", security.ScopeGroup) - MGMTDeleteCredentialsForUser = newAction("MGMT_DeleteCredentialsForUser", security.ScopeGroup) - MGMTGetRoles = newAction("MGMT_GetRoles", security.ScopeRealm) - MGMTGetRole = newAction("MGMT_GetRole", security.ScopeRealm) - MGMTGetGroups = newAction("MGMT_GetGroups", security.ScopeRealm) - MGMTCreateGroup = newAction("MGMT_CreateGroup", security.ScopeRealm) - MGMTDeleteGroup = newAction("MGMT_DeleteGroup", security.ScopeGroup) - MGMTGetAuthorizations = newAction("MGMT_GetAuthorizations", security.ScopeGroup) - MGMTUpdateAuthorizations = newAction("MGMT_UpdateAuthorizations", security.ScopeGroup) - MGMTGetClientRoles = newAction("MGMT_GetClientRoles", security.ScopeRealm) - MGMTCreateClientRole = newAction("MGMT_CreateClientRole", security.ScopeRealm) - MGMTGetRealmCustomConfiguration = newAction("MGMT_GetRealmCustomConfiguration", security.ScopeRealm) - MGMTUpdateRealmCustomConfiguration = newAction("MGMT_UpdateRealmCustomConfiguration", security.ScopeRealm) - MGMTCreateShadowUser = newAction("MGMT_CreateShadowUser", security.ScopeRealm) + MGMTGetActions = newAction("MGMT_GetActions", security.ScopeGlobal) + MGMTGetRealms = newAction("MGMT_GetRealms", security.ScopeGlobal) + MGMTGetRealm = newAction("MGMT_GetRealm", security.ScopeRealm) + MGMTGetClient = newAction("MGMT_GetClient", security.ScopeRealm) + MGMTGetClients = newAction("MGMT_GetClients", security.ScopeRealm) + MGMTGetRequiredActions = newAction("MGMT_GetRequiredActions", security.ScopeRealm) + MGMTDeleteUser = newAction("MGMT_DeleteUser", security.ScopeGroup) + MGMTGetUser = newAction("MGMT_GetUser", security.ScopeGroup) + MGMTUpdateUser = newAction("MGMT_UpdateUser", security.ScopeGroup) + MGMTGetUsers = newAction("MGMT_GetUsers", security.ScopeGroup) + MGMTCreateUser = newAction("MGMT_CreateUser", security.ScopeGroup) + MGMTGetUserAccountStatus = newAction("MGMT_GetUserAccountStatus", security.ScopeGroup) + MGMTGetRolesOfUser = newAction("MGMT_GetRolesOfUser", security.ScopeGroup) + MGMTGetGroupsOfUser = newAction("MGMT_GetGroupsOfUser", security.ScopeGroup) + MGMTSetTrustIDGroups = newAction("MGMT_SetTrustIDGroups", security.ScopeGroup) + MGMTGetClientRolesForUser = newAction("MGMT_GetClientRolesForUser", security.ScopeGroup) + MGMTAddClientRolesToUser = newAction("MGMT_AddClientRolesToUser", security.ScopeGroup) + MGMTResetPassword = newAction("MGMT_ResetPassword", security.ScopeGroup) + MGMTExecuteActionsEmail = newAction("MGMT_ExecuteActionsEmail", security.ScopeGroup) + MGMTSendNewEnrolmentCode = newAction("MGMT_SendNewEnrolmentCode", security.ScopeGroup) + MGMTSendReminderEmail = newAction("MGMT_SendReminderEmail", security.ScopeGroup) + MGMTResetSmsCounter = newAction("MGMT_ResetSmsCounter", security.ScopeGroup) + MGMTCreateRecoveryCode = newAction("MGMT_CreateRecoveryCode", security.ScopeGroup) + MGMTGetCredentialsForUser = newAction("MGMT_GetCredentialsForUser", security.ScopeGroup) + MGMTDeleteCredentialsForUser = newAction("MGMT_DeleteCredentialsForUser", security.ScopeGroup) + MGMTGetRoles = newAction("MGMT_GetRoles", security.ScopeRealm) + MGMTGetRole = newAction("MGMT_GetRole", security.ScopeRealm) + MGMTGetGroups = newAction("MGMT_GetGroups", security.ScopeRealm) + MGMTCreateGroup = newAction("MGMT_CreateGroup", security.ScopeRealm) + MGMTDeleteGroup = newAction("MGMT_DeleteGroup", security.ScopeGroup) + MGMTGetAuthorizations = newAction("MGMT_GetAuthorizations", security.ScopeGroup) + MGMTUpdateAuthorizations = newAction("MGMT_UpdateAuthorizations", security.ScopeGroup) + MGMTGetClientRoles = newAction("MGMT_GetClientRoles", security.ScopeRealm) + MGMTCreateClientRole = newAction("MGMT_CreateClientRole", security.ScopeRealm) + MGMTGetRealmCustomConfiguration = newAction("MGMT_GetRealmCustomConfiguration", security.ScopeRealm) + MGMTUpdateRealmCustomConfiguration = newAction("MGMT_UpdateRealmCustomConfiguration", security.ScopeRealm) + MGMTGetRealmBackOfficeConfiguration = newAction("MGMT_GetRealmBackOfficeConfiguration", security.ScopeGroup) + MGMTUpdateRealmBackOfficeConfiguration = newAction("MGMT_UpdateRealmBackOfficeConfiguration", security.ScopeGroup) + MGMTGetUserRealmBackOfficeConfiguration = newAction("MGMT_GetUserRealmBackOfficeConfiguration", security.ScopeRealm) + MGMTCreateShadowUser = newAction("MGMT_CreateShadowUser", security.ScopeRealm) ) // Tracking middleware at component level. @@ -478,6 +481,39 @@ func (c *authorizationComponentMW) UpdateRealmCustomConfiguration(ctx context.Co return c.next.UpdateRealmCustomConfiguration(ctx, realmName, customConfig) } +func (c *authorizationComponentMW) GetRealmBackOfficeConfiguration(ctx context.Context, realmName string, groupName string) (api.BackOfficeConfiguration, error) { + var action = MGMTGetRealmBackOfficeConfiguration.String() + var targetRealm = realmName + + if err := c.authManager.CheckAuthorizationOnTargetGroup(ctx, action, targetRealm, groupName); err != nil { + return api.BackOfficeConfiguration{}, err + } + + return c.next.GetRealmBackOfficeConfiguration(ctx, realmName, groupName) +} + +func (c *authorizationComponentMW) UpdateRealmBackOfficeConfiguration(ctx context.Context, realmName string, groupName string, config api.BackOfficeConfiguration) error { + var action = MGMTUpdateRealmBackOfficeConfiguration.String() + var targetRealm = realmName + + if err := c.authManager.CheckAuthorizationOnTargetGroup(ctx, action, targetRealm, groupName); err != nil { + return err + } + + return c.next.UpdateRealmBackOfficeConfiguration(ctx, realmName, groupName, config) +} + +func (c *authorizationComponentMW) GetUserRealmBackOfficeConfiguration(ctx context.Context, realmName string) (api.BackOfficeConfiguration, error) { + var action = MGMTGetUserRealmBackOfficeConfiguration.String() + var targetRealm = realmName + + if err := c.authManager.CheckAuthorizationOnTargetRealm(ctx, action, targetRealm); err != nil { + return api.BackOfficeConfiguration{}, err + } + + return c.next.GetUserRealmBackOfficeConfiguration(ctx, realmName) +} + func (c *authorizationComponentMW) CreateShadowUser(ctx context.Context, realmName string, userID string, provider string, fedID api.FederatedIdentityRepresentation) error { var action = MGMTCreateShadowUser.String() var targetRealm = realmName diff --git a/pkg/management/authorization_test.go b/pkg/management/authorization_test.go index 1058957b..a6abe4ed 100644 --- a/pkg/management/authorization_test.go +++ b/pkg/management/authorization_test.go @@ -82,6 +82,7 @@ func TestDeny(t *testing.T) { DefaultClientID: &clientID, DefaultRedirectURI: &clientURI, } + var config api.BackOfficeConfiguration var fedID = api.FederatedIdentityRepresentation{ UserID: &userID, @@ -212,6 +213,15 @@ func TestDeny(t *testing.T) { err = authorizationMW.UpdateRealmCustomConfiguration(ctx, realmName, customConfig) assert.Equal(t, security.ForbiddenError{}, err) + _, err = authorizationMW.GetRealmBackOfficeConfiguration(ctx, realmName, groupID) + assert.Equal(t, security.ForbiddenError{}, err) + + err = authorizationMW.UpdateRealmBackOfficeConfiguration(ctx, realmName, groupID, config) + assert.Equal(t, security.ForbiddenError{}, err) + + _, err = authorizationMW.GetUserRealmBackOfficeConfiguration(ctx, realmName) + assert.Equal(t, security.ForbiddenError{}, err) + err = authorizationMW.CreateShadowUser(ctx, realmName, userID, provider, fedID) assert.Equal(t, security.ForbiddenError{}, err) } @@ -282,6 +292,7 @@ func TestAllowed(t *testing.T) { DefaultClientID: &clientID, DefaultRedirectURI: &clientURI, } + var config api.BackOfficeConfiguration var fedID = api.FederatedIdentityRepresentation{ UserID: &userID, @@ -462,6 +473,18 @@ func TestAllowed(t *testing.T) { err = authorizationMW.UpdateRealmCustomConfiguration(ctx, realmName, customConfig) assert.Nil(t, err) + mockManagementComponent.EXPECT().GetRealmBackOfficeConfiguration(ctx, realmName, groupID).Return(config, nil).Times(1) + _, err = authorizationMW.GetRealmBackOfficeConfiguration(ctx, realmName, groupID) + assert.Nil(t, err) + + mockManagementComponent.EXPECT().UpdateRealmBackOfficeConfiguration(ctx, realmName, groupID, config).Return(nil).Times(1) + err = authorizationMW.UpdateRealmBackOfficeConfiguration(ctx, realmName, groupID, config) + assert.Nil(t, err) + + mockManagementComponent.EXPECT().GetUserRealmBackOfficeConfiguration(ctx, realmName).Return(config, nil).Times(1) + _, err = authorizationMW.GetUserRealmBackOfficeConfiguration(ctx, realmName) + assert.Nil(t, err) + mockManagementComponent.EXPECT().CreateShadowUser(ctx, realmName, userID, provider, fedID).Return(nil).Times(1) err = authorizationMW.CreateShadowUser(ctx, realmName, userID, provider, fedID) assert.Nil(t, err) diff --git a/pkg/management/component.go b/pkg/management/component.go index 9d283252..d0022323 100644 --- a/pkg/management/component.go +++ b/pkg/management/component.go @@ -11,6 +11,7 @@ import ( "github.com/cloudtrust/common-service/database" errorhandler "github.com/cloudtrust/common-service/errors" api "github.com/cloudtrust/keycloak-bridge/api/management" + "github.com/cloudtrust/keycloak-bridge/internal/dto" "github.com/cloudtrust/keycloak-bridge/internal/keycloakb" msg "github.com/cloudtrust/keycloak-bridge/internal/messages" kc "github.com/cloudtrust/keycloak-client" @@ -60,17 +61,6 @@ type KeycloakClient interface { CreateShadowUser(accessToken string, realmName string, userID string, provider string, fedID kc.FederatedIdentityRepresentation) error } -// ConfigurationDBModule is the interface of the configuration module. -type ConfigurationDBModule interface { - NewTransaction(context context.Context) (database.Transaction, error) - StoreOrUpdate(context.Context, string, configuration.RealmConfiguration) error - GetConfiguration(context.Context, string) (configuration.RealmConfiguration, error) - GetAuthorizations(context context.Context, realmID string, groupID string) ([]configuration.Authorization, error) - CreateAuthorization(context context.Context, authz configuration.Authorization) error - DeleteAuthorizations(context context.Context, realmID string, groupID string) error - DeleteAllAuthorizationsWithGroup(context context.Context, realmID, groupName string) error -} - // Component is the management component interface. type Component interface { GetActions(ctx context.Context) ([]api.ActionRepresentation, error) @@ -114,6 +104,9 @@ type Component interface { GetRealmCustomConfiguration(ctx context.Context, realmName string) (api.RealmCustomConfiguration, error) UpdateRealmCustomConfiguration(ctx context.Context, realmID string, customConfig api.RealmCustomConfiguration) error + GetRealmBackOfficeConfiguration(ctx context.Context, realmID string, groupName string) (api.BackOfficeConfiguration, error) + UpdateRealmBackOfficeConfiguration(ctx context.Context, realmID string, groupName string, config api.BackOfficeConfiguration) error + GetUserRealmBackOfficeConfiguration(ctx context.Context, realmID string) (api.BackOfficeConfiguration, error) CreateShadowUser(ctx context.Context, realmName string, userID string, provider string, fedID api.FederatedIdentityRepresentation) error } @@ -122,14 +115,14 @@ type Component interface { type component struct { keycloakClient KeycloakClient eventDBModule database.EventsDBModule - configDBModule ConfigurationDBModule + configDBModule keycloakb.ConfigurationDBModule authorizedTrustIDGroups map[string]bool logger keycloakb.Logger } // NewComponent returns the management component. func NewComponent(keycloakClient KeycloakClient, eventDBModule database.EventsDBModule, - configDBModule ConfigurationDBModule, authorizedTrustIDGroups []string, logger keycloakb.Logger) Component { + configDBModule keycloakb.ConfigurationDBModule, authorizedTrustIDGroups []string, logger keycloakb.Logger) Component { var authzedTrustIDGroups = make(map[string]bool) for _, grp := range authorizedTrustIDGroups { @@ -1292,10 +1285,130 @@ func (c *component) UpdateRealmCustomConfiguration(ctx context.Context, realmNam // from the realm ID, update the custom configuration in the DB realmID := realmConfig.Id - err = c.configDBModule.StoreOrUpdate(ctx, *realmID, config) + err = c.configDBModule.StoreOrUpdateConfiguration(ctx, *realmID, config) + return err +} + +func (c *component) GetRealmBackOfficeConfiguration(ctx context.Context, realmID string, groupName string) (api.BackOfficeConfiguration, error) { + var dbResult, err = c.configDBModule.GetBackOfficeConfiguration(ctx, realmID, []string{groupName}) + if err != nil { + c.logger.Warn(ctx, "err", err.Error()) + return nil, err + } + + return api.BackOfficeConfiguration(dbResult), nil +} + +func (c *component) UpdateRealmBackOfficeConfiguration(ctx context.Context, realmID string, groupName string, configuration api.BackOfficeConfiguration) error { + var dbResult, err = c.configDBModule.GetBackOfficeConfiguration(ctx, realmID, []string{groupName}) + if err != nil { + c.logger.Warn(ctx, "err", err.Error()) + return err + } + + err = c.removeObsoleteItemsFromBackOfficeConfiguration(ctx, realmID, groupName, dbResult, configuration) + if err != nil { + c.logger.Warn(ctx, "err", err.Error()) + return err + } + + err = c.addBackOfficeConfigurationNewItems(ctx, realmID, groupName, dbResult, configuration) + if err != nil { + c.logger.Warn(ctx, "err", err.Error()) + } + return err } +func (c *component) removeObsoleteItemsFromBackOfficeConfiguration(ctx context.Context, realmID string, groupName string, oldConf dto.BackOfficeConfiguration, newConf api.BackOfficeConfiguration) error { + var err error + + // Remove elements which were present in the old configuration and are not present in the new one + for existingRealm, existingRealmConf := range oldConf { + if newRealmConf, ok := newConf[existingRealm]; !ok { + err = c.configDBModule.DeleteBackOfficeConfiguration(ctx, realmID, groupName, existingRealm, nil, nil) + if err != nil { + return err + } + } else { + for existingType, existingTypeConf := range existingRealmConf { + if newTypeConf, ok := newRealmConf[existingType]; !ok { + err = c.configDBModule.DeleteBackOfficeConfiguration(ctx, realmID, groupName, existingRealm, &existingType, nil) + if err != nil { + return err + } + } else { + for _, existingGroupName := range existingTypeConf { + if !c.findString(newTypeConf, existingGroupName) { + err = c.configDBModule.DeleteBackOfficeConfiguration(ctx, realmID, groupName, existingRealm, &existingType, &existingGroupName) + if err != nil { + return err + } + } + } + } + } + } + } + + return nil +} + +func (c *component) addBackOfficeConfigurationNewItems(ctx context.Context, realmID string, groupName string, oldConf dto.BackOfficeConfiguration, newConf api.BackOfficeConfiguration) error { + var err error + + // Add elements which are in the new configuration and are not present in the old one + for newRealmID, newRealmConf := range newConf { + if oldRealmConf, ok := oldConf[newRealmID]; !ok { + for boType, boGroups := range newRealmConf { + err = c.configDBModule.InsertBackOfficeConfiguration(ctx, realmID, groupName, newRealmID, boType, boGroups) + if err != nil { + return err + } + } + } else { + for newTypeConf, newGroups := range newRealmConf { + if oldGroups, ok := oldRealmConf[newTypeConf]; !ok { + err = c.configDBModule.InsertBackOfficeConfiguration(ctx, realmID, groupName, newRealmID, newTypeConf, newGroups) + if err != nil { + return err + } + } else { + for _, newGroupName := range newGroups { + if !c.findString(oldGroups, newGroupName) { + err = c.configDBModule.InsertBackOfficeConfiguration(ctx, realmID, groupName, newRealmID, newTypeConf, []string{newGroupName}) + if err != nil { + return err + } + } + } + } + } + } + } + return nil +} + +func (c *component) findString(groups []string, searchGroup string) bool { + for _, aGroup := range groups { + if aGroup == searchGroup { + return true + } + } + return false +} + +func (c *component) GetUserRealmBackOfficeConfiguration(ctx context.Context, realmName string) (api.BackOfficeConfiguration, error) { + var groups = ctx.Value(cs.CtContextGroups).([]string) + var dbResult, err = c.configDBModule.GetBackOfficeConfiguration(ctx, realmName, groups) + if err != nil { + c.logger.Warn(ctx, "err", err.Error()) + return nil, err + } + + return api.BackOfficeConfiguration(dbResult), nil +} + func (c *component) CreateShadowUser(ctx context.Context, realmName string, userID string, provider string, fedID api.FederatedIdentityRepresentation) error { var accessToken = ctx.Value(cs.CtContextAccessToken).(string) diff --git a/pkg/management/component_test.go b/pkg/management/component_test.go index 0e152c62..7ef061e4 100644 --- a/pkg/management/component_test.go +++ b/pkg/management/component_test.go @@ -16,6 +16,7 @@ import ( errorhandler "github.com/cloudtrust/common-service/errors" "github.com/cloudtrust/common-service/log" api "github.com/cloudtrust/keycloak-bridge/api/management" + "github.com/cloudtrust/keycloak-bridge/internal/dto" "github.com/cloudtrust/keycloak-bridge/pkg/management/mock" kc "github.com/cloudtrust/keycloak-client" @@ -3363,7 +3364,7 @@ func TestUpdateRealmCustomConfiguration(t *testing.T) { { mockKeycloakClient.EXPECT().GetRealm(accessToken, realmID).Return(kcRealmRep, nil).Times(1) mockKeycloakClient.EXPECT().GetClients(accessToken, realmID).Return(clients, nil).Times(1) - mockConfigurationDBModule.EXPECT().StoreOrUpdate(ctx, realmID, gomock.Any()).Return(nil).Times(1) + mockConfigurationDBModule.EXPECT().StoreOrUpdateConfiguration(ctx, realmID, gomock.Any()).Return(nil).Times(1) err := managementComponent.UpdateRealmCustomConfiguration(ctx, realmID, configInit) assert.Nil(t, err) @@ -3444,6 +3445,112 @@ func TestUpdateRealmCustomConfiguration(t *testing.T) { } } +func createBackOfficeConfiguration(JSON string) dto.BackOfficeConfiguration { + var conf dto.BackOfficeConfiguration + json.Unmarshal([]byte(JSON), &conf) + return conf +} + +func TestRealmBackOfficeConfiguration(t *testing.T) { + var mockCtrl = gomock.NewController(t) + defer mockCtrl.Finish() + var mockKeycloakClient = mock.NewKeycloakClient(mockCtrl) + var mockEventDBModule = mock.NewEventDBModule(mockCtrl) + var mockConfigurationDBModule = mock.NewConfigurationDBModule(mockCtrl) + var mockLogger = log.NewNopLogger() + var allowedTrustIDGroups = []string{"grp1", "grp2"} + + var component = NewComponent(mockKeycloakClient, mockEventDBModule, mockConfigurationDBModule, allowedTrustIDGroups, mockLogger) + + var realmID = "master_id" + var groupName = "the.group" + var config = api.BackOfficeConfiguration{} + var ctx = context.WithValue(context.TODO(), cs.CtContextGroups, []string{"grp1", "grp2"}) + var largeConf = ` + { + "realm1": { + "a": [ "grp1" ] + }, + "realm2": { + "a": [ "grp1" ], + "b": [ "grp2" ], + "c": [ "grp1", "grp2" ] + } + } + ` + var smallConf = ` + { + "realm2": { + "a": [ "grp1" ], + "c": [ "grp2" ] + } + } + ` + + t.Run("UpdateRealmBackOfficeConfiguration - db.GetBackOfficeConfiguration fails", func(t *testing.T) { + var expectedError = errors.New("db error") + mockConfigurationDBModule.EXPECT().GetBackOfficeConfiguration(ctx, realmID, []string{groupName}).Return(nil, expectedError) + var err = component.UpdateRealmBackOfficeConfiguration(ctx, realmID, groupName, config) + assert.Equal(t, expectedError, err) + }) + + t.Run("UpdateRealmBackOfficeConfiguration - remove items", func(t *testing.T) { + var dbConf = createBackOfficeConfiguration(largeConf) + var requestConf, _ = api.NewBackOfficeConfigurationFromJSON(smallConf) + mockConfigurationDBModule.EXPECT().GetBackOfficeConfiguration(ctx, realmID, []string{groupName}).Return(dbConf, nil) + mockConfigurationDBModule.EXPECT().DeleteBackOfficeConfiguration(ctx, realmID, groupName, "realm1", nil, nil).Return(nil) + mockConfigurationDBModule.EXPECT().DeleteBackOfficeConfiguration(ctx, realmID, groupName, "realm2", gomock.Not(nil), nil).Return(nil) + mockConfigurationDBModule.EXPECT().DeleteBackOfficeConfiguration(ctx, realmID, groupName, "realm2", gomock.Not(nil), gomock.Not(nil)).Return(nil) + var err = component.UpdateRealmBackOfficeConfiguration(ctx, realmID, groupName, requestConf) + assert.Nil(t, err) + }) + + t.Run("UpdateRealmBackOfficeConfiguration - add items", func(t *testing.T) { + var dbConf = createBackOfficeConfiguration(smallConf) + var requestConf, _ = api.NewBackOfficeConfigurationFromJSON(largeConf) + mockConfigurationDBModule.EXPECT().GetBackOfficeConfiguration(ctx, realmID, []string{groupName}).Return(dbConf, nil) + mockConfigurationDBModule.EXPECT().InsertBackOfficeConfiguration(ctx, realmID, groupName, "realm1", "a", []string{"grp1"}).Return(nil) + mockConfigurationDBModule.EXPECT().InsertBackOfficeConfiguration(ctx, realmID, groupName, "realm2", "b", []string{"grp2"}).Return(nil) + mockConfigurationDBModule.EXPECT().InsertBackOfficeConfiguration(ctx, realmID, groupName, "realm2", "c", []string{"grp1"}).Return(nil) + var err = component.UpdateRealmBackOfficeConfiguration(ctx, realmID, groupName, requestConf) + assert.Nil(t, err) + }) + + t.Run("GetRealmBackOfficeConfiguration - error", func(t *testing.T) { + var dbConf = createBackOfficeConfiguration(smallConf) + var expectedError = errors.New("db error") + mockConfigurationDBModule.EXPECT().GetBackOfficeConfiguration(ctx, realmID, []string{groupName}).Return(dbConf, expectedError) + var res, err = component.GetRealmBackOfficeConfiguration(ctx, realmID, groupName) + assert.Equal(t, expectedError, err) + assert.Nil(t, res) + }) + + t.Run("GetRealmBackOfficeConfiguration - success", func(t *testing.T) { + var dbConf = createBackOfficeConfiguration(smallConf) + mockConfigurationDBModule.EXPECT().GetBackOfficeConfiguration(ctx, realmID, []string{groupName}).Return(dbConf, nil) + var res, err = component.GetRealmBackOfficeConfiguration(ctx, realmID, groupName) + assert.Nil(t, err) + assert.Equal(t, api.BackOfficeConfiguration(dbConf), res) + }) + + t.Run("GetUserRealmBackOfficeConfiguration - db error", func(t *testing.T) { + var dbError = errors.New("db error") + var groups = ctx.Value(cs.CtContextGroups).([]string) + mockConfigurationDBModule.EXPECT().GetBackOfficeConfiguration(ctx, realmID, groups).Return(nil, dbError) + var _, err = component.GetUserRealmBackOfficeConfiguration(ctx, realmID) + assert.Equal(t, dbError, err) + }) + + t.Run("GetUserRealmBackOfficeConfiguration - success", func(t *testing.T) { + var dbConf = createBackOfficeConfiguration(smallConf) + var groups = ctx.Value(cs.CtContextGroups).([]string) + mockConfigurationDBModule.EXPECT().GetBackOfficeConfiguration(ctx, realmID, groups).Return(dbConf, nil) + var res, err = component.GetUserRealmBackOfficeConfiguration(ctx, realmID) + assert.Nil(t, err) + assert.Equal(t, api.BackOfficeConfiguration(dbConf), res) + }) +} + func TestCreateShadowUser(t *testing.T) { var mockCtrl = gomock.NewController(t) defer mockCtrl.Finish() diff --git a/pkg/management/endpoint.go b/pkg/management/endpoint.go index 09cc08ad..95e94824 100644 --- a/pkg/management/endpoint.go +++ b/pkg/management/endpoint.go @@ -55,8 +55,11 @@ type Endpoints struct { UpdateAuthorizations endpoint.Endpoint GetActions endpoint.Endpoint - GetRealmCustomConfiguration endpoint.Endpoint - UpdateRealmCustomConfiguration endpoint.Endpoint + GetRealmCustomConfiguration endpoint.Endpoint + UpdateRealmCustomConfiguration endpoint.Endpoint + GetRealmBackOfficeConfiguration endpoint.Endpoint + UpdateRealmBackOfficeConfiguration endpoint.Endpoint + GetUserRealmBackOfficeConfiguration endpoint.Endpoint CreateShadowUser endpoint.Endpoint } @@ -102,6 +105,9 @@ type ManagementComponent interface { GetRealmCustomConfiguration(ctx context.Context, realmID string) (api.RealmCustomConfiguration, error) UpdateRealmCustomConfiguration(ctx context.Context, realmID string, customConfig api.RealmCustomConfiguration) error + GetRealmBackOfficeConfiguration(ctx context.Context, realmName string, groupID string) (api.BackOfficeConfiguration, error) + UpdateRealmBackOfficeConfiguration(ctx context.Context, realmName string, groupID string, boConf api.BackOfficeConfiguration) error + GetUserRealmBackOfficeConfiguration(ctx context.Context, realmName string) (api.BackOfficeConfiguration, error) CreateShadowUser(ctx context.Context, realmName string, userID string, provider string, fedID api.FederatedIdentityRepresentation) error } @@ -607,6 +613,45 @@ func MakeUpdateRealmCustomConfigurationEndpoint(managementComponent ManagementCo } } +// MakeGetRealmBackOfficeConfigurationEndpoint creates an endpoint for GetRealmBackOfficeConfiguration +func MakeGetRealmBackOfficeConfigurationEndpoint(managementComponent ManagementComponent) cs.Endpoint { + return func(ctx context.Context, req interface{}) (interface{}, error) { + var m = req.(map[string]string) + var groupName = m["groupName"] + if groupName == "" { + return nil, errorhandler.CreateMissingParameterError(msg.GroupName) + } + + return managementComponent.GetRealmBackOfficeConfiguration(ctx, m["realm"], groupName) + } +} + +// MakeUpdateRealmBackOfficeConfigurationEndpoint creates an endpoint for UpdateRealmBackOfficeConfiguration +func MakeUpdateRealmBackOfficeConfigurationEndpoint(managementComponent ManagementComponent) cs.Endpoint { + return func(ctx context.Context, req interface{}) (interface{}, error) { + var m = req.(map[string]string) + var boConf, err = api.NewBackOfficeConfigurationFromJSON(m["body"]) + if err != nil { + return nil, err + } + var groupName = m["groupName"] + if groupName == "" { + return nil, errorhandler.CreateMissingParameterError(msg.GroupName) + } + + return nil, managementComponent.UpdateRealmBackOfficeConfiguration(ctx, m["realm"], groupName, boConf) + } +} + +// MakeGetUserRealmBackOfficeConfigurationEndpoint creates an endpoint for GetUserRealmBackOfficeConfiguration +func MakeGetUserRealmBackOfficeConfigurationEndpoint(managementComponent ManagementComponent) cs.Endpoint { + return func(ctx context.Context, req interface{}) (interface{}, error) { + var m = req.(map[string]string) + + return managementComponent.GetUserRealmBackOfficeConfiguration(ctx, m["realm"]) + } +} + // MakeCreateShadowUserEndpoint makes the endpoint to create a shadow user. func MakeCreateShadowUserEndpoint(managementComponent ManagementComponent) cs.Endpoint { return func(ctx context.Context, req interface{}) (interface{}, error) { diff --git a/pkg/management/endpoint_test.go b/pkg/management/endpoint_test.go index 6abed182..e7118bb8 100644 --- a/pkg/management/endpoint_test.go +++ b/pkg/management/endpoint_test.go @@ -3,6 +3,7 @@ package management import ( "context" "encoding/json" + "errors" "fmt" "testing" @@ -1058,71 +1059,73 @@ func TestCreateClientRoleEndpoint(t *testing.T) { } } -func TestGetRealmCustomConfigurationEndpoint(t *testing.T) { +func TestConfigurationEndpoints(t *testing.T) { var mockCtrl = gomock.NewController(t) defer mockCtrl.Finish() var mockManagementComponent = mock.NewManagementComponent(mockCtrl) - var e = MakeGetRealmCustomConfigurationEndpoint(mockManagementComponent) + var realmName = "master" + var clientID = "123456" + var groupName = "my-group" + var ctx = context.Background() - // No error - { - var realmName = "master" - var clientID = "123456" - var ctx = context.Background() - var req = make(map[string]string) - req["realm"] = realmName - req["clientID"] = clientID + t.Run("MakeGetRealmCustomConfigurationEndpoint - No error", func(t *testing.T) { + var req = map[string]string{"realm": realmName, "clientID": clientID} + var e = MakeGetRealmCustomConfigurationEndpoint(mockManagementComponent) mockManagementComponent.EXPECT().GetRealmCustomConfiguration(ctx, realmName).Return(api.RealmCustomConfiguration{}, nil).Times(1) var res, err = e(ctx, req) assert.Nil(t, err) assert.NotNil(t, res) - } -} - -func TestUpdateRealmCustomConfigurationEndpoint(t *testing.T) { - var mockCtrl = gomock.NewController(t) - defer mockCtrl.Finish() - - var mockManagementComponent = mock.NewManagementComponent(mockCtrl) - - var e = MakeUpdateRealmCustomConfigurationEndpoint(mockManagementComponent) + }) - // No error - { - var realmName = "master" - var clientID = "123456" + t.Run("MakeUpdateRealmCustomConfigurationEndpoint - No error", func(t *testing.T) { var configJSON = "{\"DefaultClientId\":\"clientId\", \"DefaultRedirectUri\":\"http://cloudtrust.io\"}" - var ctx = context.Background() - var req = make(map[string]string) - req["realm"] = realmName - req["clientID"] = clientID - req["body"] = configJSON + var req = map[string]string{"realm": realmName, "clientID": clientID, "body": configJSON} + var e = MakeUpdateRealmCustomConfigurationEndpoint(mockManagementComponent) mockManagementComponent.EXPECT().UpdateRealmCustomConfiguration(ctx, realmName, gomock.Any()).Return(nil).Times(1) var res, err = e(ctx, req) assert.Nil(t, err) assert.Nil(t, res) - } + }) - // JSON error - { - var realmName = "master" - var clientID = "123456" + t.Run("MakeUpdateRealmCustomConfigurationEndpoint - JSON error", func(t *testing.T) { var configJSON = "{\"DefaultClientId\":\"clientId\", \"DefaultRedirectUri\":\"http://cloudtrust.io\"" - var ctx = context.Background() - var req = make(map[string]string) - req["realm"] = realmName - req["clientID"] = clientID - req["body"] = configJSON + var req = map[string]string{"realm": realmName, "clientID": clientID, "body": configJSON} + var e = MakeUpdateRealmCustomConfigurationEndpoint(mockManagementComponent) mockManagementComponent.EXPECT().UpdateRealmCustomConfiguration(ctx, realmName, gomock.Any()).Return(nil).Times(0) var res, err = e(ctx, req) assert.NotNil(t, err) assert.Nil(t, res) - } + }) + + t.Run("MakeGetRealmBackOfficeConfigurationEndpoint", func(t *testing.T) { + var req = map[string]string{"realm": realmName, "groupName": groupName} + var expectedConf api.BackOfficeConfiguration + var expectedErr = errors.New("any error") + var e = MakeGetRealmBackOfficeConfigurationEndpoint(mockManagementComponent) + + mockManagementComponent.EXPECT().GetRealmBackOfficeConfiguration(ctx, realmName, groupName).Return(expectedConf, expectedErr).Times(1) + var res, err = e(ctx, req) + assert.Equal(t, expectedErr, err) + assert.Equal(t, expectedConf, res) + }) + + t.Run("MakeUpdateRealmBackOfficeConfigurationEndpoint", func(t *testing.T) { + var config api.BackOfficeConfiguration + var configJSON, _ = json.Marshal(config) + var req = map[string]string{"realm": realmName, "groupName": groupName, "body": string(configJSON)} + var expectedErr = errors.New("update error") + var e = MakeUpdateRealmBackOfficeConfigurationEndpoint(mockManagementComponent) + + mockManagementComponent.EXPECT().UpdateRealmBackOfficeConfiguration(ctx, realmName, groupName, config).Return(expectedErr).Times(1) + var res, err = e(ctx, req) + assert.Equal(t, expectedErr, err) + assert.Nil(t, res) + }) } func TestCreateShadowUserEndpoint(t *testing.T) { diff --git a/pkg/management/http.go b/pkg/management/http.go index eee5327b..f78f70ec 100644 --- a/pkg/management/http.go +++ b/pkg/management/http.go @@ -49,6 +49,7 @@ func decodeManagementRequest(ctx context.Context, req *http.Request) (interface{ "groupIds": management_api.RegExpGroupIds, "first": management_api.RegExpNumber, "max": management_api.RegExpNumber, + "groupName": management_api.RegExpName, } return commonhttp.DecodeRequest(ctx, req, pathParams, queryParams)