Skip to content

Commit

Permalink
Merge pull request #31 from michaljirman/feature/support-directory-ro…
Browse files Browse the repository at this point in the history
…le-activation

Add support for directory role activation
  • Loading branch information
manicminer authored Apr 21, 2021
2 parents 806cfc3 + fac8809 commit 364e746
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 39 deletions.
8 changes: 2 additions & 6 deletions msgraph/applications.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,7 @@ func (c *ApplicationsClient) AddOwners(ctx context.Context, application *Applica
checkOwnerAlreadyExists := func(resp *http.Response, o *odata.OData) bool {
if resp.StatusCode == http.StatusBadRequest {
if o.Error != nil {
if o.Error.Match(odata.ErrorAddedObjectReferencesAlreadyExist) {
return true
}
return o.Error.Match(odata.ErrorAddedObjectReferencesAlreadyExist)
}
}
return false
Expand Down Expand Up @@ -314,9 +312,7 @@ func (c *ApplicationsClient) RemoveOwners(ctx context.Context, applicationId str
checkOwnerGone := func(resp *http.Response, o *odata.OData) bool {
if resp.StatusCode == http.StatusBadRequest {
if o.Error != nil {
if o.Error.Match(odata.ErrorRemovedObjectReferencesDoNotExist) {
return true
}
return o.Error.Match(odata.ErrorRemovedObjectReferencesDoNotExist)
}
}
return false
Expand Down
79 changes: 73 additions & 6 deletions msgraph/directory_role_templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,31 @@ type DirectoryRoleTemplatesClientTest struct {
}

func TestDirectoryRoleTemplatesClient(t *testing.T) {
c := DirectoryRoleTemplatesClientTest{
rs := test.RandomString()
// set up directory role templates test client
dirRoleTemplatesClient := DirectoryRoleTemplatesClientTest{
connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2),
randomString: test.RandomString(),
randomString: rs,
}
c.client = msgraph.NewDirectoryRoleTemplatesClient(c.connection.AuthConfig.TenantID)
c.client.BaseClient.Authorizer = c.connection.Authorizer
dirRoleTemplatesClient.client = msgraph.NewDirectoryRoleTemplatesClient(dirRoleTemplatesClient.connection.AuthConfig.TenantID)
dirRoleTemplatesClient.client.BaseClient.Authorizer = dirRoleTemplatesClient.connection.Authorizer

directoryRoleTemplates := testDirectoryRoleTemplatesClient_List(t, c)
testDirectoryRoleTemplatesClient_Get(t, c, *(*directoryRoleTemplates)[0].ID)
// set up directory roles test client
dirRolesClient := DirectoryRolesClientTest{
connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2),
randomString: rs,
}
dirRolesClient.client = msgraph.NewDirectoryRolesClient(dirRolesClient.connection.AuthConfig.TenantID)
dirRolesClient.client.BaseClient.Authorizer = dirRolesClient.connection.Authorizer

// list all directory roles available in the tenant
directoryRoleTemplates := testDirectoryRoleTemplatesClient_List(t, dirRoleTemplatesClient)
testDirectoryRoleTemplatesClient_Get(t, dirRoleTemplatesClient, *(*directoryRoleTemplates)[0].ID)

// activate a directory role in the tenant using role template id if not already activated
// https://docs.microsoft.com/en-us/azure/active-directory/roles/permissions-reference
globalAdministratorRoleId := "62e90394-69f5-4237-9190-012177145e10"
testDirectoryRolesClient_Activate(t, dirRolesClient, globalAdministratorRoleId)
}

func testDirectoryRoleTemplatesClient_List(t *testing.T, c DirectoryRoleTemplatesClientTest) (directoryRoleTemplates *[]msgraph.DirectoryRoleTemplate) {
Expand All @@ -50,3 +66,54 @@ func testDirectoryRoleTemplatesClient_Get(t *testing.T, c DirectoryRoleTemplates
}
return
}

func testDirectoryRolesClient_Activate(t *testing.T, c DirectoryRolesClientTest, roleTemplateId string) (directoryRole *msgraph.DirectoryRole) {
// list all activated directory roles in the tenant
directoryRoles, _, err := c.client.List(c.connection.Context)
if err != nil {
t.Fatalf("DirectoryRolesClient.List(): %v", err)
}
if directoryRoles == nil {
t.Fatal("DirectoryRolesClient.List(): directoryRoles was nil")
}

// helper function to find activate directory role by role template id
// api does not support retrieving directory role by role template id; it does not support the OData Query Parameters
findDirRoleByRoleTemplateId := func(directoryRoles []msgraph.DirectoryRole, roleTemplatedId string) *msgraph.DirectoryRole {
for _, dirRole := range directoryRoles {
if dirRole.RoleTemplateId != nil && (*dirRole.RoleTemplateId) == roleTemplateId {
return &dirRole
}
}
return nil
}

// attempt to activate directory role if not already present in the directory
if dirRole := findDirRoleByRoleTemplateId(*directoryRoles, roleTemplateId); dirRole == nil {
t.Log("activating DirectoryRolesClientTest", roleTemplateId)
directoryRole, status, err := c.client.Activate(c.connection.Context, roleTemplateId)
if err != nil {
t.Fatalf("DirectoryRolesClient.Activate(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("DirectoryRolesClient.Activate(): invalid status: %d", status)
}
if directoryRole == nil {
t.Fatal("DirectoryRolesClient.Activate(): directoryRole was nil")
}
}

// attempt to activate directory role a second time to test the API error handling
t.Log("activating DirectoryRolesClientTest", roleTemplateId)
directoryRole, status, err := c.client.Activate(c.connection.Context, roleTemplateId)
if err != nil {
t.Fatalf("DirectoryRolesClient.Activate() [attempt 2]: %v", err)
}
if (status < 200 || status >= 300) && (status < 400 || status >= 500) {
t.Fatalf("DirectoryRolesClient.Activate() [attempt 2]: invalid status: %d", status)
}
if directoryRole == nil {
t.Fatal("DirectoryRolesClient.Activate() [attempt 2]: directoryRole was nil")
}
return
}
60 changes: 53 additions & 7 deletions msgraph/directory_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"io/ioutil"
"net/http"
"net/url"
"regexp"

"github.com/manicminer/hamilton/odata"
)
Expand All @@ -25,7 +24,7 @@ func NewDirectoryRolesClient(tenantId string) *DirectoryRolesClient {
}
}

// List returns a list of DirectoryRoles.
// List returns a list of DirectoryRoles activated in the tenant.
func (c *DirectoryRolesClient) List(ctx context.Context) (*[]DirectoryRole, int, error) {
resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{
ValidStatusCodes: []int{http.StatusOK},
Expand Down Expand Up @@ -121,18 +120,16 @@ func (c *DirectoryRolesClient) AddMembers(ctx context.Context, directoryRole *Di
return status, errors.New("cannot update directory role with nil Owners")
}
for _, member := range *directoryRole.Members {
// don't fail if an member already exists
// don't fail if a member already exists
checkMemberAlreadyExists := func(resp *http.Response, o *odata.OData) bool {
if resp.StatusCode == http.StatusBadRequest {
if o.Error != nil {
re := regexp.MustCompile("One or more added object references already exist")
if re.MatchString(o.Error.String()) {
return true
}
return o.Error.Match(odata.ErrorAddedObjectReferencesAlreadyExist)
}
}
return false
}

data := struct {
Member string `json:"@odata.id"`
}{
Expand Down Expand Up @@ -220,3 +217,52 @@ func (c *DirectoryRolesClient) GetMember(ctx context.Context, directoryRoleId, m
}
return &data.Id, status, nil
}

// Activate activates a directory role. To read a directory role or update its members, it must first be activated in the tenant using role template id.
// This method will attempt to detect whether a role is already activated in the tenant, but may fail in some circumstances.
func (c *DirectoryRolesClient) Activate(ctx context.Context, roleTemplateID string) (*DirectoryRole, int, error) {
var status int

// don't fail if a role is already activated
checkRoleAlreadyActivated := func(resp *http.Response, o *odata.OData) bool {
if resp.StatusCode == http.StatusBadRequest {
if o.Error != nil {
return o.Error.Match(odata.ErrorConflictingObjectPresentInDirectory)
}
}
return false
}

data := struct {
RoleTemplateID string `json:"roleTemplateId"`
}{
RoleTemplateID: roleTemplateID,
}
body, err := json.Marshal(data)
if err != nil {
return nil, status, fmt.Errorf("json.Marshal(): %v", err)
}

resp, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{
Body: body,
ValidStatusCodes: []int{http.StatusCreated},
ValidStatusFunc: checkRoleAlreadyActivated,
Uri: Uri{
Entity: "/directoryRoles",
HasTenantId: true,
},
})
if err != nil {
return nil, status, fmt.Errorf("DirectoryRolesClient.BaseClient.Post(): %v", err)
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, status, fmt.Errorf("ioutil.ReadAll(): %v", err)
}
var newDirRole DirectoryRole
if err := json.Unmarshal(respBody, &newDirRole); err != nil {
return nil, status, fmt.Errorf("json.Unmarshal(): %v", err)
}
return &newDirRole, status, nil
}
16 changes: 4 additions & 12 deletions msgraph/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,7 @@ func (c *GroupsClient) AddMembers(ctx context.Context, group *Group) (int, error
checkMemberAlreadyExists := func(resp *http.Response, o *odata.OData) bool {
if resp.StatusCode == http.StatusBadRequest {
if o.Error != nil {
if o.Error.Match(odata.ErrorAddedObjectReferencesAlreadyExist) {
return true
}
return o.Error.Match(odata.ErrorAddedObjectReferencesAlreadyExist)
}
}
return false
Expand Down Expand Up @@ -283,9 +281,7 @@ func (c *GroupsClient) RemoveMembers(ctx context.Context, id string, memberIds *
checkMemberGone := func(resp *http.Response, o *odata.OData) bool {
if resp.StatusCode == http.StatusBadRequest {
if o.Error != nil {
if o.Error.Match(odata.ErrorRemovedObjectReferencesDoNotExist) {
return true
}
return o.Error.Match(odata.ErrorRemovedObjectReferencesDoNotExist)
}
}
return false
Expand Down Expand Up @@ -383,9 +379,7 @@ func (c *GroupsClient) AddOwners(ctx context.Context, group *Group) (int, error)
checkOwnerAlreadyExists := func(resp *http.Response, o *odata.OData) bool {
if resp.StatusCode == http.StatusBadRequest {
if o.Error != nil {
if o.Error.Match(odata.ErrorAddedObjectReferencesAlreadyExist) {
return true
}
return o.Error.Match(odata.ErrorAddedObjectReferencesAlreadyExist)
}
}
return false
Expand Down Expand Up @@ -434,9 +428,7 @@ func (c *GroupsClient) RemoveOwners(ctx context.Context, id string, ownerIds *[]
checkOwnerGone := func(resp *http.Response, o *odata.OData) bool {
if resp.StatusCode == http.StatusBadRequest {
if o.Error != nil {
if o.Error.Match(odata.ErrorRemovedObjectReferencesDoNotExist) {
return true
}
return o.Error.Match(odata.ErrorRemovedObjectReferencesDoNotExist)
}
}
return false
Expand Down
8 changes: 2 additions & 6 deletions msgraph/serviceprincipals.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,7 @@ func (c *ServicePrincipalsClient) AddOwners(ctx context.Context, servicePrincipa
checkOwnerAlreadyExists := func(resp *http.Response, o *odata.OData) bool {
if resp.StatusCode == http.StatusBadRequest {
if o.Error != nil {
if o.Error.Match(odata.ErrorAddedObjectReferencesAlreadyExist) {
return true
}
return o.Error.Match(odata.ErrorAddedObjectReferencesAlreadyExist)
}
}
return false
Expand Down Expand Up @@ -284,9 +282,7 @@ func (c *ServicePrincipalsClient) RemoveOwners(ctx context.Context, servicePrinc
checkOwnerGone := func(resp *http.Response, o *odata.OData) bool {
if resp.StatusCode == http.StatusBadRequest {
if o.Error != nil {
if o.Error.Match(odata.ErrorRemovedObjectReferencesDoNotExist) {
return true
}
return o.Error.Match(odata.ErrorRemovedObjectReferencesDoNotExist)
}
}
return false
Expand Down
5 changes: 3 additions & 2 deletions odata/odata.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
)

const (
ErrorAddedObjectReferencesAlreadyExist = "One or more added object references already exist"
ErrorRemovedObjectReferencesDoNotExist = "One or more removed object references do not exist"
ErrorAddedObjectReferencesAlreadyExist = "One or more added object references already exist"
ErrorConflictingObjectPresentInDirectory = "A conflicting object with one or more of the specified property values is present in the directory"
ErrorRemovedObjectReferencesDoNotExist = "One or more removed object references do not exist"
)

// OData is used to unmarshall OData metadata from an API response.
Expand Down

0 comments on commit 364e746

Please sign in to comment.