Skip to content

Commit

Permalink
Management of the account status (#23)
Browse files Browse the repository at this point in the history
Management of the account status
  • Loading branch information
fperot74 committed Apr 12, 2019
1 parent 41cdb32 commit 2750ee8
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 0 deletions.
23 changes: 23 additions & 0 deletions api/management/swagger-api_management.yaml
Expand Up @@ -225,6 +225,29 @@ paths:
responses:
200:
description: successful operation
/realms/{realm}/users/{userID}/status:
get:
tags:
- Users
summary: Get the account status for the user (User enabled and has a second factor)
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:
200:
description: successful operation
content:
application/json:
/realms/{realm}/users/{userID}/role-mappings/clients/{clientID}:
get:
tags:
Expand Down
16 changes: 16 additions & 0 deletions cmd/keycloakb/keycloak_bridge.go
Expand Up @@ -419,6 +419,16 @@ func main() {
getUsersEndpoint = ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Every(time.Second), rateLimit["management"]))(getUsersEndpoint)
}

var getUserAccountStatusEndpoint endpoint.Endpoint
{
getUserAccountStatusEndpoint = management.MakeGetUserAccountStatusEndpoint(keycloakComponent)
getUserAccountStatusEndpoint = middleware.MakeEndpointInstrumentingMW(influxMetrics.NewHistogram("get_user_accountstatus"))(getUserAccountStatusEndpoint)
getUserAccountStatusEndpoint = middleware.MakeEndpointLoggingMW(log.With(managementLogger, "mw", "endpoint"))(getUserAccountStatusEndpoint)
getUserAccountStatusEndpoint = middleware.MakeEndpointTracingMW(tracer, "get_users_endpoint")(getUserAccountStatusEndpoint)
getUserAccountStatusEndpoint = middleware.MakeEndpointTokenForRealmMW(log.With(managementLogger, "mw", "endpoint"))(getUserAccountStatusEndpoint)
getUserAccountStatusEndpoint = ratelimit.NewErroringLimiter(rate.NewLimiter(rate.Every(time.Second), rateLimit["management"]))(getUserAccountStatusEndpoint)
}

var getRolesEndpoint endpoint.Endpoint
{
getRolesEndpoint = management.MakeGetRolesEndpoint(keycloakComponent)
Expand Down Expand Up @@ -538,6 +548,7 @@ func main() {
UpdateUser: updateUserEndpoint,
DeleteUser: deleteUserEndpoint,
GetUsers: getUsersEndpoint,
GetUserAccountStatus: getUserAccountStatusEndpoint,
GetRoles: getRolesEndpoint,
GetRole: getRoleEndpoint,
GetClientRoles: getClientRolesEndpoint,
Expand Down Expand Up @@ -595,6 +606,8 @@ func main() {
var deleteUserHandler = ConfigureManagementHandler(ComponentName, ComponentID, idGenerator, keycloakClient, tracer, logger)(managementEndpoints.DeleteUser)
var getUsersHandler = ConfigureManagementHandler(ComponentName, ComponentID, idGenerator, keycloakClient, tracer, logger)(managementEndpoints.GetUsers)

var getUserAccountStatusHandler = ConfigureManagementHandler(ComponentName, ComponentID, idGenerator, keycloakClient, tracer, logger)(managementEndpoints.GetUserAccountStatus)

var getClientRoleForUserHandler = ConfigureManagementHandler(ComponentName, ComponentID, idGenerator, keycloakClient, tracer, logger)(managementEndpoints.GetClientRoleForUser)
var addClientRoleToUserHandler = ConfigureManagementHandler(ComponentName, ComponentID, idGenerator, keycloakClient, tracer, logger)(managementEndpoints.AddClientRoleToUser)
var getRealmRoleForUserHandler = ConfigureManagementHandler(ComponentName, ComponentID, idGenerator, keycloakClient, tracer, logger)(managementEndpoints.GetRealmRoleForUser)
Expand Down Expand Up @@ -624,6 +637,9 @@ func main() {
managementSubroute.Path("/realms/{realm}/users/{userID}").Methods("PUT").Handler(updateUserHandler)
managementSubroute.Path("/realms/{realm}/users/{userID}").Methods("DELETE").Handler(deleteUserHandler)

// account status
managementSubroute.Path("/realms/{realm}/users/{userID}/status").Methods("GET").Handler(getUserAccountStatusHandler)

managementSubroute.Path("/realms/{realm}/users/{userID}/role-mappings/clients/{clientID}").Methods("GET").Handler(getClientRoleForUserHandler)
managementSubroute.Path("/realms/{realm}/users/{userID}/role-mappings/clients/{clientID}").Methods("POST").Handler(addClientRoleToUserHandler)
managementSubroute.Path("/realms/{realm}/users/{userID}/role-mappings/realm").Methods("GET").Handler(getRealmRoleForUserHandler)
Expand Down
19 changes: 19 additions & 0 deletions pkg/management/component.go
Expand Up @@ -39,6 +39,7 @@ type Component interface {
UpdateUser(ctx context.Context, realmName, userID string, user api.UserRepresentation) error
GetUsers(ctx context.Context, realmName, group string, paramKV ...string) ([]api.UserRepresentation, error)
CreateUser(ctx context.Context, realmName string, user api.UserRepresentation) (string, error)
GetUserAccountStatus(ctx context.Context, realmName, userID string) (bool, error)
GetClientRolesForUser(ctx context.Context, realmName, userID, clientID string) ([]api.RoleRepresentation, error)
AddClientRolesToUser(ctx context.Context, realmName, userID, clientID string, roles []api.RoleRepresentation) error
GetRealmRolesForUser(ctx context.Context, realmName, userID string) ([]api.RoleRepresentation, error)
Expand Down Expand Up @@ -298,6 +299,24 @@ func (c *component) GetUsers(ctx context.Context, realmName string, group string
return usersRep, nil
}

// GetUserAccountStatus gets the user status : user should be enabled in Keycloak and have multifactor activated
func (c *component) GetUserAccountStatus(ctx context.Context, realmName, userID string) (bool, error) {
var accessToken = ctx.Value("access_token").(string)

userKc, err := c.keycloakClient.GetUser(accessToken, realmName, userID)

if err != nil {
return false, err
}

if !*userKc.Enabled {
return false, nil
}

creds, err := c.GetCredentialsForUser(ctx, realmName, userID)
return len(creds) > 0, err
}

func (c *component) GetClientRolesForUser(ctx context.Context, realmName, userID, clientID string) ([]api.RoleRepresentation, error) {
var accessToken = ctx.Value("access_token").(string)

Expand Down
76 changes: 76 additions & 0 deletions pkg/management/component_test.go
Expand Up @@ -555,6 +555,82 @@ func TestGetUsers(t *testing.T) {
}
}

func TestGetUserAccountStatus(t *testing.T) {
var mockCtrl = gomock.NewController(t)
defer mockCtrl.Finish()
var mockKeycloakClient = mock.NewKeycloakClient(mockCtrl)

var managementComponent = NewComponent(mockKeycloakClient)

var accessToken = "TOKEN=="
var realmReq = "master"
var realmName = "aRealm"
var userID = "789-789-456"

// GetUser returns an error
{
var userRep kc.UserRepresentation
mockKeycloakClient.EXPECT().GetUser(accessToken, realmName, userID).Return(userRep, fmt.Errorf("Unexpected error")).Times(1)
var ctx = context.WithValue(context.Background(), "access_token", accessToken)
_, err := managementComponent.GetUserAccountStatus(ctx, realmName, userID)
assert.NotNil(t, err)
}

// GetUser returns a non-enabled user
{
var userRep kc.UserRepresentation
enabled := false
userRep.Enabled = &enabled
mockKeycloakClient.EXPECT().GetUser(accessToken, realmName, userID).Return(userRep, nil).Times(1)
var ctx = context.WithValue(context.Background(), "access_token", accessToken)
status, err := managementComponent.GetUserAccountStatus(ctx, realmName, userID)
assert.Nil(t, err)
assert.False(t, status)
}

// GetUser returns an enabled user but GetCredentialsForUser fails
{
var userRep kc.UserRepresentation
enabled := true
userRep.Enabled = &enabled
mockKeycloakClient.EXPECT().GetUser(accessToken, realmName, userID).Return(userRep, nil).Times(1)
mockKeycloakClient.EXPECT().GetCredentialsForUser(accessToken, realmReq, realmName, userID).Return(nil, fmt.Errorf("Unexpected error")).Times(1)
var ctx = context.WithValue(context.Background(), "access_token", accessToken)
ctx = context.WithValue(ctx, "realm", realmReq)
_, err := managementComponent.GetUserAccountStatus(ctx, realmName, userID)
assert.NotNil(t, err)
}

// GetUser returns an enabled user but GetCredentialsForUser have no credential
{
var userRep kc.UserRepresentation
enabled := true
userRep.Enabled = &enabled
mockKeycloakClient.EXPECT().GetUser(accessToken, realmName, userID).Return(userRep, nil).Times(1)
mockKeycloakClient.EXPECT().GetCredentialsForUser(accessToken, realmReq, realmName, userID).Return([]kc.CredentialRepresentation{}, nil).Times(1)
var ctx = context.WithValue(context.Background(), "access_token", accessToken)
ctx = context.WithValue(ctx, "realm", realmReq)
status, err := managementComponent.GetUserAccountStatus(ctx, realmName, userID)
assert.Nil(t, err)
assert.False(t, status)
}

// GetUser returns an enabled user and GetCredentialsForUser have credentials
{
var userRep kc.UserRepresentation
var creds kc.CredentialRepresentation
enabled := true
userRep.Enabled = &enabled
mockKeycloakClient.EXPECT().GetUser(accessToken, realmName, userID).Return(userRep, nil).Times(1)
mockKeycloakClient.EXPECT().GetCredentialsForUser(accessToken, realmReq, realmName, userID).Return([]kc.CredentialRepresentation{creds}, nil).Times(1)
var ctx = context.WithValue(context.Background(), "access_token", accessToken)
ctx = context.WithValue(ctx, "realm", realmReq)
status, err := managementComponent.GetUserAccountStatus(ctx, realmName, userID)
assert.Nil(t, err)
assert.True(t, status)
}
}

func TestGetClientRolesForUser(t *testing.T) {
var mockCtrl = gomock.NewController(t)
defer mockCtrl.Finish()
Expand Down
10 changes: 10 additions & 0 deletions pkg/management/endpoint.go
Expand Up @@ -19,6 +19,7 @@ type Endpoints struct {
UpdateUser endpoint.Endpoint
GetUsers endpoint.Endpoint
CreateUser endpoint.Endpoint
GetUserAccountStatus endpoint.Endpoint
GetClientRoleForUser endpoint.Endpoint
AddClientRoleToUser endpoint.Endpoint
GetRealmRoleForUser endpoint.Endpoint
Expand All @@ -42,6 +43,7 @@ type ManagementComponent interface {
UpdateUser(ctx context.Context, realmName, userID string, user api.UserRepresentation) error
GetUsers(ctx context.Context, realmName, group string, paramKV ...string) ([]api.UserRepresentation, error)
CreateUser(ctx context.Context, realmName string, user api.UserRepresentation) (string, error)
GetUserAccountStatus(ctx context.Context, realmName, userID string) (bool, error)
GetClientRolesForUser(ctx context.Context, realmName, userID, clientID string) ([]api.RoleRepresentation, error)
AddClientRolesToUser(ctx context.Context, realmName, userID, clientID string, roles []api.RoleRepresentation) error
GetRealmRolesForUser(ctx context.Context, realmName, userID string) ([]api.RoleRepresentation, error)
Expand Down Expand Up @@ -160,6 +162,14 @@ func MakeGetUsersEndpoint(managementComponent ManagementComponent) endpoint.Endp
}
}

func MakeGetUserAccountStatusEndpoint(managementComponent ManagementComponent) endpoint.Endpoint {
return func(ctx context.Context, req interface{}) (interface{}, error) {
var m = req.(map[string]string)

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

func MakeGetClientRolesForUserEndpoint(managementComponent ManagementComponent) endpoint.Endpoint {
return func(ctx context.Context, req interface{}) (interface{}, error) {
var m = req.(map[string]string)
Expand Down
23 changes: 23 additions & 0 deletions pkg/management/endpoint_test.go
Expand Up @@ -264,6 +264,29 @@ func TestGetUsersEndpoint(t *testing.T) {
}
}

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

var mockManagementComponent = mock.NewManagementComponent(mockCtrl)

var e = MakeGetUserAccountStatusEndpoint(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().GetUserAccountStatus(ctx, realm, userID).Return(false, nil).Times(1)
var _, err = e(ctx, req)
assert.Nil(t, err)
}
}

func TestGetClientRolesForUserEndpoint(t *testing.T) {
var mockCtrl = gomock.NewController(t)
defer mockCtrl.Finish()
Expand Down
13 changes: 13 additions & 0 deletions pkg/management/mock/component.go

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

13 changes: 13 additions & 0 deletions pkg/middleware/mock/management_component.go

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

0 comments on commit 2750ee8

Please sign in to comment.