Skip to content

Commit

Permalink
[CLOUDTRUST-4637] Get users profile
Browse files Browse the repository at this point in the history
  • Loading branch information
fperot74 committed Feb 13, 2023
1 parent 83e30b5 commit 8a34394
Show file tree
Hide file tree
Showing 17 changed files with 461 additions and 176 deletions.
2 changes: 1 addition & 1 deletion api/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (c *Client) ResetPassword(accessToken string, realmName, userID string, cre
return c.put(accessToken, url.Path(resetPasswordPath), url.Param("realm", realmName), url.Param("id", userID), body.JSON(cred))
}

// Logout all sessions of the user.
// LogoutAllSessions of the user.
func (c *Client) LogoutAllSessions(accessToken string, realmName, userID string) error {
var _, err = c.post(accessToken, nil, url.Path(logoutPath), url.Param("realm", realmName), url.Param("id", userID))
return err
Expand Down
2 changes: 1 addition & 1 deletion api/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (c *Client) GetRoles(accessToken string, realmName string) ([]keycloak.Role
return resp, err
}

// GetRoles gets all roles for the realm or client with their attributes
// GetRolesWithAttributes gets all roles for the realm or client with their attributes
func (c *Client) GetRolesWithAttributes(accessToken string, realmName string) ([]keycloak.RoleRepresentation, error) {
var resp = []keycloak.RoleRepresentation{}
var err = c.get(accessToken, &resp, url.Path(rolePath), url.Param("realm", realmName), query.Add("briefRepresentation", "false"))
Expand Down
9 changes: 9 additions & 0 deletions api/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
expiredToUAcceptancePath = adminRootPath + "/expired-tou-acceptance"
getSupportInfoPath = adminRootPath + "/support-infos"
generateTrustIDAuthToken = "/auth/realms/:realmReq/trustid-auth-token/realms/:realm/users/:userId/generate"
profilePath = userPath + "/profile"
)

// GetUsers returns a list of users, filtered according to the query parameters.
Expand Down Expand Up @@ -191,8 +192,16 @@ func (c *Client) GetSupportInfo(accessToken string, email string) ([]keycloak.Em
return emailInfos, err
}

// GenerateTrustIDAuthToken generates a TrustID auth token
func (c *Client) GenerateTrustIDAuthToken(accessToken string, reqRealmName string, realmName string, userID string) (string, error) {
var token keycloak.TrustIDAuthTokenRepresentation
err := c.get(accessToken, &token, url.Path(generateTrustIDAuthToken), url.Param("realmReq", reqRealmName), url.Param("realm", realmName), url.Param("userId", userID))
return *token.Token, err
}

// GetUserProfile gets the configuration of attribute management
func (c *Client) GetUserProfile(accessToken string, realmName string) (keycloak.UserProfileRepresentation, error) {
var profile keycloak.UserProfileRepresentation
err := c.get(accessToken, &profile, url.Path(profilePath), url.Param("realm", realmName))
return profile, err
}
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"time"
)

// KeycloakURIProvider interface
type KeycloakURIProvider interface {
GetDefaultKey() string
GetAllBaseURIs() []string
Expand Down
89 changes: 89 additions & 0 deletions definitions-profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package keycloak

import "strings"

// UserProfileRepresentation struct
type UserProfileRepresentation struct {
Attributes []ProfileAttrbRepresentation `json:"attributes"`
Groups []ProfileGroupRepresentation `json:"groups"`
}

// ProfileAttrbRepresentation struct
type ProfileAttrbRepresentation struct {
Name *string `json:"name,omitempty"`
DisplayName *string `json:"displayName,omitempty"`
Group *string `json:"group,omitempty"`
Required *ProfileAttrbRequiredRepresentation `json:"required,omitempty"`
Permissions *ProfileAttrbPermissionsRepresentation `json:"permissions,omitempty"`
Validations ProfileAttrbValidationRepresentation `json:"validations,omitempty"`
Selector *ProfileAttrbSelectorRepresentation `json:"selector,omitempty"`
Annotations map[string]string `json:"annotations,omitempty"`
}

// ProfileAttrbValidationRepresentation struct
// Known keys:
// - email: empty
// - length: min, max (int/string), trim-disabled (boolean as string)
// - integer: min, max (integer as string)
// - double: min, max (double as string)
// - options: options (array of allowed values)
// - pattern: pattern (regex as string), error-message (string)
// - local-date: empty
// - uri: empty
// - username-prohibited-characters: error-message (string)
// - person-name-prohibited-characters: error-message (string)
type ProfileAttrbValidationRepresentation map[string]ProfileAttrValidatorRepresentation

type ProfileAttrValidatorRepresentation map[string]interface{}

// ProfileAttrbRequiredRepresentation struct
type ProfileAttrbRequiredRepresentation struct {
Roles []string `json:"roles,omitempty"`
Scopes []string `json:"scopes,omitempty"`
}

// ProfileAttrbPermissionsRepresentation struct
type ProfileAttrbPermissionsRepresentation struct {
View []string `json:"view,omitempty"`
Edit []string `json:"edit,omitempty"`
}

// ProfileAttrbSelectorRepresentation struct
type ProfileAttrbSelectorRepresentation struct {
Scopes []string `json:"scopes,omitempty"`
}

// ProfileGroupRepresentation struct
type ProfileGroupRepresentation struct {
Name *string `json:"name,omitempty"`
DisplayHeader *string `displayHeader:"name,omitempty"`
DisplayDescription *string `displayDescription:"name,omitempty"`
Annotations map[string]string `annotations:"name,omitempty"`
}

// IsAnnotationTrue checks if an annotation is true
func (attrb *ProfileAttrbRepresentation) IsAnnotationTrue(key string) bool {
return attrb.AnnotationEqualsIgnoreCase(key, "true")
}

// IsAnnotationFalse checks if an annotation is false
func (attrb *ProfileAttrbRepresentation) IsAnnotationFalse(key string) bool {
return attrb.AnnotationEqualsIgnoreCase(key, "false")
}

// AnnotationEqualsIgnoreCase checks if an annotation
func (attrb *ProfileAttrbRepresentation) AnnotationEqualsIgnoreCase(key string, value string) bool {
return attrb.AnnotationMatches(key, func(attrbValue string) bool {
return strings.EqualFold(value, attrbValue)
})
}

// AnnotationMatches checks if an annotation
func (attrb *ProfileAttrbRepresentation) AnnotationMatches(key string, matcher func(value string) bool) bool {
if attrb.Annotations != nil {
if attrbValue, ok := attrb.Annotations[key]; ok {
return matcher(attrbValue)
}
}
return false
}
27 changes: 27 additions & 0 deletions definitions-profile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package keycloak

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestAttribute(t *testing.T) {
var attrb = ProfileAttrbRepresentation{}
var key = "key"

t.Run("Annotations is nil", func(t *testing.T) {
assert.False(t, attrb.IsAnnotationFalse(key))
assert.False(t, attrb.IsAnnotationTrue(key))
})
t.Run("Annotations is empty", func(t *testing.T) {
attrb.Annotations = map[string]string{}
assert.False(t, attrb.IsAnnotationFalse(key))
assert.False(t, attrb.IsAnnotationTrue(key))
})
t.Run("Annotations is empty", func(t *testing.T) {
attrb.Annotations[key] = "false"
assert.True(t, attrb.IsAnnotationFalse(key))
assert.False(t, attrb.IsAnnotationTrue(key))
})
}
17 changes: 16 additions & 1 deletion definitions.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package keycloak

import (
"strings"
)

// AdminEventRepresentation struct
type AdminEventRepresentation struct {
AuthDetails *AuthDetailsRepresentation `json:"authDetails,omitempty"`
Expand Down Expand Up @@ -442,7 +446,7 @@ type RealmRepresentation struct {
AdminEventsDetailsEnabled *bool `json:"adminEventsDetailsEnabled,omitempty"`
AdminEventsEnabled *bool `json:"adminEventsEnabled,omitempty"`
AdminTheme *string `json:"adminTheme,omitempty"`
Attributes *map[string]interface{} `json:"attributes,omitempty"`
Attributes *map[string]*string `json:"attributes,omitempty"`
AuthenticationFlows *[]AuthenticationFlowRepresentation `json:"authenticationFlows,omitempty"`
AuthenticatorConfig *[]AuthenticatorConfigRepresentation `json:"authenticatorConfig,omitempty"`
BrowserFlow *string `json:"browserFlow,omitempty"`
Expand Down Expand Up @@ -811,6 +815,17 @@ type EmailInfoRepresentation struct {
CreationDate *int64 `json:"creationDate,omitempty"`
}

// TrustIDAuthTokenRepresentation struct
type TrustIDAuthTokenRepresentation struct {
Token *string `json:"token"`
}

// IsUserProfileEnabled tells if user profile is enabled for the given realm
func (r *RealmRepresentation) IsUserProfileEnabled() bool {
if r.Attributes != nil && *r.Attributes != nil {
if v, ok := (*r.Attributes)["userProfileEnabled"]; ok {
return v != nil && strings.EqualFold(*v, "true")
}
}
return false
}
29 changes: 29 additions & 0 deletions definitions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package keycloak

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestIsUserProfileEnabled(t *testing.T) {
var realm = RealmRepresentation{Attributes: nil}
var bFalse = "FALSE"
var bTrue = "TRue"

t.Run("No attributes", func(t *testing.T) {
assert.False(t, realm.IsUserProfileEnabled())
})
t.Run("Empty attributes", func(t *testing.T) {
realm.Attributes = &map[string]*string{}
assert.False(t, realm.IsUserProfileEnabled())
})
t.Run("User profile attribute is false", func(t *testing.T) {
(*realm.Attributes)["userProfileEnabled"] = &bFalse
assert.False(t, realm.IsUserProfileEnabled())
})
t.Run("User profile attribute is true", func(t *testing.T) {
(*realm.Attributes)["userProfileEnabled"] = &bTrue
assert.True(t, realm.IsUserProfileEnabled())
})
}
19 changes: 19 additions & 0 deletions errormessages_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package keycloak

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestHTTPError(t *testing.T) {
var err = HTTPError{HTTPStatus: 400, Message: "error message"}
assert.Equal(t, "400:error message", err.Error())
}

func TestClientDetailedError(t *testing.T) {
var err = ClientDetailedError{HTTPStatus: 400, Message: "error message"}
assert.Equal(t, "400:error message", err.Error())
assert.Equal(t, 400, err.Status())
assert.Equal(t, "error message", err.ErrorMessage())
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/cloudtrust/keycloak-client/v2
go 1.17

require (
github.com/cloudtrust/common-service/v2 v2.6.0
github.com/cloudtrust/common-service/v2 v2.6.4
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/gbrlsnchs/jwt/v2 v2.0.0
github.com/go-kit/kit v0.12.0
Expand Down
Loading

0 comments on commit 8a34394

Please sign in to comment.