Repository: Nerzal/gocloak (go)

Issue:
add method get all available client roles to the user
Add method get available client-level roles that can be mapped to the user:
https://www.keycloak.org/docs-api/10.0/rest-api/index.html

> GET /{realm}/groups/{id}/role-mappings/clients/{client}/available


Pull request description:
add method GetAvailableClientRolesByUserID
Fix #170 

Patch:
diff --git a/README.md b/README.md
index df0d7c8..1a1bf5c 100644
--- a/README.md
+++ b/README.md
@@ -202,6 +202,7 @@ type GoCloak interface {
 	GetClientRolesByGroupID(token string, realm string, clientID string, groupID string) ([]*Role, error)
 	GetCompositeClientRolesByUserID(token string, realm string, clientID string, userID string) ([]*Role, error)
 	GetCompositeClientRolesByGroupID(token string, realm string, clientID string, groupID string) ([]*Role, error)
+    GetAvailableClientRolesByUserID(token string, realm string, clientID string, userID string) ([]*Role, error)
 
 	// *** Realm ***
 
diff --git a/client.go b/client.go
index df1b025..fb75963 100644
--- a/client.go
+++ b/client.go
@@ -1135,6 +1135,22 @@ func (client *gocloak) GetCompositeClientRolesByUserID(token string, realm strin
 	return result, nil
 }
 
+// GetAvailableClientRolesByUserID returns all available roles to the given user
+func (client *gocloak) GetAvailableClientRolesByUserID(token string, realm string, clientID string, userID string) ([]*Role, error) {
+	const errMessage = "could not get available client roles by user id"
+
+	var result []*Role
+	resp, err := client.getRequestWithBearerAuth(token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, "users", userID, "role-mappings", "clients", clientID, "available"))
+
+	if err = checkForError(resp, err, errMessage); err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
 // GetCompositeClientRolesByGroupID returns all client roles and composite roles assigned to the given group
 func (client *gocloak) GetCompositeClientRolesByGroupID(token string, realm string, clientID string, groupID string) ([]*Role, error) {
 	const errMessage = "could not get composite client roles by group id"
diff --git a/gocloak.go b/gocloak.go
index 36fd24b..72d1cee 100644
--- a/gocloak.go
+++ b/gocloak.go
@@ -196,6 +196,9 @@ type GoCloak interface {
 	GetCompositeClientRolesByUserID(token string, realm string, clientID string, userID string) ([]*Role, error)
 	// GetCompositeClientRolesByGroupID returns all client roles and composite roles assigned to the given group
 	GetCompositeClientRolesByGroupID(token string, realm string, clientID string, groupID string) ([]*Role, error)
+	// GetAvailableClientRolesByUserID returns all available roles to the given user
+	GetAvailableClientRolesByUserID(token string, realm string, clientID string, userID string) ([]*Role, error)
+
 	// GetClientRole get a role for the given client in a realm by role name
 	GetClientRole(token string, realm string, clientID string, roleName string) (*Role, error)
 	// AddClientRoleComposite adds roles as composite
diff --git a/models.go b/models.go
index dfce682..f3610b3 100644
--- a/models.go
+++ b/models.go
@@ -389,9 +389,8 @@ type ResourceServerRepresentation struct {
 
 // RoleDefinition represents a role in a RolePolicyRepresentation
 type RoleDefinition struct {
-	ID       *string `json:"id"`
-	Private  *bool   `json:"private,omitempty"`
-	Required *bool   `json:"required,omitempty"`
+	ID      *string `json:"id"`
+	Private *bool   `json:"private,omitempty"`
 }
 
 // PolicyEnforcementMode is an enum type for PolicyEnforcementMode of ResourceServerRepresentation


Test patch:
diff --git a/client_test.go b/client_test.go
index 76ef8a5..317723d 100644
--- a/client_test.go
+++ b/client_test.go
@@ -2560,6 +2560,80 @@ func TestGocloak_GetClientRolesByUserID(t *testing.T) {
 	assert.Fail(t, "The role has not been found in the assigned composite roles. Role: %+v", *role)
 }
 
+func TestGoCloak_GetAvailableClientRolesByUserID(t *testing.T) {
+	t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+
+	tearDownUser, userID := CreateUser(t, client)
+	defer tearDownUser()
+	tearDownRole, roleName1 := CreateClientRole(t, client)
+	defer tearDownRole()
+	tearDownRole2, roleName2 := CreateClientRole(t, client)
+	defer tearDownRole2()
+
+	role1, err := client.GetClientRole(
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		gocloakClientID,
+		roleName1)
+	assert.NoError(t, err)
+
+	role2, err := client.GetClientRole(
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		gocloakClientID,
+		roleName2)
+	assert.NoError(t, err)
+
+	err = client.AddClientRoleToUser(
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		gocloakClientID,
+		userID,
+		[]Role{
+			*role1,
+		})
+	assert.NoError(t, err)
+
+	roles, err := client.GetClientRolesByUserID(
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		gocloakClientID,
+		userID)
+	assert.NoError(t, err)
+	t.Logf("User roles: %+v", roles)
+	var found bool
+	for _, r := range roles {
+		if r.Name == nil {
+			continue
+		}
+		if *(r.Name) == *(role1.Name) {
+			found = true
+			break
+		}
+	}
+	assert.True(t, found, "The role1 has not been found in the assigned roles. Role: %+v", *role1)
+
+	roles, err = client.GetAvailableClientRolesByUserID(
+		token.AccessToken,
+		cfg.GoCloak.Realm,
+		gocloakClientID,
+		userID)
+	assert.NoError(t, err)
+	t.Logf("User roles: %+v", roles)
+	for _, r := range roles {
+		if r.Name == nil {
+			continue
+		}
+		if *(r.Name) == *(role2.Name) {
+			return
+		}
+	}
+	assert.Fail(t, "The role2 has not been found in the assigned composite roles. Role: %+v", *role2)
+}
+
 func TestGocloak_GetClientRolesByGroupID(t *testing.T) {
 	t.Parallel()
 	cfg := GetConfig(t)