Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CLOUDTRUST-2357] Add a way to solve temporary locked user #198

Merged
merged 1 commit into from
Mar 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
53 changes: 37 additions & 16 deletions api/management/swagger-api_management.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -655,11 +655,11 @@ paths:
type: array
items:
$ref: '#/components/schemas/Credential'
/realms/{realm}/users/{userID}/recovery-code:
post:
/realms/{realm}/users/{userID}/credentials/{credentialID}:
delete:
tags:
- Credentials
summary: Set a recovery code for the user
summary: Delete the credentials for the user
parameters:
- name: realm
in: path
Expand All @@ -673,20 +673,20 @@ paths:
required: true
schema:
type: string
- name: credentialID
in: path
description: Credential id
required: true
schema:
type: string
responses:
201:
200:
description: successful operation
content:
text/plain:
schema:
type: string
409:
description: recovery code already set
/realms/{realm}/users/{userID}/credentials/{credentialID}:
/realms/{realm}/users/{userID}/clear-login-failures:
delete:
tags:
- Credentials
summary: Delete the credentials for the user
- login-failures
summary: Delete the login failures for the user
parameters:
- name: realm
in: path
Expand All @@ -700,15 +700,36 @@ paths:
required: true
schema:
type: string
- name: credentialID
responses:
200:
description: successful operation
/realms/{realm}/users/{userID}/recovery-code:
post:
tags:
- Credentials
summary: Set a recovery code for the user
parameters:
- name: realm
in: path
description: Credential id
description: realm name (not id!)
required: true
schema:
type: string
- name: userID
in: path
description: User id
required: true
schema:
type: string
responses:
200:
201:
description: successful operation
content:
text/plain:
schema:
type: string
409:
description: recovery code already set
/realms/{realm}/roles:
get:
tags:
Expand Down
4 changes: 4 additions & 0 deletions cmd/keycloakb/keycloak_bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ func main() {
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"]),
ClearUserLoginFailures: prepareEndpoint(management.MakeClearUserLoginFailures(keycloakComponent), "clear_user_login_failures_endpoint", influxMetrics, managementLogger, tracer, rateLimit["management"]),

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"]),
Expand Down Expand Up @@ -841,6 +842,7 @@ func main() {

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)
var clearUserLoginFailuresHandler = configureManagementHandler(keycloakb.ComponentName, ComponentID, idGenerator, keycloakClient, audienceRequired, tracer, logger)(managementEndpoints.ClearUserLoginFailures)

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)
Expand Down Expand Up @@ -899,6 +901,8 @@ func main() {
managementSubroute.Path("/realms/{realm}/users/{userID}/credentials").Methods("GET").Handler(getCredentialsForUserHandler)
managementSubroute.Path("/realms/{realm}/users/{userID}/credentials/{credentialID}").Methods("DELETE").Handler(deleteCredentialsForUserHandler)

managementSubroute.Path("/realms/{realm}/users/{userID}/clear-login-failures").Methods("DELETE").Handler(clearUserLoginFailuresHandler)

// roles
managementSubroute.Path("/realms/{realm}/roles").Methods("GET").Handler(getRolesHandler)
managementSubroute.Path("/realms/{realm}/roles-by-id/{roleID}").Methods("GET").Handler(getRoleHandler)
Expand Down
12 changes: 12 additions & 0 deletions pkg/management/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ var (
MGMTCreateRecoveryCode = newAction("MGMT_CreateRecoveryCode", security.ScopeGroup)
MGMTGetCredentialsForUser = newAction("MGMT_GetCredentialsForUser", security.ScopeGroup)
MGMTDeleteCredentialsForUser = newAction("MGMT_DeleteCredentialsForUser", security.ScopeGroup)
MGMTClearUserLoginFailures = newAction("MGMT_ClearUserLoginFailures", security.ScopeGroup)
MGMTGetRoles = newAction("MGMT_GetRoles", security.ScopeRealm)
MGMTGetRole = newAction("MGMT_GetRole", security.ScopeRealm)
MGMTGetGroups = newAction("MGMT_GetGroups", security.ScopeRealm)
Expand Down Expand Up @@ -371,6 +372,17 @@ func (c *authorizationComponentMW) DeleteCredentialsForUser(ctx context.Context,
return c.next.DeleteCredentialsForUser(ctx, realmName, userID, credentialID)
}

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

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

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

func (c *authorizationComponentMW) GetRoles(ctx context.Context, realmName string) ([]api.RoleRepresentation, error) {
var action = MGMTGetRoles.String()
var targetRealm = realmName
Expand Down
7 changes: 7 additions & 0 deletions pkg/management/authorization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ func TestDeny(t *testing.T) {
err = authorizationMW.DeleteCredentialsForUser(ctx, realmName, userID, credentialID)
assert.Equal(t, security.ForbiddenError{}, err)

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

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

Expand Down Expand Up @@ -431,6 +434,10 @@ func TestAllowed(t *testing.T) {
err = authorizationMW.DeleteCredentialsForUser(ctx, realmName, userID, credentialID)
assert.Nil(t, err)

mockManagementComponent.EXPECT().ClearUserLoginFailures(ctx, realmName, userID).Return(nil).Times(1)
err = authorizationMW.ClearUserLoginFailures(ctx, realmName, userID)
assert.Nil(t, err)

mockManagementComponent.EXPECT().GetRoles(ctx, realmName).Return([]api.RoleRepresentation{}, nil).Times(1)
_, err = authorizationMW.GetRoles(ctx, realmName)
assert.Nil(t, err)
Expand Down
15 changes: 15 additions & 0 deletions pkg/management/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type KeycloakClient interface {
UpdateLabelCredential(accessToken string, realmName string, userID string, credentialID string, label string) error
DeleteCredential(accessToken string, realmName string, userID string, credentialID string) error
CreateShadowUser(accessToken string, realmName string, userID string, provider string, fedID kc.FederatedIdentityRepresentation) error
ClearUserLoginFailures(accessToken string, realmName, userID string) error
}

// Component is the management component interface.
Expand Down Expand Up @@ -91,6 +92,7 @@ type Component interface {
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
ClearUserLoginFailures(ctx context.Context, realmName, userID string) error
GetRoles(ctx context.Context, realmName string) ([]api.RoleRepresentation, error)
GetRole(ctx context.Context, realmName string, roleID string) (api.RoleRepresentation, error)
GetClientRoles(ctx context.Context, realmName, idClient string) ([]api.RoleRepresentation, error)
Expand Down Expand Up @@ -790,6 +792,19 @@ func (c *component) DeleteCredentialsForUser(ctx context.Context, realmName stri
return err
}

func (c *component) ClearUserLoginFailures(ctx context.Context, realmName, userID string) error {
var accessToken = ctx.Value(cs.CtContextAccessToken).(string)
var err = c.keycloakClient.ClearUserLoginFailures(accessToken, realmName, userID)
if err != nil {
c.logger.Warn(ctx, "err", err.Error())
return err
}

c.reportEvent(ctx, "LOGIN_FAILURE_CLEARED", database.CtEventRealmName, realmName, database.CtEventUserID, userID)

return nil
}

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

Expand Down
29 changes: 29 additions & 0 deletions pkg/management/component_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2110,6 +2110,35 @@ func TestDeleteCredentialsForUser(t *testing.T) {
})
}

func TestClearUserLoginFailures(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 logger = log.NewNopLogger()

var accessToken = "TOKEN=="
var realm = "master"
var userID = "1245-7854-8963"
var allowedTrustIDGroups = []string{"grp1", "grp2"}
var ctx = context.WithValue(context.TODO(), cs.CtContextAccessToken, accessToken)
var component = NewComponent(mockKeycloakClient, mockEventDBModule, mockConfigurationDBModule, allowedTrustIDGroups, logger)

t.Run("Error occured", func(t *testing.T) {
var expectedError = errors.New("kc error")
mockKeycloakClient.EXPECT().ClearUserLoginFailures(accessToken, realm, userID).Return(expectedError)
var err = component.ClearUserLoginFailures(ctx, realm, userID)
assert.Equal(t, expectedError, err)
})
t.Run("Success", func(t *testing.T) {
mockKeycloakClient.EXPECT().ClearUserLoginFailures(accessToken, realm, userID).Return(nil)
mockEventDBModule.EXPECT().ReportEvent(ctx, "LOGIN_FAILURE_CLEARED", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
var err = component.ClearUserLoginFailures(ctx, realm, userID)
assert.Nil(t, err)
})
}

func TestGetRoles(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 @@ -42,6 +42,7 @@ type Endpoints struct {
CreateRecoveryCode endpoint.Endpoint
GetCredentialsForUser endpoint.Endpoint
DeleteCredentialsForUser endpoint.Endpoint
ClearUserLoginFailures endpoint.Endpoint

GetRoles endpoint.Endpoint
GetRole endpoint.Endpoint
Expand Down Expand Up @@ -94,6 +95,7 @@ type ManagementComponent interface {
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
ClearUserLoginFailures(ctx context.Context, realmName, userID string) error
GetRoles(ctx context.Context, realmName string) ([]api.RoleRepresentation, error)
GetRole(ctx context.Context, realmName string, roleID string) (api.RoleRepresentation, error)
GetClientRoles(ctx context.Context, realmName, idClient string) ([]api.RoleRepresentation, error)
Expand Down Expand Up @@ -445,6 +447,15 @@ func MakeDeleteCredentialsForUserEndpoint(managementComponent ManagementComponen
}
}

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

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

// MakeGetRolesEndpoint creates an endpoint for GetRoles
func MakeGetRolesEndpoint(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 @@ -771,6 +771,28 @@ func TestDeleteCredentialsForUserEndpoint(t *testing.T) {
}
}

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

var mockManagementComponent = mock.NewManagementComponent(mockCtrl)
var e = MakeClearUserLoginFailures(mockManagementComponent)

// No error - Without param
{
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().ClearUserLoginFailures(ctx, realm, userID).Return(nil).Times(1)
var _, err = e(ctx, req)
assert.Nil(t, err)
}
}

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