Repository: Nerzal/gocloak
Pull request #359 (2022-06-29 18:04:33)

GetComponentsWithParams and UpdateComponent added
Thanks for your contribution!

There is two new methods.
1. GetComponentsWithParams() . According to keycloak's documentation GET /{realm}/components has some query parameters. I create new function to compatibility with old versions, because changing of GetComponents s bad idea.
2 UpdateComponent - allow to update exists component.

All new methods covered by tests.


Nerzal: Hi, 

can you please run

> go fmt -w -s .

That should fix the pipeline :) 
Tolyar: > 

Do you mean gofmt -w -s . ? Because go fmt does not support -s and -w flags. Problem that gofmt -w -s . do nothing in my branch. git diff show zero changes. I use go1.18.4

Nerzal: hmn does goftm ./... does anything ? 
Tolyar: > hmn does goftm ./... does anything ?

Yes. I think this is because I've used gofumpt instead of gofmt as default formatter. Looks like gofmt thinks that all is ok but linter on github is don't. Another strange thing - I've enabled all actions from yours repo in my repo and all was passed before I've created PR.
Tolyar: > hmn does goftm ./... does anything ?

:) I found a problem. It is not code problem but actions. This is the error from linter

>Run cat test.log
cat: test.log: No such file or directory
Error: Process completed with exit code [1](https://github.com/Nerzal/gocloak/runs/7682602770?check_suite_focus=true#step:10:1).




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 38498f4..ae1f870 100644
--- a/client.go
+++ b/client.go
@@ -158,7 +158,6 @@ func injectTracingHeaders(ctx context.Context, req *resty.Request) *resty.Reques
 
 	// inject tracing header into request
 	err := tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
-
 	if err != nil {
 		return req
 	}
@@ -1440,6 +1439,56 @@ func (client *gocloak) GetComponents(ctx context.Context, token, realm string) (
 	return result, nil
 }
 
+// GetComponentsWithParams get all components in realm with query params
+func (client *gocloak) GetComponentsWithParams(ctx context.Context, token, realm string, params GetComponentsParams) ([]*Component, error) {
+	const errMessage = "could not get components"
+	var result []*Component
+
+	queryParams, err := GetQueryParams(params)
+	if err != nil {
+		return nil, errors.Wrap(err, errMessage)
+	}
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetResult(&result).
+		SetQueryParams(queryParams).
+		Get(client.getAdminRealmURL(realm, "components"))
+
+	if err := checkForError(resp, err, errMessage); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// GetComponent get exactly one component by ID
+func (client *gocloak) GetComponent(ctx context.Context, token, realm string, componentID string) (*Component, error) {
+	const errMessage = "could not get components"
+	var result *Component
+
+	componentURL := fmt.Sprintf("components/%s", componentID)
+
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, componentURL))
+
+	if err := checkForError(resp, err, errMessage); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+// UpdateComponent updates the given component
+func (client *gocloak) UpdateComponent(ctx context.Context, token, realm string, component Component) error {
+	const errMessage = "could not update component"
+
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetBody(component).
+		Put(client.getAdminRealmURL(realm, "components", PString(component.ID)))
+
+	return checkForError(resp, err, errMessage)
+}
+
 // GetDefaultGroups returns a list of default groups
 func (client *gocloak) GetDefaultGroups(ctx context.Context, token, realm string) ([]*Group, error) {
 	const errMessage = "could not get default groups"
@@ -1519,6 +1568,23 @@ func (client *gocloak) GetGroup(ctx context.Context, token, realm, groupID strin
 	return &result, nil
 }
 
+// GetGroupByPath get group with path in realm
+func (client *gocloak) GetGroupByPath(ctx context.Context, token, realm, groupPath string) (*Group, error) {
+	const errMessage = "could not get group"
+
+	var result Group
+
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, "group-by-path", groupPath))
+
+	if err := checkForError(resp, err, errMessage); err != nil {
+		return nil, err
+	}
+
+	return &result, nil
+}
+
 // GetGroups get all groups in realm
 func (client *gocloak) GetGroups(ctx context.Context, token, realm string, params GetGroupsParams) ([]*Group, error) {
 	const errMessage = "could not get groups"
@@ -1548,7 +1614,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 +2097,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"
@@ -2196,7 +2294,7 @@ func (client *gocloak) ClearKeysCache(ctx context.Context, token, realm string)
 	return checkForError(resp, err, errMessage)
 }
 
-//GetAuthenticationFlows get all authentication flows from a realm
+// GetAuthenticationFlows get all authentication flows from a realm
 func (client *gocloak) GetAuthenticationFlows(ctx context.Context, token, realm string) ([]*AuthenticationFlowRepresentation, error) {
 	const errMessage = "could not retrieve authentication flows"
 	var result []*AuthenticationFlowRepresentation
@@ -2210,7 +2308,7 @@ func (client *gocloak) GetAuthenticationFlows(ctx context.Context, token, realm
 	return result, nil
 }
 
-//Create a new Authentication flow in a realm
+// Create a new Authentication flow in a realm
 func (client *gocloak) CreateAuthenticationFlow(ctx context.Context, token, realm string, flow AuthenticationFlowRepresentation) error {
 	const errMessage = "could not create authentication flows"
 	var result []*AuthenticationFlowRepresentation
@@ -2221,7 +2319,7 @@ func (client *gocloak) CreateAuthenticationFlow(ctx context.Context, token, real
 	return checkForError(resp, err, errMessage)
 }
 
-//DeleteAuthenticationFlow deletes a flow in a realm with the given ID
+// DeleteAuthenticationFlow deletes a flow in a realm with the given ID
 func (client *gocloak) DeleteAuthenticationFlow(ctx context.Context, token, realm, flowID string) error {
 	const errMessage = "could not delete authentication flows"
 	resp, err := client.getRequestWithBearerAuth(ctx, token).
@@ -2230,7 +2328,7 @@ func (client *gocloak) DeleteAuthenticationFlow(ctx context.Context, token, real
 	return checkForError(resp, err, errMessage)
 }
 
-//GetAuthenticationExecutions retrieves all executions of a given flow
+// GetAuthenticationExecutions retrieves all executions of a given flow
 func (client *gocloak) GetAuthenticationExecutions(ctx context.Context, token, realm, flow string) ([]*ModifyAuthenticationExecutionRepresentation, error) {
 	const errMessage = "could not retrieve authentication flows"
 	var result []*ModifyAuthenticationExecutionRepresentation
@@ -2244,7 +2342,7 @@ func (client *gocloak) GetAuthenticationExecutions(ctx context.Context, token, r
 	return result, nil
 }
 
-//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
 func (client *gocloak) CreateAuthenticationExecution(ctx context.Context, token, realm, flow string, execution CreateAuthenticationExecutionRepresentation) error {
 	const errMessage = "could not create authentication execution"
 	resp, err := client.getRequestWithBearerAuth(ctx, token).SetBody(execution).
@@ -2253,7 +2351,7 @@ func (client *gocloak) CreateAuthenticationExecution(ctx context.Context, token,
 	return checkForError(resp, err, errMessage)
 }
 
-//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
 func (client *gocloak) UpdateAuthenticationExecution(ctx context.Context, token, realm, flow string, execution ModifyAuthenticationExecutionRepresentation) error {
 	const errMessage = "could not update authentication execution"
 	resp, err := client.getRequestWithBearerAuth(ctx, token).SetBody(execution).
@@ -2271,7 +2369,7 @@ func (client *gocloak) DeleteAuthenticationExecution(ctx context.Context, token,
 	return checkForError(resp, err, errMessage)
 }
 
-//CreateAuthenticationExecutionFlow creates a new execution for the given flow name in the given realm
+// CreateAuthenticationExecutionFlow creates a new execution for the given flow name in the given realm
 func (client *gocloak) CreateAuthenticationExecutionFlow(ctx context.Context, token, realm, flow string, executionFlow CreateAuthenticationExecutionFlowRepresentation) error {
 	const errMessage = "could not create authentication execution flow"
 	resp, err := client.getRequestWithBearerAuth(ctx, token).SetBody(executionFlow).
@@ -3758,6 +3856,57 @@ func (client *gocloak) CreateClientScopesScopeMappingsRealmRoles(ctx context.Con
 	return checkForError(resp, err, errMessage)
 }
 
+// CreateRequiredAction creates a required action for a given realm
+func (client *gocloak) RegisterRequiredAction(ctx context.Context, token string, realm string, requiredAction RequiredActionProviderRepresentation) error {
+	const errMessage = "could not create required action"
+
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetBody(requiredAction).
+		Post(client.getAdminRealmURL(realm, "authentication", "register-required-action"))
+
+	if err := checkForError(resp, err, errMessage); err != nil {
+		return err
+	}
+
+	return err
+}
+
+// GetRequiredActions gets a list of required actions for a given realm
+func (client *gocloak) GetRequiredActions(ctx context.Context, token string, realm string) ([]*RequiredActionProviderRepresentation, error) {
+	const errMessage = "could not get required actions"
+	var result []*RequiredActionProviderRepresentation
+
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, "authentication", "required-actions"))
+
+	if err := checkForError(resp, err, errMessage); err != nil {
+		return nil, err
+	}
+
+	return result, err
+}
+
+// GetRequiredAction gets a required action for a given realm
+func (client *gocloak) GetRequiredAction(ctx context.Context, token string, realm string, alias string) (*RequiredActionProviderRepresentation, error) {
+	const errMessage = "could not get required action"
+	var result RequiredActionProviderRepresentation
+
+	if alias == "" {
+		return nil, errors.New("alias is required for getting a required action")
+	}
+
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, "authentication", "required-actions", alias))
+
+	if err := checkForError(resp, err, errMessage); err != nil {
+		return nil, err
+	}
+
+	return &result, err
+}
+
 // UpdateRequiredAction updates a required action for a given realm
 func (client *gocloak) UpdateRequiredAction(ctx context.Context, token string, realm string, requiredAction RequiredActionProviderRepresentation) error {
 	const errMessage = "could not update required action"
@@ -3765,10 +3914,27 @@ 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 checkForError(resp, err, errMessage)
+}
+
+// DeleteRequiredAction updates a required action for a given realm
+func (client *gocloak) DeleteRequiredAction(ctx context.Context, token string, realm string, alias string) error {
+	const errMessage = "could not delete required action"
+
+	if alias == "" {
+		return errors.New("alias is required for deleting a required action")
+	}
+	resp, err := client.getRequestWithBearerAuth(ctx, token).
+		Delete(client.getAdminRealmURL(realm, "authentication", "required-actions", alias))
+
+	if err := checkForError(resp, err, errMessage); err != nil {
+		return err
+	}
+
 	return err
 }
 
diff --git a/gocloak.go b/gocloak.go
index 91c4af3..75dccc3 100644
--- a/gocloak.go
+++ b/gocloak.go
@@ -179,6 +179,12 @@ type GoCloak interface {
 	GetKeyStoreConfig(ctx context.Context, accessToken, realm string) (*KeyStoreConfig, error)
 	// GetComponents gets components of the given realm
 	GetComponents(ctx context.Context, accessToken, realm string) ([]*Component, error)
+	// GetComponentsWithParams get all components in realm with query params
+	GetComponentsWithParams(ctx context.Context, token, realm string, params GetComponentsParams) ([]*Component, error)
+	// GetComponent get exactly one component by ID
+	GetComponent(ctx context.Context, token, realm string, componentID string) (*Component, error)
+	// UpdateComponent updates the given component
+	UpdateComponent(ctx context.Context, token, realm string, component Component) error
 	// GetDefaultGroups returns a list of default groups
 	GetDefaultGroups(ctx context.Context, accessToken, realm string) ([]*Group, error)
 	// AddDefaultGroup adds group to the list of default groups
@@ -189,10 +195,14 @@ 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
 	GetGroup(ctx context.Context, accessToken, realm, groupID string) (*Group, error)
+	// GetGroupByPath get group with path in realm
+	GetGroupByPath(ctx context.Context, token, realm, groupPath string) (*Group, error)
 	// GetGroupMembers get a list of users of group with id in realm
 	GetGroupMembers(ctx context.Context, accessToken, realm, groupID string, params GetGroupsParams) ([]*User, error)
 	// GetRoleMappingByGroupID gets the rolemapping for the given group id
@@ -250,6 +260,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 +331,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 ***
@@ -522,6 +534,14 @@ type GoCloak interface {
 	// RequiredActions API
 	// -------------------
 
+	// UpdateRequiredAction updates a required action for a given realm
+	RegisterRequiredAction(ctx context.Context, token string, realm string, requiredAction RequiredActionProviderRepresentation) error
 	// UpdateRequiredAction updates a required action for a given realm
 	UpdateRequiredAction(ctx context.Context, token string, realm string, requiredAction RequiredActionProviderRepresentation) error
+	// UpdateRequiredAction updates a required action for a given realm
+	GetRequiredAction(ctx context.Context, token string, realm string, alias string) (*RequiredActionProviderRepresentation, error)
+	// UpdateRequiredAction updates a required action for a given realm
+	GetRequiredActions(ctx context.Context, token string, realm string) ([]*RequiredActionProviderRepresentation, error)
+	// UpdateRequiredAction updates a required action for a given realm
+	DeleteRequiredAction(ctx context.Context, token string, realm string, alias string) error
 }
diff --git a/models.go b/models.go
index 2de1eac..36c360a 100644
--- a/models.go
+++ b/models.go
@@ -301,6 +301,13 @@ type GetUsersParams struct {
 	Username            *string `json:"username,omitempty"`
 }
 
+// GetComponentsParams represents the optional parameters for getting components
+type GetComponentsParams struct {
+	Name         *string `json:"name,omitempty"`
+	ProviderType *string `json:"provider,omitempty"`
+	ParentID     *string `json:"parent,omitempty"`
+}
+
 // ExecuteActionsEmail represents parameters for executing action emails
 type ExecuteActionsEmail struct {
 	UserID      *string   `json:"-"`
@@ -429,6 +436,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 +711,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 +1067,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
@@ -1321,7 +1368,6 @@ type RequiredActionProviderRepresentation struct {
 
 // prettyStringStruct returns struct formatted into pretty string
 func prettyStringStruct(t interface{}) string {
-
 	json, err := json.MarshalIndent(t, "", "\t")
 	if err != nil {
 		return ""
@@ -1348,6 +1394,7 @@ func (v *Attributes) String() string                                { return pre
 func (v *Access) String() string                                    { return prettyStringStruct(v) }
 func (v *UserGroup) String() string                                 { return prettyStringStruct(v) }
 func (v *GetUsersParams) String() string                            { return prettyStringStruct(v) }
+func (v *GetComponentsParams) String() string                       { return prettyStringStruct(v) }
 func (v *ExecuteActionsEmail) String() string                       { return prettyStringStruct(v) }
 func (v *Group) String() string                                     { return prettyStringStruct(v) }
 func (v *GroupsCount) String() string                               { return prettyStringStruct(v) }


Test patch:
diff --git a/client_test.go b/client_test.go
index 6a4209d..941569b 100644
--- a/client_test.go
+++ b/client_test.go
@@ -528,9 +528,10 @@ func NewClientWithDebug(t testing.TB) gocloak.GoCloak {
 }
 
 // FailRequest fails requests and returns an error
-//   err - returned error or nil to return the default error
-//   failN - number of requests to be failed
-//   skipN = number of requests to be executed and not failed by this function
+//
+//	err - returned error or nil to return the default error
+//	failN - number of requests to be failed
+//	skipN = number of requests to be executed and not failed by this function
 func FailRequest(client gocloak.GoCloak, err error, failN, skipN int) gocloak.GoCloak {
 	client.RestyClient().OnBeforeRequest(
 		func(c *resty.Client, r *resty.Request) error {
@@ -2215,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)
@@ -5279,7 +5321,6 @@ func Test_ErrorsGetAuthorizationPolicyScopes(t *testing.T) {
 				},
 			},
 		})
-
 	})
 
 	// Create SCOPE
@@ -6264,7 +6305,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)
@@ -6363,10 +6404,9 @@ func TestGocloak_CreateAuthenticationFlowsAndCreateAuthenticationExecutionAndFlo
 	cfg := GetConfig(t)
 	client := NewClientWithDebug(t)
 	token := GetAdminToken(t, client)
-	authExec :=
-		gocloak.CreateAuthenticationExecutionRepresentation{
-			Provider: gocloak.StringP("idp-auto-link"),
-		}
+	authExec := gocloak.CreateAuthenticationExecutionRepresentation{
+		Provider: gocloak.StringP("idp-auto-link"),
+	}
 	authFlow := gocloak.AuthenticationFlowRepresentation{
 		Alias:       gocloak.StringP("testauthflow2"),
 		BuiltIn:     gocloak.BoolP(false),
@@ -6490,6 +6530,68 @@ func TestGocloak_CreateAuthenticationFlowsAndCreateAuthenticationExecutionAndFlo
 	require.True(t, deleted, "Failed to delete authentication flow, no flow was deleted")
 }
 
+func TestGocloak_CreateAndGetRequiredAction(t *testing.T) {
+	t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+	requiredAction := gocloak.RequiredActionProviderRepresentation{
+		Alias:         gocloak.StringP("VERIFY_EMAIL_NEW"),
+		Config:        nil,
+		DefaultAction: gocloak.BoolP(false),
+		Enabled:       gocloak.BoolP(true),
+		Name:          gocloak.StringP("Verify Email new"),
+		Priority:      gocloak.Int32P(50),
+		ProviderID:    gocloak.StringP("VERIFY_EMAIL_NEW"),
+	}
+	err := client.RegisterRequiredAction(context.Background(), token.AccessToken, cfg.GoCloak.Realm, requiredAction)
+	require.NoError(t, err, "Failed to register required action")
+
+	ra, err := client.GetRequiredAction(context.Background(), token.AccessToken, cfg.GoCloak.Realm, *requiredAction.Alias)
+	require.NoError(t, err, "Failed to get required action")
+	require.NotNil(t, ra, "required action created must not be nil")
+	require.Equal(t, *ra.Alias, *requiredAction.Alias, "required action alias must be equal with template")
+	t.Logf("got required action: %+v", ra)
+
+	ras, err := client.GetRequiredActions(context.Background(), token.AccessToken, cfg.GoCloak.Realm)
+	require.NoError(t, err, "Failed to get required actions")
+
+	for _, r := range ras {
+		t.Logf("got required action: %+v", r)
+		if r.Alias != nil && *r.Alias == *ra.Alias {
+			goto FOUND_RA
+		}
+	}
+	require.Fail(t, "required action not found in list of required actions")
+
+FOUND_RA:
+
+	err = client.DeleteRequiredAction(context.Background(), token.AccessToken, cfg.GoCloak.Realm, *requiredAction.Alias)
+	require.NoError(t, err, "Failed to Delete required action")
+}
+
+func TestGocloak_GetUnknownRequiredAction(t *testing.T) {
+	t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+
+	ra, err := client.GetRequiredAction(context.Background(), token.AccessToken, cfg.GoCloak.Realm, "unknown_required_action")
+	require.Error(t, err, "Request should fail if no required action with the given name is there")
+	require.Nil(t, ra, "required action created must be nil if it could not be found")
+}
+
+func TestGocloak_GetEmptyAliasRequiredAction(t *testing.T) {
+	t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+
+	ra, err := client.GetRequiredAction(context.Background(), token.AccessToken, cfg.GoCloak.Realm, "")
+	require.Error(t, err, "Request should fail if no alias is given")
+	require.Nil(t, ra, "required action created must be nil if it could not be found")
+}
+
 func TestGocloak_UpdateRequiredAction(t *testing.T) {
 	t.Parallel()
 	cfg := GetConfig(t)
@@ -6507,3 +6609,113 @@ func TestGocloak_UpdateRequiredAction(t *testing.T) {
 	err := client.UpdateRequiredAction(context.Background(), token.AccessToken, cfg.GoCloak.Realm, requiredAction)
 	require.NoError(t, err, "Failed to update required action")
 }
+
+func CreateComponent(t *testing.T, client gocloak.GoCloak) (func(), *gocloak.Component) {
+	newComponent := &gocloak.Component{
+		Name:         GetRandomNameP("CreateComponent"),
+		ProviderID:   gocloak.StringP("rsa-generated"),
+		ProviderType: gocloak.StringP("org.keycloak.keys.KeyProvider"),
+	}
+	cfg := GetConfig(t)
+	token := GetAdminToken(t, client)
+	createdID, err := client.CreateComponent(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		*newComponent,
+	)
+	require.NoError(t, err, "CreateComponent failed")
+	tearDown := func() {
+		_ = client.DeleteComponent(
+			context.Background(),
+			token.AccessToken,
+			cfg.GoCloak.Realm,
+			createdID,
+		)
+	}
+	newComponent.ID = &createdID
+	return tearDown, newComponent
+}
+
+func Test_GetComponentsWithParams(t *testing.T) {
+	// t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+	tearDownComponent, component := CreateComponent(t, client)
+	defer tearDownComponent()
+
+	components, err := client.GetComponentsWithParams(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		gocloak.GetComponentsParams{
+			Name:         component.Name,
+			ProviderType: component.ProviderType,
+			ParentID:     component.ParentID,
+		},
+	)
+	require.NoError(t, err, "GetComponentsWithParams failed")
+	if len(components) != 1 {
+		require.NoError(t, fmt.Errorf("Expected 1 component, got %d", len(components)), "GetComponentsWithParams failed")
+	}
+}
+
+func Test_GetComponent(t *testing.T) {
+	// t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+	tearDownComponent, component := CreateComponent(t, client)
+	defer tearDownComponent()
+
+	_, err := client.GetComponent(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		*component.ID,
+	)
+	require.NoError(t, err, "GetComponent failed")
+}
+
+func Test_UpdateComponent(t *testing.T) {
+	// t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+	tearDownComponent, component := CreateComponent(t, client)
+	defer tearDownComponent()
+
+	component.Name = GetRandomNameP("UpdateComponent")
+
+	err := client.UpdateComponent(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		*component,
+	)
+	require.NoError(t, err, "UpdateComponent failed")
+
+	components, err := client.GetComponentsWithParams(
+		context.Background(),
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		gocloak.GetComponentsParams{
+			Name:         component.Name,
+			ProviderType: component.ProviderType,
+			ParentID:     component.ParentID,
+		},
+	)
+	require.NoError(t, err, "GetComponentWithParams after UpdateComponent failed")
+
+	if len(components) != 1 {
+		require.NoError(t, fmt.Errorf("Expected 1 component, got %d", len(components)), "UpdateComponent failed")
+	}
+	if *components[0].Name != *component.Name {
+		require.NoError(
+			t,
+			fmt.Errorf("Expected name after update '%s', got '%s'", *component.Name, *components[0].Name),
+			"UpdateComponent failed",
+		)
+	}
+}
diff --git a/model_test.go b/model_test.go
index fdb3c6d..ebda139 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),
@@ -247,7 +247,6 @@ func TestStringer(t *testing.T) {
 	"displayName": "someRealm"
 }`
 	assert.Equal(t, expectedStr, str)
-
 }
 
 type Stringable interface {
@@ -255,7 +254,6 @@ type Stringable interface {
 }
 
 func TestStringerOmitEmpty(t *testing.T) {
-
 	customs := []Stringable{
 		&gocloak.CertResponseKey{},
 		&gocloak.CertResponse{},
@@ -333,14 +331,13 @@ func TestStringerOmitEmpty(t *testing.T) {
 		&gocloak.GetResourcePoliciesParams{},
 		&gocloak.CredentialRepresentation{},
 		&gocloak.GetUsersParams{},
+		&gocloak.GetComponentsParams{},
 		&gocloak.GetClientsParams{},
 		&gocloak.RequestingPartyTokenOptions{},
 		&gocloak.RequestingPartyPermission{},
 	}
 
 	for _, custom := range customs {
-
 		assert.Equal(t, "{}", custom.(Stringable).String())
 	}
-
 }