Skip to content

Commit

Permalink
[CLOUDTRUST-2294] Add endpoint to manage back-office configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
fperot74 committed Mar 3, 2020
1 parent 65d44a3 commit 039b8a9
Show file tree
Hide file tree
Showing 19 changed files with 876 additions and 145 deletions.
33 changes: 33 additions & 0 deletions api/management/api.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -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().
Expand Down
52 changes: 52 additions & 0 deletions api/management/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
81 changes: 81 additions & 0 deletions api/management/swagger-api_management.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
16 changes: 14 additions & 2 deletions cmd/keycloakb/keycloak_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"]),
}
}
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down
4 changes: 4 additions & 0 deletions internal/dto/conf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package dto

// BackOfficeConfiguration definition
type BackOfficeConfiguration map[string]map[string][]string
32 changes: 29 additions & 3 deletions internal/keycloakb/configdbinstrumenting.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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) {
Expand Down
Loading

0 comments on commit 039b8a9

Please sign in to comment.