Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for TeamCity Role Assignments for User Groups #86

Merged
merged 3 commits into from
May 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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