Skip to content

Commit

Permalink
Merge pull request #86 from eli-guidewire/group-role-assignment
Browse files Browse the repository at this point in the history
Added support for TeamCity Role Assignments for User Groups
  • Loading branch information
cvbarros committed May 29, 2020
2 parents 12a7ad3 + 4969811 commit 710a4d5
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 13 deletions.
Binary file modified integration_tests/teamcity_data.tar.gz
Binary file not shown.
105 changes: 105 additions & 0 deletions teamcity/role_assignment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package teamcity

import (
"fmt"
"net/http"

"github.com/dghubble/sling"
)

// GroupRoleAssignment is the model for role assignment for groups in TeamCity
type GroupRoleAssignment struct {
GroupKey string //`json:"groupkey,omitempty" xml:"groupkey"`
RoleID string //`json:"roleid,omitempty" xml:"roleid"`
Scope string //`json:"scope,omitempty" xml:"scope"`
}

// RoleAssignmentReference represents a response of a request to assign role to a group or a user
type RoleAssignmentReference struct {
RoleID string `json:"roleId,omitempty" xml:"roleId"`
Scope string `json:"scope,omitempty" xml:"scope"`
Href string `json:"href,omitempty" xml:"href"`
}

type roleAssignmentsJSON struct {
Items []RoleAssignmentReference `json:"role"`
}

// NewGroupRoleAssignment returns an instance of a GroupRoleAssignment. A non-empty groupKey, roleId, and scope is required.
func NewGroupRoleAssignment(groupKey string, roleID string, scope string) (*GroupRoleAssignment, error) {
if groupKey == "" {
return nil, fmt.Errorf("GroupKey is required")
}

if roleID == "" {
return nil, fmt.Errorf("RoleId is required")
}

if scope == "" {
return nil, fmt.Errorf("scope is required. Use \"g\" at the global level for System Administrators, otherwise for other roles, use \"p:_Root\" for the root project, or \"p:<project_id>\" for other projects")
}

return &GroupRoleAssignment{
GroupKey: groupKey,
RoleID: roleID,
Scope: scope,
}, nil
}

// RoleAssignmentService has operations for handling role assignments for groups or users
type RoleAssignmentService struct {
groupSling *sling.Sling
httpClient *http.Client
groupHelper *restHelper
}

func newRoleAssignmentService(base *sling.Sling, httpClient *http.Client) *RoleAssignmentService {
groupSling := base.New().Path(fmt.Sprintf("userGroups/"))
return &RoleAssignmentService{
httpClient: httpClient,
groupSling: groupSling,
groupHelper: newRestHelperWithSling(httpClient, groupSling),
}
}

// AssignToGroup adds a role assignment to a group
func (s *RoleAssignmentService) AssignToGroup(assignment *GroupRoleAssignment) (*RoleAssignmentReference, error) {
var out RoleAssignmentReference

// URL for assigning role is /app/rest/userGroups/{groupLocator}/roles/{roleId}/{scope}
err := s.groupHelper.post(fmt.Sprintf("%s/roles/%s/%s", assignment.GroupKey, assignment.RoleID, assignment.Scope), nil, &out, "AssignToGroup role to group")
if err != nil {
return nil, err
}
return &out, nil
}

// GetForGroup get a specific role assignment for a group
func (s *RoleAssignmentService) GetForGroup(assignment *GroupRoleAssignment) (*RoleAssignmentReference, error) {
var out RoleAssignmentReference

// URL for getting a specific role assignments is /app/rest/userGroups/{groupLocator}/roles/{roleId}/{scope}
err := s.groupHelper.get(fmt.Sprintf("%s/roles/%s/%s", assignment.GroupKey, assignment.RoleID, assignment.Scope), &out, "GetForGroup role assignmens for group")
if err != nil {
return nil, err
}
return &out, nil
}

// GetAllForGroup gets all the role assignments for a group
func (s *RoleAssignmentService) GetAllForGroup(group *Group) ([]RoleAssignmentReference, error) {
var aux roleAssignmentsJSON

// URL for getting role assignments is /app/rest/userGroups/{groupLocator}/roles
err := s.groupHelper.get(fmt.Sprintf("%s/roles", group.Key), &aux, "GetForGroup role assignments for group")
if err != nil {
return nil, err
}
return aux.Items, nil
}

// UnassignFromGroup removes the role assignment from a group
func (s *RoleAssignmentService) UnassignFromGroup(assignment *GroupRoleAssignment) error {
// URL for unassigning role is /app/rest/userGroups/{groupLocator}/roles/{roleId}/{scope}
return s.groupHelper.delete(fmt.Sprintf("%s/roles/%s/%s", assignment.GroupKey, assignment.RoleID, assignment.Scope), "UnassignFromGroup role from group")
}
130 changes: 130 additions & 0 deletions teamcity/role_assignment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package teamcity_test

import (
"testing"

"github.com/cvbarros/go-teamcity/teamcity"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRoleAssignment_AssignGlobalSysAdminToGroup(t *testing.T) {
client := setup()

newGroup, _ := teamcity.NewGroup("TESTGROUPKEY", "Test Group Name", "Test Group Description")
newGroupRoleAssignment, _ := teamcity.NewGroupRoleAssignment("TESTGROUPKEY", "SYSTEM_ADMIN", "g") // "g" is for sys admins at the global level

actualGroup, err := client.Groups.Create(newGroup)
require.NoError(t, err)
require.NotNil(t, actualGroup)

createdGroupRoleAssignment, err := client.RoleAssignments.AssignToGroup(newGroupRoleAssignment)
require.NoError(t, err)
require.NotNil(t, createdGroupRoleAssignment)
assert.NotEmpty(t, createdGroupRoleAssignment.RoleID)
assert.NotEmpty(t, createdGroupRoleAssignment.Scope)
assert.NotEmpty(t, createdGroupRoleAssignment.Href)

groupRoleAssignments, err := client.RoleAssignments.GetAllForGroup(newGroup)
require.NoError(t, err)
assert.Equal(t, 1, len(groupRoleAssignments))
actualGroupRoleAssignmentReference := groupRoleAssignments[0]
assert.Equal(t, createdGroupRoleAssignment.RoleID, actualGroupRoleAssignmentReference.RoleID)
assert.Equal(t, createdGroupRoleAssignment.Scope, actualGroupRoleAssignmentReference.Scope)
assert.Equal(t, createdGroupRoleAssignment.Href, actualGroupRoleAssignmentReference.Href)

actualGroupRoleAssignmentReference2, err := client.RoleAssignments.GetForGroup(newGroupRoleAssignment)
require.NoError(t, err)
assert.Equal(t, createdGroupRoleAssignment.RoleID, actualGroupRoleAssignmentReference2.RoleID)
assert.Equal(t, createdGroupRoleAssignment.Scope, actualGroupRoleAssignmentReference2.Scope)
assert.Equal(t, createdGroupRoleAssignment.Href, actualGroupRoleAssignmentReference2.Href)

// Clean up group after test
cleanUpGroup(t, client, actualGroup.Key)
}

func TestRoleAssignment_AssignToGroup(t *testing.T) {
client := setup()

parent, _ := teamcity.NewProject("ParentProject", "Parent Project", "")
child, _ := teamcity.NewProject("ChildProject", "Child Project", "ParentProject")

_, err := client.Projects.Create(parent)
require.NoError(t, err)
created, err := client.Projects.Create(child)
require.NoError(t, err)

newGroup, _ := teamcity.NewGroup("TESTGROUPKEY", "Test Group Name", "Test Group Description")
newGroupRoleAssignment, _ := teamcity.NewGroupRoleAssignment("TESTGROUPKEY", "PROJECT_DEVELOPER", "p:"+created.ID)

actualGroup, err := client.Groups.Create(newGroup)
require.NoError(t, err)
require.NotNil(t, actualGroup)

createdGroupRoleAssignment, err := client.RoleAssignments.AssignToGroup(newGroupRoleAssignment)
require.NoError(t, err)
require.NotNil(t, createdGroupRoleAssignment)
assert.NotEmpty(t, createdGroupRoleAssignment.RoleID)
assert.NotEmpty(t, createdGroupRoleAssignment.Scope)
assert.NotEmpty(t, createdGroupRoleAssignment.Href)

groupRoleAssignments, err := client.RoleAssignments.GetAllForGroup(newGroup)
require.NoError(t, err)
assert.Equal(t, 1, len(groupRoleAssignments))
actualGroupRoleAssignmentReference := groupRoleAssignments[0]
assert.Equal(t, createdGroupRoleAssignment.RoleID, actualGroupRoleAssignmentReference.RoleID)
assert.Equal(t, createdGroupRoleAssignment.Scope, actualGroupRoleAssignmentReference.Scope)
assert.Equal(t, createdGroupRoleAssignment.Href, actualGroupRoleAssignmentReference.Href)

actualGroupRoleAssignmentReference2, err := client.RoleAssignments.GetForGroup(newGroupRoleAssignment)
require.NoError(t, err)
assert.Equal(t, createdGroupRoleAssignment.RoleID, actualGroupRoleAssignmentReference2.RoleID)
assert.Equal(t, createdGroupRoleAssignment.Scope, actualGroupRoleAssignmentReference2.Scope)
assert.Equal(t, createdGroupRoleAssignment.Href, actualGroupRoleAssignmentReference2.Href)

// Clean up group and projects after test
cleanUpGroup(t, client, actualGroup.Key)
cleanUpProject(t, client, "ParentProject")
}

func TestRoleAssignment_UnassignFromGroup(t *testing.T) {
client := setup()

parent, _ := teamcity.NewProject("ParentProject", "Parent Project", "")
child, _ := teamcity.NewProject("ChildProject", "Child Project", "ParentProject")

_, err := client.Projects.Create(parent)
require.NoError(t, err)
created, err := client.Projects.Create(child)
require.NoError(t, err)

newGroup, _ := teamcity.NewGroup("TESTGROUPKEY", "Test Group Name", "Test Group Description")
newGroupRoleAssignment, _ := teamcity.NewGroupRoleAssignment("TESTGROUPKEY", "PROJECT_DEVELOPER", "p:"+created.ID)

actualGroup, err := client.Groups.Create(newGroup)
require.NoError(t, err)
require.NotNil(t, actualGroup)

createdGroupRoleAssignment, err := client.RoleAssignments.AssignToGroup(newGroupRoleAssignment)
require.NoError(t, err)
require.NotNil(t, createdGroupRoleAssignment)
assert.NotEmpty(t, createdGroupRoleAssignment.RoleID)
assert.NotEmpty(t, createdGroupRoleAssignment.Scope)
assert.NotEmpty(t, createdGroupRoleAssignment.Href)

err = client.RoleAssignments.UnassignFromGroup(newGroupRoleAssignment)
require.NoError(t, err)

groupRoleAssignments, err := client.RoleAssignments.GetAllForGroup(newGroup)
require.NoError(t, err)
assert.Equal(t, 0, len(groupRoleAssignments))

// The Role has been unassigneded, so expect error, and message to contain 404 (NOT FOUND)
_, err = client.RoleAssignments.GetForGroup(newGroupRoleAssignment)
require.Error(t, err)
assert.Contains(t, err.Error(), "404")

// Clean up group and projects after test
cleanUpGroup(t, client, actualGroup.Key)
cleanUpProject(t, client, "ParentProject")
}
28 changes: 15 additions & 13 deletions teamcity/teamcity.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,12 @@ type Client struct {

commonBase *sling.Sling

Projects *ProjectService
BuildTypes *BuildTypeService
Server *ServerService
VcsRoots *VcsRootService
Groups *GroupService
Projects *ProjectService
BuildTypes *BuildTypeService
Server *ServerService
VcsRoots *VcsRootService
Groups *GroupService
RoleAssignments *RoleAssignmentService
}

func NewClient(auth Auth, httpClient *http.Client) (*Client, error) {
Expand Down Expand Up @@ -105,14 +106,15 @@ func newClientInstance(auth Auth, address string, httpClient *http.Client) (*Cli
}

return &Client{
address: address,
HTTPClient: httpClient,
commonBase: sharedClient,
Projects: newProjectService(sharedClient.New(), httpClient),
BuildTypes: newBuildTypeService(sharedClient.New(), httpClient),
Server: newServerService(sharedClient.New()),
VcsRoots: newVcsRootService(sharedClient.New(), httpClient),
Groups: newGroupService(sharedClient.New(), httpClient),
address: address,
HTTPClient: httpClient,
commonBase: sharedClient,
Projects: newProjectService(sharedClient.New(), httpClient),
BuildTypes: newBuildTypeService(sharedClient.New(), httpClient),
Server: newServerService(sharedClient.New()),
VcsRoots: newVcsRootService(sharedClient.New(), httpClient),
Groups: newGroupService(sharedClient.New(), httpClient),
RoleAssignments: newRoleAssignmentService(sharedClient.New(), httpClient),
}, nil
}

Expand Down

0 comments on commit 710a4d5

Please sign in to comment.