Repository: Nerzal/gocloak
Pull request #363 (2022-08-27 22:27:10)

add missing attributes to ProtocolMappersConfig
In my case I'm missing some attributes for the protocol mapper `role_list`

Actually when looking at the doc and keycloak code, `ProtocolMappersConfig` should be a `map[string]string`, I guess that would break things... This change fixes it for me, good enough for me :)

codecov[bot]: # [Codecov](https://codecov.io/gh/Nerzal/gocloak/pull/363?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nerzal) Report
> Merging [#363](https://codecov.io/gh/Nerzal/gocloak/pull/363?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nerzal) (18daf77) into [main](https://codecov.io/gh/Nerzal/gocloak/commit/fe4f627eaf1bff988ee5df2fd0d0b87daac6c074?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nerzal) (fe4f627) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##             main     #363   +/-   ##
=======================================
  Coverage   77.16%   77.16%           
=======================================
  Files           4        4           
  Lines        2071     2071           
=======================================
  Hits         1598     1598           
  Misses        324      324           
  Partials      149      149           
```


| [Impacted Files](https://codecov.io/gh/Nerzal/gocloak/pull/363?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nerzal) | Coverage Δ | |
|---|---|---|
| [models.go](https://codecov.io/gh/Nerzal/gocloak/pull/363/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nerzal#diff-bW9kZWxzLmdv) | `93.28% <ø> (ø)` | |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Nerzal)


Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 9e1d67a..d59a6c3 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -9,7 +9,6 @@ linters:
     - gofmt
     - misspell
     - gosec
-    - maligned
     - unconvert
     - revive
     - gocognit
diff --git a/client.go b/client.go
index d4c88c6..71227ab 100644
--- a/client.go
+++ b/client.go
@@ -1548,7 +1548,23 @@ func (client *gocloak) GetGroupsByRole(ctx context.Context, token, realm string,
 	var result []*Group
 	resp, err := client.getRequestWithBearerAuth(ctx, token).
 		SetResult(&result).
-		Get(fmt.Sprintf("%s/%s/%s", client.getAdminRealmURL(realm, "roles"), roleName, "groups"))
+		Get(client.getAdminRealmURL(realm, "roles", roleName, "groups"))
+
+	if err := checkForError(resp, err, errMessage); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// GetGroupsByClientRole gets groups with specified roles assigned of given client within a realm
+func (client *gocloak) GetGroupsByClientRole(ctx context.Context, token, realm string, roleName string, clientID string) ([]*Group, error) {
+	const errMessage = "could not get groups"
+
+	var result []*Group
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, "clients", clientID, "roles", roleName, "groups"))
 
 	if err := checkForError(resp, err, errMessage); err != nil {
 		return nil, err
@@ -2015,6 +2031,22 @@ func (client *gocloak) GetCompositeRealmRoles(ctx context.Context, token, realm,
 	return result, nil
 }
 
+// GetCompositeRolesByRoleID returns all realm composite roles associated with the given client role
+func (client *gocloak) GetCompositeRolesByRoleID(ctx context.Context, token, realm, roleID string) ([]*Role, error) {
+	const errMessage = "could not get composite client roles by role id"
+
+	var result []*Role
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, "roles-by-id", roleID, "composites"))
+
+	if err = checkForError(resp, err, errMessage); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
 // GetCompositeRealmRolesByRoleID returns all realm composite roles associated with the given client role
 func (client *gocloak) GetCompositeRealmRolesByRoleID(ctx context.Context, token, realm, roleID string) ([]*Role, error) {
 	const errMessage = "could not get composite client roles by role id"
@@ -3816,11 +3848,11 @@ func (client *gocloak) UpdateRequiredAction(ctx context.Context, token string, r
 	if NilOrEmpty(requiredAction.ProviderID) {
 		return errors.New("providerId is required for updating a required action")
 	}
-	_, err := client.getRequestWithBearerAuth(ctx, token).
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
 		SetBody(requiredAction).
 		Put(client.getAdminRealmURL(realm, "authentication", "required-actions", *requiredAction.ProviderID))
 
-	return err
+	return checkForError(resp, err, errMessage)
 }
 
 // DeleteRequiredAction updates a required action for a given realm
diff --git a/gocloak.go b/gocloak.go
index ac80346..dfb8ff9 100644
--- a/gocloak.go
+++ b/gocloak.go
@@ -189,6 +189,8 @@ type GoCloak interface {
 	GetGroups(ctx context.Context, accessToken, realm string, params GetGroupsParams) ([]*Group, error)
 	// GetGroupsByRole gets groups with specified roles assigned of given realm
 	GetGroupsByRole(ctx context.Context, accessToken, realm string, roleName string) ([]*Group, error)
+	// GetGroupsByClientRole gets groups with specified roles assigned of given client within a realm
+	GetGroupsByClientRole(ctx context.Context, accessToken, realm string, roleName string, clientID string) ([]*Group, error)
 	// GetGroupsCount gets groups count of the given realm
 	GetGroupsCount(ctx context.Context, token, realm string, params GetGroupsParams) (int, error)
 	// GetGroup gets the given group
@@ -250,6 +252,8 @@ type GoCloak interface {
 	DeleteRealmRoleComposite(ctx context.Context, token, realm, roleName string, roles []Role) error
 	// GetCompositeRealmRoles returns all realm composite roles associated with the given realm role
 	GetCompositeRealmRoles(ctx context.Context, token, realm, roleName string) ([]*Role, error)
+	// GetCompositeRolesByRoleID returns all realm composite roles associated with the given client role
+	GetCompositeRolesByRoleID(ctx context.Context, token, realm, roleID string) ([]*Role, error)
 	// GetCompositeRealmRolesByRoleID returns all realm composite roles associated with the given client role
 	GetCompositeRealmRolesByRoleID(ctx context.Context, token, realm, roleID string) ([]*Role, error)
 	// GetCompositeRealmRolesByUserID returns all realm roles and composite roles assigned to the given user
@@ -319,22 +323,22 @@ type GoCloak interface {
 	ClearUserCache(ctx context.Context, token, realm string) error
 	// ClearKeysCache clears realm cache
 	ClearKeysCache(ctx context.Context, token, realm string) error
-	//GetAuthenticationFlows get all authentication flows from a realm
+	// GetAuthenticationFlows get all authentication flows from a realm
 	GetAuthenticationFlows(ctx context.Context, token, realm string) ([]*AuthenticationFlowRepresentation, error)
-	//Create a new Authentication flow in a realm
+	// Create a new Authentication flow in a realm
 	CreateAuthenticationFlow(ctx context.Context, token, realm string, flow AuthenticationFlowRepresentation) error
-	//DeleteAuthenticationFlow deletes a flow in a realm with the given ID
+	// DeleteAuthenticationFlow deletes a flow in a realm with the given ID
 	DeleteAuthenticationFlow(ctx context.Context, token, realm, flowID string) error
-	//GetAuthenticationExecutions retrieves all executions of a given flow
+	// GetAuthenticationExecutions retrieves all executions of a given flow
 	GetAuthenticationExecutions(ctx context.Context, token, realm, flow string) ([]*ModifyAuthenticationExecutionRepresentation, error)
-	//CreateAuthenticationExecution creates a new execution for the given flow name in the given realm
+	// CreateAuthenticationExecution creates a new execution for the given flow name in the given realm
 	CreateAuthenticationExecution(ctx context.Context, token, realm, flow string, execution CreateAuthenticationExecutionRepresentation) error
-	//UpdateAuthenticationExecution updates an authentication execution for the given flow in the given realm
+	// UpdateAuthenticationExecution updates an authentication execution for the given flow in the given realm
 	UpdateAuthenticationExecution(ctx context.Context, token, realm, flow string, execution ModifyAuthenticationExecutionRepresentation) error
 	// DeleteAuthenticationExecution delete a single execution with the given ID
 	DeleteAuthenticationExecution(ctx context.Context, token, realm, executionID string) error
 
-	//CreateAuthenticationExecutionFlow creates a new flow execution for the given flow name in the given realm
+	// CreateAuthenticationExecutionFlow creates a new flow execution for the given flow name in the given realm
 	CreateAuthenticationExecutionFlow(ctx context.Context, token, realm, flow string, execution CreateAuthenticationExecutionFlowRepresentation) error
 
 	// *** Users ***
diff --git a/models.go b/models.go
index 2de1eac..d3cd449 100644
--- a/models.go
+++ b/models.go
@@ -429,6 +429,9 @@ type ProtocolMappersConfig struct {
 	UsermodelClientRoleMappingClientID *string `json:"usermodel.clientRoleMapping.clientId,omitempty"`
 	IncludedClientAudience             *string `json:"included.client.audience,omitempty"`
 	FullPath                           *string `json:"full.path,omitempty"`
+	AttributeName                      *string `json:"attribute.name,omitempty"`
+	AttributeNameFormat                *string `json:"attribute.nameformat,omitempty"`
+	Single                             *string `json:"single,omitempty"`
 }
 
 // Client is a ClientRepresentation
@@ -701,100 +704,120 @@ type RolesRepresentation struct {
 
 // RealmRepresentation represents a realm
 type RealmRepresentation struct {
-	AccessCodeLifespan                  *int                 `json:"accessCodeLifespan,omitempty"`
-	AccessCodeLifespanLogin             *int                 `json:"accessCodeLifespanLogin,omitempty"`
-	AccessCodeLifespanUserAction        *int                 `json:"accessCodeLifespanUserAction,omitempty"`
-	AccessTokenLifespan                 *int                 `json:"accessTokenLifespan,omitempty"`
-	AccessTokenLifespanForImplicitFlow  *int                 `json:"accessTokenLifespanForImplicitFlow,omitempty"`
-	AccountTheme                        *string              `json:"accountTheme,omitempty"`
-	ActionTokenGeneratedByAdminLifespan *int                 `json:"actionTokenGeneratedByAdminLifespan,omitempty"`
-	ActionTokenGeneratedByUserLifespan  *int                 `json:"actionTokenGeneratedByUserLifespan,omitempty"`
-	AdminEventsDetailsEnabled           *bool                `json:"adminEventsDetailsEnabled,omitempty"`
-	AdminEventsEnabled                  *bool                `json:"adminEventsEnabled,omitempty"`
-	AdminTheme                          *string              `json:"adminTheme,omitempty"`
-	Attributes                          *map[string]string   `json:"attributes,omitempty"`
-	AuthenticationFlows                 *[]interface{}       `json:"authenticationFlows,omitempty"`
-	AuthenticatorConfig                 *[]interface{}       `json:"authenticatorConfig,omitempty"`
-	BrowserFlow                         *string              `json:"browserFlow,omitempty"`
-	BrowserSecurityHeaders              *map[string]string   `json:"browserSecurityHeaders,omitempty"`
-	BruteForceProtected                 *bool                `json:"bruteForceProtected,omitempty"`
-	ClientAuthenticationFlow            *string              `json:"clientAuthenticationFlow,omitempty"`
-	ClientScopeMappings                 *map[string]string   `json:"clientScopeMappings,omitempty"`
-	ClientScopes                        *[]ClientScope       `json:"clientScopes,omitempty"`
-	Clients                             *[]Client            `json:"clients,omitempty"`
-	Components                          interface{}          `json:"components,omitempty"`
-	DefaultDefaultClientScopes          *[]string            `json:"defaultDefaultClientScopes,omitempty"`
-	DefaultGroups                       *[]string            `json:"defaultGroups,omitempty"`
-	DefaultLocale                       *string              `json:"defaultLocale,omitempty"`
-	DefaultOptionalClientScopes         *[]string            `json:"defaultOptionalClientScopes,omitempty"`
-	DefaultRole                         *Role                `json:"defaultRole,omitempty"`
-	DefaultRoles                        *[]string            `json:"defaultRoles,omitempty"`
-	DefaultSignatureAlgorithm           *string              `json:"defaultSignatureAlgorithm,omitempty"`
-	DirectGrantFlow                     *string              `json:"directGrantFlow,omitempty"`
-	DisplayName                         *string              `json:"displayName,omitempty"`
-	DisplayNameHTML                     *string              `json:"displayNameHtml,omitempty"`
-	DockerAuthenticationFlow            *string              `json:"dockerAuthenticationFlow,omitempty"`
-	DuplicateEmailsAllowed              *bool                `json:"duplicateEmailsAllowed,omitempty"`
-	EditUsernameAllowed                 *bool                `json:"editUsernameAllowed,omitempty"`
-	EmailTheme                          *string              `json:"emailTheme,omitempty"`
-	Enabled                             *bool                `json:"enabled,omitempty"`
-	EnabledEventTypes                   *[]string            `json:"enabledEventTypes,omitempty"`
-	EventsEnabled                       *bool                `json:"eventsEnabled,omitempty"`
-	EventsExpiration                    *int64               `json:"eventsExpiration,omitempty"`
-	EventsListeners                     *[]string            `json:"eventsListeners,omitempty"`
-	FailureFactor                       *int                 `json:"failureFactor,omitempty"`
-	FederatedUsers                      *[]interface{}       `json:"federatedUsers,omitempty"`
-	Groups                              *[]interface{}       `json:"groups,omitempty"`
-	ID                                  *string              `json:"id,omitempty"`
-	IdentityProviderMappers             *[]interface{}       `json:"identityProviderMappers,omitempty"`
-	IdentityProviders                   *[]interface{}       `json:"identityProviders,omitempty"`
-	InternationalizationEnabled         *bool                `json:"internationalizationEnabled,omitempty"`
-	KeycloakVersion                     *string              `json:"keycloakVersion,omitempty"`
-	LoginTheme                          *string              `json:"loginTheme,omitempty"`
-	LoginWithEmailAllowed               *bool                `json:"loginWithEmailAllowed,omitempty"`
-	MaxDeltaTimeSeconds                 *int                 `json:"maxDeltaTimeSeconds,omitempty"`
-	MaxFailureWaitSeconds               *int                 `json:"maxFailureWaitSeconds,omitempty"`
-	MinimumQuickLoginWaitSeconds        *int                 `json:"minimumQuickLoginWaitSeconds,omitempty"`
-	NotBefore                           *int                 `json:"notBefore,omitempty"`
-	OfflineSessionIdleTimeout           *int                 `json:"offlineSessionIdleTimeout,omitempty"`
-	OfflineSessionMaxLifespan           *int                 `json:"offlineSessionMaxLifespan,omitempty"`
-	OfflineSessionMaxLifespanEnabled    *bool                `json:"offlineSessionMaxLifespanEnabled,omitempty"`
-	OtpPolicyAlgorithm                  *string              `json:"otpPolicyAlgorithm,omitempty"`
-	OtpPolicyDigits                     *int                 `json:"otpPolicyDigits,omitempty"`
-	OtpPolicyInitialCounter             *int                 `json:"otpPolicyInitialCounter,omitempty"`
-	OtpPolicyLookAheadWindow            *int                 `json:"otpPolicyLookAheadWindow,omitempty"`
-	OtpPolicyPeriod                     *int                 `json:"otpPolicyPeriod,omitempty"`
-	OtpPolicyType                       *string              `json:"otpPolicyType,omitempty"`
-	OtpSupportedApplications            *[]string            `json:"otpSupportedApplications,omitempty"`
-	PasswordPolicy                      *string              `json:"passwordPolicy,omitempty"`
-	PermanentLockout                    *bool                `json:"permanentLockout,omitempty"`
-	ProtocolMappers                     *[]interface{}       `json:"protocolMappers,omitempty"`
-	QuickLoginCheckMilliSeconds         *int64               `json:"quickLoginCheckMilliSeconds,omitempty"`
-	Realm                               *string              `json:"realm,omitempty"`
-	RefreshTokenMaxReuse                *int                 `json:"refreshTokenMaxReuse,omitempty"`
-	RegistrationAllowed                 *bool                `json:"registrationAllowed,omitempty"`
-	RegistrationEmailAsUsername         *bool                `json:"registrationEmailAsUsername,omitempty"`
-	RegistrationFlow                    *string              `json:"registrationFlow,omitempty"`
-	RememberMe                          *bool                `json:"rememberMe,omitempty"`
-	RequiredActions                     *[]interface{}       `json:"requiredActions,omitempty"`
-	ResetCredentialsFlow                *string              `json:"resetCredentialsFlow,omitempty"`
-	ResetPasswordAllowed                *bool                `json:"resetPasswordAllowed,omitempty"`
-	RevokeRefreshToken                  *bool                `json:"revokeRefreshToken,omitempty"`
-	Roles                               *RolesRepresentation `json:"roles,omitempty"`
-	ScopeMappings                       *[]interface{}       `json:"scopeMappings,omitempty"`
-	SMTPServer                          *map[string]string   `json:"smtpServer,omitempty"`
-	SslRequired                         *string              `json:"sslRequired,omitempty"`
-	SsoSessionIdleTimeout               *int                 `json:"ssoSessionIdleTimeout,omitempty"`
-	SsoSessionIdleTimeoutRememberMe     *int                 `json:"ssoSessionIdleTimeoutRememberMe,omitempty"`
-	SsoSessionMaxLifespan               *int                 `json:"ssoSessionMaxLifespan,omitempty"`
-	SsoSessionMaxLifespanRememberMe     *int                 `json:"ssoSessionMaxLifespanRememberMe,omitempty"`
-	SupportedLocales                    *[]string            `json:"supportedLocales,omitempty"`
-	UserFederationMappers               *[]interface{}       `json:"userFederationMappers,omitempty"`
-	UserFederationProviders             *[]interface{}       `json:"userFederationProviders,omitempty"`
-	UserManagedAccessAllowed            *bool                `json:"userManagedAccessAllowed,omitempty"`
-	Users                               *[]User              `json:"users,omitempty"`
-	VerifyEmail                         *bool                `json:"verifyEmail,omitempty"`
-	WaitIncrementSeconds                *int                 `json:"waitIncrementSeconds,omitempty"`
+	AccessCodeLifespan                                        *int                 `json:"accessCodeLifespan,omitempty"`
+	AccessCodeLifespanLogin                                   *int                 `json:"accessCodeLifespanLogin,omitempty"`
+	AccessCodeLifespanUserAction                              *int                 `json:"accessCodeLifespanUserAction,omitempty"`
+	AccessTokenLifespan                                       *int                 `json:"accessTokenLifespan,omitempty"`
+	AccessTokenLifespanForImplicitFlow                        *int                 `json:"accessTokenLifespanForImplicitFlow,omitempty"`
+	AccountTheme                                              *string              `json:"accountTheme,omitempty"`
+	ActionTokenGeneratedByAdminLifespan                       *int                 `json:"actionTokenGeneratedByAdminLifespan,omitempty"`
+	ActionTokenGeneratedByUserLifespan                        *int                 `json:"actionTokenGeneratedByUserLifespan,omitempty"`
+	AdminEventsDetailsEnabled                                 *bool                `json:"adminEventsDetailsEnabled,omitempty"`
+	AdminEventsEnabled                                        *bool                `json:"adminEventsEnabled,omitempty"`
+	AdminTheme                                                *string              `json:"adminTheme,omitempty"`
+	Attributes                                                *map[string]string   `json:"attributes,omitempty"`
+	AuthenticationFlows                                       *[]interface{}       `json:"authenticationFlows,omitempty"`
+	AuthenticatorConfig                                       *[]interface{}       `json:"authenticatorConfig,omitempty"`
+	BrowserFlow                                               *string              `json:"browserFlow,omitempty"`
+	BrowserSecurityHeaders                                    *map[string]string   `json:"browserSecurityHeaders,omitempty"`
+	BruteForceProtected                                       *bool                `json:"bruteForceProtected,omitempty"`
+	ClientAuthenticationFlow                                  *string              `json:"clientAuthenticationFlow,omitempty"`
+	ClientScopeMappings                                       *map[string]string   `json:"clientScopeMappings,omitempty"`
+	ClientScopes                                              *[]ClientScope       `json:"clientScopes,omitempty"`
+	Clients                                                   *[]Client            `json:"clients,omitempty"`
+	Components                                                interface{}          `json:"components,omitempty"`
+	DefaultDefaultClientScopes                                *[]string            `json:"defaultDefaultClientScopes,omitempty"`
+	DefaultGroups                                             *[]string            `json:"defaultGroups,omitempty"`
+	DefaultLocale                                             *string              `json:"defaultLocale,omitempty"`
+	DefaultOptionalClientScopes                               *[]string            `json:"defaultOptionalClientScopes,omitempty"`
+	DefaultRole                                               *Role                `json:"defaultRole,omitempty"`
+	DefaultRoles                                              *[]string            `json:"defaultRoles,omitempty"`
+	DefaultSignatureAlgorithm                                 *string              `json:"defaultSignatureAlgorithm,omitempty"`
+	DirectGrantFlow                                           *string              `json:"directGrantFlow,omitempty"`
+	DisplayName                                               *string              `json:"displayName,omitempty"`
+	DisplayNameHTML                                           *string              `json:"displayNameHtml,omitempty"`
+	DockerAuthenticationFlow                                  *string              `json:"dockerAuthenticationFlow,omitempty"`
+	DuplicateEmailsAllowed                                    *bool                `json:"duplicateEmailsAllowed,omitempty"`
+	EditUsernameAllowed                                       *bool                `json:"editUsernameAllowed,omitempty"`
+	EmailTheme                                                *string              `json:"emailTheme,omitempty"`
+	Enabled                                                   *bool                `json:"enabled,omitempty"`
+	EnabledEventTypes                                         *[]string            `json:"enabledEventTypes,omitempty"`
+	EventsEnabled                                             *bool                `json:"eventsEnabled,omitempty"`
+	EventsExpiration                                          *int64               `json:"eventsExpiration,omitempty"`
+	EventsListeners                                           *[]string            `json:"eventsListeners,omitempty"`
+	FailureFactor                                             *int                 `json:"failureFactor,omitempty"`
+	FederatedUsers                                            *[]interface{}       `json:"federatedUsers,omitempty"`
+	Groups                                                    *[]interface{}       `json:"groups,omitempty"`
+	ID                                                        *string              `json:"id,omitempty"`
+	IdentityProviderMappers                                   *[]interface{}       `json:"identityProviderMappers,omitempty"`
+	IdentityProviders                                         *[]interface{}       `json:"identityProviders,omitempty"`
+	InternationalizationEnabled                               *bool                `json:"internationalizationEnabled,omitempty"`
+	KeycloakVersion                                           *string              `json:"keycloakVersion,omitempty"`
+	LoginTheme                                                *string              `json:"loginTheme,omitempty"`
+	LoginWithEmailAllowed                                     *bool                `json:"loginWithEmailAllowed,omitempty"`
+	MaxDeltaTimeSeconds                                       *int                 `json:"maxDeltaTimeSeconds,omitempty"`
+	MaxFailureWaitSeconds                                     *int                 `json:"maxFailureWaitSeconds,omitempty"`
+	MinimumQuickLoginWaitSeconds                              *int                 `json:"minimumQuickLoginWaitSeconds,omitempty"`
+	NotBefore                                                 *int                 `json:"notBefore,omitempty"`
+	OfflineSessionIdleTimeout                                 *int                 `json:"offlineSessionIdleTimeout,omitempty"`
+	OfflineSessionMaxLifespan                                 *int                 `json:"offlineSessionMaxLifespan,omitempty"`
+	OfflineSessionMaxLifespanEnabled                          *bool                `json:"offlineSessionMaxLifespanEnabled,omitempty"`
+	OtpPolicyAlgorithm                                        *string              `json:"otpPolicyAlgorithm,omitempty"`
+	OtpPolicyDigits                                           *int                 `json:"otpPolicyDigits,omitempty"`
+	OtpPolicyInitialCounter                                   *int                 `json:"otpPolicyInitialCounter,omitempty"`
+	OtpPolicyLookAheadWindow                                  *int                 `json:"otpPolicyLookAheadWindow,omitempty"`
+	OtpPolicyPeriod                                           *int                 `json:"otpPolicyPeriod,omitempty"`
+	OtpPolicyType                                             *string              `json:"otpPolicyType,omitempty"`
+	OtpSupportedApplications                                  *[]string            `json:"otpSupportedApplications,omitempty"`
+	PasswordPolicy                                            *string              `json:"passwordPolicy,omitempty"`
+	PermanentLockout                                          *bool                `json:"permanentLockout,omitempty"`
+	ProtocolMappers                                           *[]interface{}       `json:"protocolMappers,omitempty"`
+	QuickLoginCheckMilliSeconds                               *int64               `json:"quickLoginCheckMilliSeconds,omitempty"`
+	Realm                                                     *string              `json:"realm,omitempty"`
+	RefreshTokenMaxReuse                                      *int                 `json:"refreshTokenMaxReuse,omitempty"`
+	RegistrationAllowed                                       *bool                `json:"registrationAllowed,omitempty"`
+	RegistrationEmailAsUsername                               *bool                `json:"registrationEmailAsUsername,omitempty"`
+	RegistrationFlow                                          *string              `json:"registrationFlow,omitempty"`
+	RememberMe                                                *bool                `json:"rememberMe,omitempty"`
+	RequiredActions                                           *[]interface{}       `json:"requiredActions,omitempty"`
+	ResetCredentialsFlow                                      *string              `json:"resetCredentialsFlow,omitempty"`
+	ResetPasswordAllowed                                      *bool                `json:"resetPasswordAllowed,omitempty"`
+	RevokeRefreshToken                                        *bool                `json:"revokeRefreshToken,omitempty"`
+	Roles                                                     *RolesRepresentation `json:"roles,omitempty"`
+	ScopeMappings                                             *[]interface{}       `json:"scopeMappings,omitempty"`
+	SMTPServer                                                *map[string]string   `json:"smtpServer,omitempty"`
+	SslRequired                                               *string              `json:"sslRequired,omitempty"`
+	SsoSessionIdleTimeout                                     *int                 `json:"ssoSessionIdleTimeout,omitempty"`
+	SsoSessionIdleTimeoutRememberMe                           *int                 `json:"ssoSessionIdleTimeoutRememberMe,omitempty"`
+	SsoSessionMaxLifespan                                     *int                 `json:"ssoSessionMaxLifespan,omitempty"`
+	SsoSessionMaxLifespanRememberMe                           *int                 `json:"ssoSessionMaxLifespanRememberMe,omitempty"`
+	SupportedLocales                                          *[]string            `json:"supportedLocales,omitempty"`
+	UserFederationMappers                                     *[]interface{}       `json:"userFederationMappers,omitempty"`
+	UserFederationProviders                                   *[]interface{}       `json:"userFederationProviders,omitempty"`
+	UserManagedAccessAllowed                                  *bool                `json:"userManagedAccessAllowed,omitempty"`
+	Users                                                     *[]User              `json:"users,omitempty"`
+	VerifyEmail                                               *bool                `json:"verifyEmail,omitempty"`
+	WaitIncrementSeconds                                      *int                 `json:"waitIncrementSeconds,omitempty"`
+	WebAuthnPolicyAcceptableAaguids                           *[]string            `json:"webAuthnPolicyAcceptableAaguids,omitempty"`
+	WebAuthnPolicyAttestationConveyancePreference             *string              `json:"webAuthnPolicyAttestationConveyancePreference,omitempty"`
+	WebAuthnPolicyAuthenticatorAttachment                     *string              `json:"webAuthnPolicyAuthenticatorAttachment,omitempty"`
+	WebAuthnPolicyAvoidSameAuthenticatorRegister              *bool                `json:"webAuthnPolicyAvoidSameAuthenticatorRegister,omitempty"`
+	WebAuthnPolicyCreateTimeout                               *int                 `json:"webAuthnPolicyCreateTimeout,omitempty"`
+	WebAuthnPolicyPasswordlessAcceptableAaguids               *[]string            `json:"webAuthnPolicyPasswordlessAcceptableAaguids,omitempty"`
+	WebAuthnPolicyPasswordlessAttestationConveyancePreference *string              `json:"webAuthnPolicyPasswordlessAttestationConveyancePreference,omitempty"`
+	WebAuthnPolicyPasswordlessAuthenticatorAttachment         *string              `json:"webAuthnPolicyPasswordlessAuthenticatorAttachment,omitempty"`
+	WebAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister  *bool                `json:"webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister,omitempty"`
+	WebAuthnPolicyPasswordlessCreateTimeout                   *int                 `json:"webAuthnPolicyPasswordlessCreateTimeout,omitempty"`
+	WebAuthnPolicyPasswordlessRequireResidentKey              *string              `json:"webAuthnPolicyPasswordlessRequireResidentKey,omitempty"`
+	WebAuthnPolicyPasswordlessRpEntityName                    *string              `json:"webAuthnPolicyPasswordlessRpEntityName,omitempty"`
+	WebAuthnPolicyPasswordlessRpID                            *string              `json:"webAuthnPolicyPasswordlessRpId,omitempty"`
+	WebAuthnPolicyPasswordlessSignatureAlgorithms             *[]string            `json:"webAuthnPolicyPasswordlessSignatureAlgorithms,omitempty"`
+	WebAuthnPolicyPasswordlessUserVerificationRequirement     *string              `json:"webAuthnPolicyPasswordlessUserVerificationRequirement,omitempty"`
+	WebAuthnPolicyRequireResidentKey                          *string              `json:"webAuthnPolicyRequireResidentKey,omitempty"`
+	WebAuthnPolicyRpEntityName                                *string              `json:"webAuthnPolicyRpEntityName,omitempty"`
+	WebAuthnPolicyRpID                                        *string              `json:"webAuthnPolicyRpId,omitempty"`
+	WebAuthnPolicySignatureAlgorithms                         *[]string            `json:"webAuthnPolicySignatureAlgorithms,omitempty"`
+	WebAuthnPolicyUserVerificationRequirement                 *string              `json:"webAuthnPolicyUserVerificationRequirement,omitempty"`
 }
 
 // AuthenticationFlowRepresentation represents an authentication flow of a realm
@@ -1037,6 +1060,23 @@ type ServerInfoRepesentation struct {
 	PasswordPolicies       []*PasswordPolicy         `json:"passwordPolicies,omitempty"`
 	ProtocolMapperTypes    *ProtocolMapperTypes      `json:"protocolMapperTypes,omitempty"`
 	BuiltinProtocolMappers *BuiltinProtocolMappers   `json:"builtinProtocolMappers,omitempty"`
+	Themes                 *Themes                   `json:"themes,omitempty"`
+}
+
+// ThemeRepresentation contains the theme name and locales
+type ThemeRepresentation struct {
+	Name    string   `json:"name,omitempty"`
+	Locales []string `json:"locales,omitempty"`
+}
+
+// Themes contains the available keycloak themes with locales
+type Themes struct {
+	Accounts []ThemeRepresentation `json:"account,omitempty"`
+	Admin    []ThemeRepresentation `json:"admin,omitempty"`
+	Common   []ThemeRepresentation `json:"common,omitempty"`
+	Email    []ThemeRepresentation `json:"email,omitempty"`
+	Login    []ThemeRepresentation `json:"login,omitempty"`
+	Welcome  []ThemeRepresentation `json:"welcome,omitempty"`
 }
 
 // FederatedIdentityRepresentation represents an user federated identity


Test patch:
diff --git a/client_test.go b/client_test.go
index 55524ba..cdbf960 100644
--- a/client_test.go
+++ b/client_test.go
@@ -2216,6 +2216,47 @@ func Test_GetGroupsBriefRepresentation(t *testing.T) {
 	require.Fail(t, "GetGroupsBriefRepresentation failed")
 }
 
+func Test_GetGroupsByRole(t *testing.T) {
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+	ctx := context.Background()
+
+	grpTearDown, groupID := CreateGroup(t, client)
+	defer grpTearDown()
+
+	roleTearDown, roleName := CreateRealmRole(t, client)
+	defer roleTearDown()
+
+	role, _ := client.GetRealmRole(ctx, token.AccessToken, cfg.GoCloak.Realm, roleName)
+	_ = client.AddRealmRoleToGroup(ctx, token.AccessToken, cfg.GoCloak.Realm, groupID, []gocloak.Role{*role})
+
+	groupsByRole, err := client.GetGroupsByRole(ctx, token.AccessToken, cfg.GoCloak.Realm, *role.Name)
+	require.NoError(t, err, "GetGroupsByRole failed")
+	require.Len(t, groupsByRole, 1)
+}
+
+func Test_GetGroupsByClientRole(t *testing.T) {
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+	ctx := context.Background()
+
+	grpTearDown, groupID := CreateGroup(t, client)
+	defer grpTearDown()
+
+	clientRoleTeardown, roleName := CreateClientRole(t, client)
+	defer clientRoleTeardown()
+
+	role, _ := client.GetClientRole(ctx, token.AccessToken, cfg.GoCloak.Realm, gocloakClientID, roleName)
+
+	_ = client.AddClientRoleToGroup(ctx, token.AccessToken, cfg.GoCloak.Realm, gocloakClientID, groupID, []gocloak.Role{*role})
+
+	groupsByClientRole, err := client.GetGroupsByClientRole(ctx, token.AccessToken, cfg.GoCloak.Realm, roleName, gocloakClientID)
+	require.NoError(t, err, "GetGroupsByClientRole failed")
+	require.Len(t, groupsByClientRole, 1)
+}
+
 func Test_GetGroupFull(t *testing.T) {
 	// t.Parallel()
 	cfg := GetConfig(t)
@@ -6265,7 +6306,7 @@ func Test_ImportIdentityProviderConfig(t *testing.T) {
 }
 
 func Test_ImportIdentityProviderConfigFromFile(t *testing.T) {
-	//// t.Parallel()
+	// t.Parallel()
 	cfg := GetConfig(t)
 	client := NewClientWithDebug(t)
 	token := GetAdminToken(t, client)
diff --git a/model_test.go b/model_test.go
index fdb3c6d..5504893 100644
--- a/model_test.go
+++ b/model_test.go
@@ -167,7 +167,7 @@ func TestParseAPIErrType(t *testing.T) {
 }
 
 func TestStringer(t *testing.T) {
-	//nested structs
+	// nested structs
 	actions := []string{"someAction", "anotherAction"}
 	access := gocloak.AccessRepresentation{
 		Manage:      gocloak.BoolP(true),