Skip to content

Commit

Permalink
Merge 0ce9978 into b49f222
Browse files Browse the repository at this point in the history
  • Loading branch information
harture committed Jan 8, 2020
2 parents b49f222 + 0ce9978 commit 201a518
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 6 deletions.
10 changes: 5 additions & 5 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@

[[constraint]]
name = "github.com/cloudtrust/keycloak-client"
branch = "master"
#version = "v1.2.1"
branch="CLOUDTRUST-2147_RecoveryModeAPI"

[[constraint]]
name = "github.com/go-kit/kit"
Expand Down
27 changes: 27 additions & 0 deletions api/management/swagger-api_management.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,33 @@ paths:
type: array
items:
$ref: '#/components/schemas/Credential'
/realms/{realm}/users/{userID}/recovery-code:
post:
tags:
- Credentials
summary: Set a recovery code for the user
parameters:
- name: realm
in: path
description: realm name (not id!)
required: true
schema:
type: string
- name: userID
in: path
description: User id
required: true
schema:
type: string
responses:
201:
description: successful operation
content:
text/plain:
schema:
type: string
409:
description: recovery code already set
/realms/{realm}/users/{userID}/credentials/{credentialID}:
delete:
tags:
Expand Down
3 changes: 3 additions & 0 deletions cmd/keycloakb/keycloak_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ func main() {
SendReminderEmail: prepareEndpoint(management.MakeSendReminderEmailEndpoint(keycloakComponent), "send_reminder_email_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]),
SendNewEnrolmentCode: prepareEndpoint(management.MakeSendNewEnrolmentCodeEndpoint(keycloakComponent), "send_new_enrolment_code_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]),
ResetSmsCounter: prepareEndpoint(management.MakeResetSmsCounterEndpoint(keycloakComponent), "reset_sms_counter_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]),
CreateRecoveryCode: prepareEndpoint(management.MakeCreateRecoveryCodeEndpoint(keycloakComponent), "create_recovery_code_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]),
GetCredentialsForUser: prepareEndpoint(management.MakeGetCredentialsForUserEndpoint(keycloakComponent), "get_credentials_for_user_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]),
DeleteCredentialsForUser: prepareEndpoint(management.MakeDeleteCredentialsForUserEndpoint(keycloakComponent), "delete_credentials_for_user_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]),
GetRealmCustomConfiguration: prepareEndpoint(management.MakeGetRealmCustomConfigurationEndpoint(keycloakComponent), "get_realm_custom_config_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]),
Expand Down Expand Up @@ -649,6 +650,7 @@ func main() {
var sendNewEnrolmentCodeHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.SendNewEnrolmentCode)
var sendReminderEmailHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.SendReminderEmail)
var resetSmsCounterHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.ResetSmsCounter)
var createRecoveryCodeHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.CreateRecoveryCode)

var getCredentialsForUserHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.GetCredentialsForUser)
var deleteCredentialsForUserHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.DeleteCredentialsForUser)
Expand Down Expand Up @@ -687,6 +689,7 @@ func main() {
managementSubroute.Path("/realms/{realm}/users/{userID}/send-new-enrolment-code").Methods("POST").Handler(sendNewEnrolmentCodeHandler)
managementSubroute.Path("/realms/{realm}/users/{userID}/send-reminder-email").Methods("POST").Handler(sendReminderEmailHandler)
managementSubroute.Path("/realms/{realm}/users/{userID}/reset-sms-counter").Methods("PUT").Handler(resetSmsCounterHandler)
managementSubroute.Path("/realms/{realm}/users/{userID}/recovery-code").Methods("POST").Handler(createRecoveryCodeHandler)

// Credentials
managementSubroute.Path("/realms/{realm}/users/{userID}/credentials").Methods("GET").Handler(getCredentialsForUserHandler)
Expand Down
7 changes: 7 additions & 0 deletions configs/authorization.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@
"l3_support_manager": {}
}
},
"RecoveryCode": {
"master": {
"integrator_manager": {},
"l2_support_manager": {},
"l3_support_manager": {}
}
},
"GetRequiredActions": {
"master": {}
},
Expand Down
12 changes: 12 additions & 0 deletions pkg/management/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
SendNewEnrolmentCode = "SendNewEnrolmentCode"
SendReminderEmail = "SendReminderEmail"
ResetSmsCounter = "ResetSmsCounter"
CreateRecoveryCode = "CreateRecoveryCode"
GetCredentialsForUser = "GetCredentialsForUser"
DeleteCredentialsForUser = "DeleteCredentialsForUser"
GetRoles = "GetRoles"
Expand Down Expand Up @@ -241,6 +242,17 @@ func (c *authorizationComponentMW) ResetPassword(ctx context.Context, realmName
return c.next.ResetPassword(ctx, realmName, userID, password)
}

func (c *authorizationComponentMW) CreateRecoveryCode(ctx context.Context, realmName string, userID string) (string, error) {
var action = CreateRecoveryCode
var targetRealm = realmName

if err := c.authManager.CheckAuthorizationOnTargetUser(ctx, action, targetRealm, userID); err != nil {
return "", err
}

return c.next.CreateRecoveryCode(ctx, realmName, userID)
}

func (c *authorizationComponentMW) SendVerifyEmail(ctx context.Context, realmName string, userID string, paramKV ...string) error {
var action = SendVerifyEmail
var targetRealm = realmName
Expand Down
9 changes: 9 additions & 0 deletions pkg/management/authorization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ func TestDeny(t *testing.T) {
err = authorizationMW.ResetSmsCounter(ctx, realmName, userID)
assert.Equal(t, security.ForbiddenError{}, err)

_, err = authorizationMW.CreateRecoveryCode(ctx, realmName, userID)
assert.Equal(t, security.ForbiddenError{}, err)

_, err = authorizationMW.GetCredentialsForUser(ctx, realmName, userID)
assert.Equal(t, security.ForbiddenError{}, err)

Expand Down Expand Up @@ -247,6 +250,7 @@ func TestAllowed(t *testing.T) {
"SendNewEnrolmentCode": {"*": {"*": {} }},
"SendReminderEmail": {"*": {"*": {} }},
"ResetSmsCounter": {"*": {"*": {} }},
"CreateRecoveryCode": {"*": {"*": {} }},
"GetCredentialsForUser": {"*": {"*": {} }},
"DeleteCredentialsForUser": {"*": {"*": {} }},
"GetRoles": {"*": {"*": {} }},
Expand Down Expand Up @@ -353,6 +357,11 @@ func TestAllowed(t *testing.T) {
err = authorizationMW.ResetSmsCounter(ctx, realmName, userID)
assert.Nil(t, err)

mockManagementComponent.EXPECT().CreateRecoveryCode(ctx, realmName, userID).Return("123456", nil).Times(1)
code, err := authorizationMW.CreateRecoveryCode(ctx, realmName, userID)
assert.Nil(t, err)
assert.NotNil(t, code)

mockManagementComponent.EXPECT().GetCredentialsForUser(ctx, realmName, userID).Return([]api.CredentialRepresentation{}, nil).Times(1)
_, err = authorizationMW.GetCredentialsForUser(ctx, realmName, userID)
assert.Nil(t, err)
Expand Down
18 changes: 18 additions & 0 deletions pkg/management/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type KeycloakClient interface {
SendVerifyEmail(accessToken string, realmName string, userID string, paramKV ...string) error
ExecuteActionsEmail(accessToken string, realmName string, userID string, actions []string, paramKV ...string) error
SendNewEnrolmentCode(accessToken string, realmName string, userID string) (kc.SmsCodeRepresentation, error)
CreateRecoveryCode(accessToken string, realmName string, userID string) (kc.RecoveryCodeRepresentation, error)
SendReminderEmail(accessToken string, realmName string, userID string, paramKV ...string) error
GetRoles(accessToken string, realmName string) ([]kc.RoleRepresentation, error)
GetRole(accessToken string, realmName string, roleID string) (kc.RoleRepresentation, error)
Expand Down Expand Up @@ -81,6 +82,7 @@ type Component interface {
SendNewEnrolmentCode(ctx context.Context, realmName string, userID string) (string, error)
SendReminderEmail(ctx context.Context, realmName string, userID string, paramKV ...string) error
ResetSmsCounter(ctx context.Context, realmName string, userID string) error
CreateRecoveryCode(ctx context.Context, realmName string, userID string) (string, error)
GetCredentialsForUser(ctx context.Context, realmName string, userID string) ([]api.CredentialRepresentation, error)
DeleteCredentialsForUser(ctx context.Context, realmName string, userID string, credentialID string) error
GetRoles(ctx context.Context, realmName string) ([]api.RoleRepresentation, error)
Expand Down Expand Up @@ -666,6 +668,22 @@ func (c *component) ResetSmsCounter(ctx context.Context, realmName, userID strin
return nil
}

func (c *component) CreateRecoveryCode(ctx context.Context, realmName, userID string) (string, error) {
var accessToken = ctx.Value(cs.CtContextAccessToken).(string)

recoveryCodeKc, err := c.keycloakClient.CreateRecoveryCode(accessToken, realmName, userID)

if err != nil {
c.logger.Warn(ctx, "err", err.Error())
return "", err
}

// store the API call into the DB
c.reportEvent(ctx, "CREATE_RECOVERY_CODE", database.CtEventRealmName, realmName, database.CtEventUserID, userID)

return *recoveryCodeKc.Code, err
}

func (c *component) GetCredentialsForUser(ctx context.Context, realmName string, userID string) ([]api.CredentialRepresentation, error) {
var accessToken = ctx.Value(cs.CtContextAccessToken).(string)

Expand Down
73 changes: 73 additions & 0 deletions pkg/management/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1509,6 +1509,79 @@ func TestResetPassword(t *testing.T) {

}

func TestRecoveryCode(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 = mock.NewLogger(mockCtrl)

var managementComponent = NewComponent(mockKeycloakClient, mockEventDBModule, mockConfigurationDBModule, mockLogger)

var accessToken = "TOKEN=="
var realmName = "master"
var userID = "41dbf4a8-32a9-4000-8c17-edc854c31231"
var username = "username"
var code = "123456"

// RecoveryCode
{
var kcCodeRep = kc.RecoveryCodeRepresentation{
Code: &code,
}

mockKeycloakClient.EXPECT().CreateRecoveryCode(accessToken, realmName, userID).Return(kcCodeRep, nil).Times(1)

var ctx = context.WithValue(context.Background(), cs.CtContextAccessToken, accessToken)
ctx = context.WithValue(ctx, cs.CtContextRealm, realmName)
ctx = context.WithValue(ctx, cs.CtContextUsername, username)

mockEventDBModule.EXPECT().ReportEvent(ctx, "CREATE_RECOVERY_CODE", "back-office", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1)

recoveryCode, err := managementComponent.CreateRecoveryCode(ctx, "master", userID)

assert.Nil(t, err)
assert.Equal(t, code, recoveryCode)
}

// RecoveryCode already exists
{
var err409 = kc.HTTPError{
HTTPStatus: 409,
Message: "Conflict",
}
var kcCodeRep = kc.RecoveryCodeRepresentation{}

mockKeycloakClient.EXPECT().CreateRecoveryCode(accessToken, realmName, userID).Return(kcCodeRep, err409).Times(1)

var ctx = context.WithValue(context.Background(), cs.CtContextAccessToken, accessToken)
ctx = context.WithValue(ctx, cs.CtContextRealm, realmName)
ctx = context.WithValue(ctx, cs.CtContextUsername, username)

mockLogger.EXPECT().Warn(gomock.Any(), "err", "409:Conflict")
_, err := managementComponent.CreateRecoveryCode(ctx, "master", userID)

assert.NotNil(t, err)
}

// Error
{
var kcCodeRep = kc.RecoveryCodeRepresentation{}
mockKeycloakClient.EXPECT().CreateRecoveryCode(accessToken, realmName, userID).Return(kcCodeRep, fmt.Errorf("Error")).Times(1)

var ctx = context.WithValue(context.Background(), cs.CtContextAccessToken, accessToken)
ctx = context.WithValue(ctx, cs.CtContextRealm, realmName)
ctx = context.WithValue(ctx, cs.CtContextUsername, username)

mockLogger.EXPECT().Warn(gomock.Any(), "err", "Error")
_, err := managementComponent.CreateRecoveryCode(ctx, "master", userID)

assert.NotNil(t, err)
}

}

func TestSendVerifyEmail(t *testing.T) {
var mockCtrl = gomock.NewController(t)
defer mockCtrl.Finish()
Expand Down
11 changes: 11 additions & 0 deletions pkg/management/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Endpoints struct {
SendNewEnrolmentCode endpoint.Endpoint
SendReminderEmail endpoint.Endpoint
ResetSmsCounter endpoint.Endpoint
CreateRecoveryCode endpoint.Endpoint
GetCredentialsForUser endpoint.Endpoint
DeleteCredentialsForUser endpoint.Endpoint
GetRoles endpoint.Endpoint
Expand Down Expand Up @@ -71,6 +72,7 @@ type ManagementComponent interface {
SendNewEnrolmentCode(ctx context.Context, realmName string, userID string) (string, error)
SendReminderEmail(ctx context.Context, realmName string, userID string, paramKV ...string) error
ResetSmsCounter(ctx context.Context, realmName string, userID string) error
CreateRecoveryCode(ctx context.Context, realmName string, userID string) (string, error)
GetCredentialsForUser(ctx context.Context, realmName string, userID string) ([]api.CredentialRepresentation, error)
DeleteCredentialsForUser(ctx context.Context, realmName string, userID string, credentialID string) error
GetRoles(ctx context.Context, realmName string) ([]api.RoleRepresentation, error)
Expand Down Expand Up @@ -385,6 +387,15 @@ func MakeResetSmsCounterEndpoint(managementComponent ManagementComponent) cs.End
}
}

// MakeCreateRecoveryCodeEndpoint creates an endpoint for MakeCreateRecoveryCode
func MakeCreateRecoveryCodeEndpoint(managementComponent ManagementComponent) cs.Endpoint {
return func(ctx context.Context, req interface{}) (interface{}, error) {
var m = req.(map[string]string)

return managementComponent.CreateRecoveryCode(ctx, m["realm"], m["userID"])
}
}

// MakeGetCredentialsForUserEndpoint creates an endpoint for GetCredentialsForUser
func MakeGetCredentialsForUserEndpoint(managementComponent ManagementComponent) cs.Endpoint {
return func(ctx context.Context, req interface{}) (interface{}, error) {
Expand Down
22 changes: 22 additions & 0 deletions pkg/management/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,28 @@ func TestResetSmsCounterEndpoint(t *testing.T) {

}

func TestRecoveryCodeEndpoint(t *testing.T) {
var mockCtrl = gomock.NewController(t)
defer mockCtrl.Finish()

var mockManagementComponent = mock.NewManagementComponent(mockCtrl)

var e = MakeCreateRecoveryCodeEndpoint(mockManagementComponent)

var realm = "master"
var userID = "123-456-789"
var ctx = context.Background()
var req = make(map[string]string)
req["realm"] = realm
req["userID"] = userID

mockManagementComponent.EXPECT().CreateRecoveryCode(ctx, realm, userID).Return("123456", nil).Times(1)
var res, err = e(ctx, req)
assert.Nil(t, err)
assert.Equal(t, "123456", res)

}

func TestGetCredentialsForUserEndpoint(t *testing.T) {
var mockCtrl = gomock.NewController(t)
defer mockCtrl.Finish()
Expand Down

0 comments on commit 201a518

Please sign in to comment.