Repository: Nerzal/gocloak
Pull request #130 (2020-01-08 22:32:16)

Adding the ability to lookup users by client roles
Also changed the signature of GetUsersByRoleName to allow for query params to set `first` and `max`.


Patch:
diff --git a/README.md b/README.md
index 953d841..1c3766f 100644
--- a/README.md
+++ b/README.md
@@ -155,6 +155,7 @@ type GoCloak interface {
 	AddClientRoleComposite(token string, realm string, roleID string, roles []Role) error
 	DeleteClientRoleComposite(token string, realm string, roleID string, roles []Role) error
 	GetUsersByRoleName(token string, realm string, roleName string) ([]*User, error)
+	GetUsersByClientRoleName(token string, realm string, clientID string, roleName string, params GetUsersByRoleParams) ([]*User, error)
 	UserAttributeContains(attributes map[string][]string, attribute string, value string) bool
 	CreateClientProtocolMapper(token, realm, clientID string, mapper ProtocolMapperRepresentation) error
 	DeleteClientProtocolMapper(token, realm, clientID, mapperID string) error
diff --git a/client.go b/client.go
index bbd6c1b..3e425d3 100644
--- a/client.go
+++ b/client.go
@@ -1200,6 +1200,26 @@ func (client *gocloak) GetUsersByRoleName(token string, realm string, roleName s
 	return result, nil
 }
 
+// GetUsersByClientRoleName returns all users have a given client role
+func (client *gocloak) GetUsersByClientRoleName(token string, realm string, clientID string, roleName string, params GetUsersByRoleParams) ([]*User, error) {
+	var result []*User
+	queryParams, err := GetQueryParams(params)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := client.getRequestWithBearerAuth(token).
+		SetResult(&result).
+		SetQueryParams(queryParams).
+		Get(client.getAdminRealmURL(realm, "clients", clientID, "roles", roleName, "users"))
+
+	if err := checkForError(resp, err); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
 // SetPassword sets a new password for the user with the given id. Needs elevated privileges
 func (client *gocloak) SetPassword(token string, userID string, realm string, password string, temporary bool) error {
 	requestBody := SetPasswordRequest{Password: &password, Temporary: &temporary, Type: StringP("password")}
diff --git a/gocloak.go b/gocloak.go
index 98cd698..ae6847c 100644
--- a/gocloak.go
+++ b/gocloak.go
@@ -207,6 +207,8 @@ type GoCloak interface {
 	GetUserGroups(accessToken string, realm string, userID string) ([]*UserGroup, error)
 	// GetUsersByRoleName returns all users have a given role
 	GetUsersByRoleName(token string, realm string, roleName string) ([]*User, error)
+	// GetUsersByClientRoleName returns all users have a given client role
+	GetUsersByClientRoleName(token string, realm string, clientID string, roleName string, params GetUsersByRoleParams) ([]*User, error)
 	// SetPassword sets a new password for the user with the given id. Needs elevated privileges
 	SetPassword(token string, userID string, realm string, password string, temporary bool) error
 	// UpdateUser updates the given user
diff --git a/models.go b/models.go
index 5d5f8ba..fb05173 100644
--- a/models.go
+++ b/models.go
@@ -818,6 +818,12 @@ type GetPermissionParams struct {
 	Type     *string `json:"type,omitempty"`
 }
 
+// GetUsersByRoleParams represents the optional parameters for getting users by role
+type GetUsersByRoleParams struct {
+	First *int `json:"first,string,omitempty"`
+	Max   *int `json:"max,string,omitempty"`
+}
+
 // PermissionRepresentation is a representation of a Permission
 type PermissionRepresentation struct {
 	DecisionStrategy *DecisionStrategy `json:"decisionStrategy,omitempty"`


Test patch:
diff --git a/client_test.go b/client_test.go
index 2846262..0864bc8 100644
--- a/client_test.go
+++ b/client_test.go
@@ -2040,6 +2040,54 @@ func TestGocloak_GetUsersByRoleName(t *testing.T) {
 	)
 }
 
+func TestGocloak_GetUsersByClientRoleName(t *testing.T) {
+	t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+
+	tearDownUser, userID := CreateUser(t, client)
+	defer tearDownUser()
+
+	tearDownRole, roleName := CreateClientRole(t, client)
+	defer tearDownRole()
+
+	role, err := client.GetClientRole(
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		gocloakClientID,
+		roleName)
+	assert.NoError(t, err)
+	err = client.AddClientRoleToUser(
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		gocloakClientID,
+		userID,
+		[]Role{
+			*role,
+		})
+	assert.NoError(t, err)
+
+	users, err := client.GetUsersByClientRoleName(
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		gocloakClientID,
+		roleName,
+		GetUsersByRoleParams{})
+	assert.NoError(t, err)
+
+	assert.NotEqual(
+		t,
+		len(users),
+		0,
+	)
+	assert.Equal(
+		t,
+		userID,
+		*(users[0].ID),
+	)
+}
+
 func TestGocloak_GetUserSessions(t *testing.T) {
 	t.Parallel()
 	cfg := GetConfig(t)