Repository: Nerzal/gocloak
Pull request #164 (2020-05-04 14:27:13)

add groups count
Thanks for your contribution!
Hi, if there is an issue, that your PR adresses, please link it! 



Patch:
diff --git a/README.md b/README.md
index 79dc843..df0d7c8 100644
--- a/README.md
+++ b/README.md
@@ -156,6 +156,7 @@ type GoCloak interface {
 	GetUserGroups(accessToken string, realm string, userID string) ([]*UserGroup, error)
 	GetComponents(accessToken string, realm string) ([]*Component, error)
 	GetGroups(accessToken string, realm string, params GetGroupsParams) ([]*Group, error)
+	GetGroupsCount(token string, realm string) (int, error)
 	GetGroup(accessToken string, realm, groupID string) (*Group, error)
 	GetDefaultGroups(accessToken string, realm string) ([]*Group, error)
 	AddDefaultGroup(accessToken string, realm string, groupID string) error
diff --git a/client.go b/client.go
index 067711c..df1b025 100644
--- a/client.go
+++ b/client.go
@@ -30,11 +30,13 @@ const (
 	urlSeparator  string = "/"
 )
 
-var authAdminRealms = makeURL("auth", "admin", "realms")
-var authRealms = makeURL("auth", "realms")
-var tokenEndpoint = makeURL("protocol", "openid-connect", "token")
-var logoutEndpoint = makeURL("protocol", "openid-connect", "logout")
-var openIDConnect = makeURL("protocol", "openid-connect")
+var (
+	authAdminRealms = makeURL("auth", "admin", "realms")
+	authRealms      = makeURL("auth", "realms")
+	tokenEndpoint   = makeURL("protocol", "openid-connect", "token")
+	logoutEndpoint  = makeURL("protocol", "openid-connect", "logout")
+	openIDConnect   = makeURL("protocol", "openid-connect")
+)
 
 func makeURL(path ...string) string {
 	return strings.Join(path, urlSeparator)
@@ -146,7 +148,7 @@ func (client *gocloak) getAdminRealmURL(realm string, path ...string) string {
 }
 
 func (client *gocloak) GetServerInfo(accessToken string) (*ServerInfoRepesentation, error) {
-	var errMessage = "could not get server info"
+	errMessage := "could not get server info"
 	var result ServerInfoRepesentation
 
 	resp, err := client.getRequestWithBearerAuth(accessToken).
@@ -1015,6 +1017,22 @@ func (client *gocloak) GetGroups(token string, realm string, params GetGroupsPar
 	return result, nil
 }
 
+// GetGroupsCount gets the groups count in the realm
+func (client *gocloak) GetGroupsCount(token string, realm string) (int, error) {
+	const errMessage = "could not get groups count"
+
+	var result GroupsCount
+	resp, err := client.getRequestWithBearerAuth(token).
+		SetResult(&result).
+		Get(client.getAdminRealmURL(realm, "groups", "count"))
+
+	if err := checkForError(resp, err, errMessage); err != nil {
+		return -1, errors.Wrap(err, errMessage)
+	}
+
+	return result.Count, nil
+}
+
 // GetGroupMembers get a list of users of group with id in realm
 func (client *gocloak) GetGroupMembers(token string, realm string, groupID string, params GetGroupsParams) ([]*User, error) {
 	const errMessage = "could not get group members"
diff --git a/gocloak.go b/gocloak.go
index 6a46ecc..36fd24b 100644
--- a/gocloak.go
+++ b/gocloak.go
@@ -115,6 +115,8 @@ type GoCloak interface {
 	RemoveDefaultGroup(accessToken string, realm string, groupID string) error
 	// GetGroups gets all groups of the given realm
 	GetGroups(accessToken string, realm string, params GetGroupsParams) ([]*Group, error)
+	// GetGroupsCount gets groups count of the given realm
+	GetGroupsCount(token string, realm string) (int, error)
 	// GetGroup gets the given group
 	GetGroup(accessToken string, realm, groupID string) (*Group, error)
 	// GetGroupMembers get a list of users of group with id in realm
diff --git a/models.go b/models.go
index e29ed96..3302815 100644
--- a/models.go
+++ b/models.go
@@ -241,6 +241,11 @@ type Group struct {
 	RealmRoles  []string            `json:"realmRoles,omitempty"`
 }
 
+// GroupsCount represents the groups count response from keycloak
+type GroupsCount struct {
+	Count int `json:"count"`
+}
+
 // GetGroupsParams represents the optional parameters for getting groups
 type GetGroupsParams struct {
 	First               *int    `json:"first,string,omitempty"`
@@ -493,10 +498,10 @@ type GroupDefinition struct {
 
 // ResourceRepresentation is a representation of a Resource
 type ResourceRepresentation struct {
-	ID                 *string                      `json:"_id,omitempty"` //TODO: is marked "_optional" in template, input error or deliberate?
+	ID                 *string                      `json:"_id,omitempty"` // TODO: is marked "_optional" in template, input error or deliberate?
 	Attributes         map[string][]string          `json:"attributes,omitempty"`
 	DisplayName        *string                      `json:"displayName,omitempty"`
-	IconURI            *string                      `json:"icon_uri,omitempty"` //TODO: With "_" because that's how it's written down in the template
+	IconURI            *string                      `json:"icon_uri,omitempty"` // TODO: With "_" because that's how it's written down in the template
 	Name               *string                      `json:"name,omitempty"`
 	Owner              *ResourceOwnerRepresentation `json:"owner"`
 	OwnerManagedAccess *bool                        `json:"ownerManagedAccess"`


Test patch:
diff --git a/client_test.go b/client_test.go
index 999137f..76ef8a5 100644
--- a/client_test.go
+++ b/client_test.go
@@ -1451,7 +1451,6 @@ func TestGocloak_ExecuteActionsEmail_UpdatePassword(t *testing.T) {
 		token.AccessToken,
 		cfg.GoCloak.Realm,
 		params)
-
 	if err != nil {
 		if err.Error() == "500 Internal Server Error: Failed to send execute actions email" {
 			return
@@ -1934,6 +1933,19 @@ func TestGocloak_GetUserCount(t *testing.T) {
 	assert.NoError(t, err, "GetUserCount failed")
 }
 
+func TestGocloak_GetGroupsCount(t *testing.T) {
+	t.Parallel()
+	cfg := GetConfig(t)
+	client := NewClientWithDebug(t)
+	token := GetAdminToken(t, client)
+
+	count, err := client.GetGroupsCount(
+		token.AccessToken,
+		cfg.GoCloak.Realm)
+	t.Logf("Groups in Realm: %d", count)
+	assert.NoError(t, err, "GetGroupsCount failed")
+}
+
 func TestGocloak_AddUserToGroup(t *testing.T) {
 	t.Parallel()
 	cfg := GetConfig(t)