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

Support role definitions and assignments via the unified role management endpoints #137

Merged
merged 3 commits into from
Jan 26, 2022
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
12 changes: 12 additions & 0 deletions internal/test/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ type Test struct {
MeClient *msgraph.MeClient
NamedLocationsClient *msgraph.NamedLocationsClient
ReportsClient *msgraph.ReportsClient
RoleAssignmentsClient *msgraph.RoleAssignmentsClient
RoleDefinitionsClient *msgraph.RoleDefinitionsClient
SchemaExtensionsClient *msgraph.SchemaExtensionsClient
ServicePrincipalsAppRoleAssignmentsClient *msgraph.AppRoleAssignmentsClient
ServicePrincipalsClient *msgraph.ServicePrincipalsClient
Expand Down Expand Up @@ -276,6 +278,16 @@ func NewTest(t *testing.T) (c *Test) {
c.ReportsClient.BaseClient.Endpoint = c.Connection.AuthConfig.Environment.MsGraph.Endpoint
c.ReportsClient.BaseClient.RetryableClient.RetryMax = retry

c.RoleAssignmentsClient = msgraph.NewRoleAssignmentsClient(c.Connection.AuthConfig.TenantID)
c.RoleAssignmentsClient.BaseClient.Authorizer = c.Connection.Authorizer
c.RoleAssignmentsClient.BaseClient.Endpoint = c.Connection.AuthConfig.Environment.MsGraph.Endpoint
c.RoleAssignmentsClient.BaseClient.RetryableClient.RetryMax = retry

c.RoleDefinitionsClient = msgraph.NewRoleDefinitionsClient(c.Connection.AuthConfig.TenantID)
c.RoleDefinitionsClient.BaseClient.Authorizer = c.Connection.Authorizer
c.RoleDefinitionsClient.BaseClient.Endpoint = c.Connection.AuthConfig.Environment.MsGraph.Endpoint
c.RoleDefinitionsClient.BaseClient.RetryableClient.RetryMax = retry

c.SchemaExtensionsClient = msgraph.NewSchemaExtensionsClient(c.Connection.AuthConfig.TenantID)
c.SchemaExtensionsClient.BaseClient.Authorizer = c.Connection.Authorizer
c.SchemaExtensionsClient.BaseClient.Endpoint = c.Connection.AuthConfig.Environment.MsGraph.Endpoint
Expand Down
28 changes: 28 additions & 0 deletions msgraph/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,34 @@ type TemporaryAccessPassAuthenticationMethod struct {
MethodUsabilityReason *MethodUsabilityReason `json:"methodUsabilityReason,omitempty"`
}

type UnifiedRoleAssignment struct {
DirectoryObject

AppScopeId *string `json:"appScopeId,omitempty"`
DirectoryScopeId *string `json:"directoryScopeId,omitempty"`
PrincipalId *string `json:"principalId,omitempty"`
RoleDefinitionId *string `json:"roleDefinitionId,omitempty"`
}

type UnifiedRoleDefinition struct {
DirectoryObject

Description *string `json:"description,omitempty"`
DisplayName *string `json:"displayName,omitempty"`
IsBuiltIn *bool `json:"isBuiltIn,omitempty"`
IsEnabled *bool `json:"isEnabled,omitempty"`
ResourceScopes *[]string `json:"resourceScopes,omitempty"`
RolePermissions *[]UnifiedRolePermission `json:"rolePermissions,omitempty"`
TemplateId *string `json:"templateId,omitempty"`
Version *string `json:"version,omitempty"`
}

type UnifiedRolePermission struct {
AllowedResourceActions *[]string `json:"allowedResourceActions,omitempty"`
Condition *string `json:"condition,omitempty"`
ExcludedResourceActions *[]string `json:"excludedResourceActions,omitempty"`
}

// User describes a User object.
type User struct {
DirectoryObject
Expand Down
133 changes: 133 additions & 0 deletions msgraph/role_assignments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package msgraph

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/manicminer/hamilton/odata"
)

// RoleAssignmentsClient performs operations on RoleAssignments.
type RoleAssignmentsClient struct {
BaseClient Client
}

// NewRoleAssignmentsClient returns a new RoleAssignmentsClient
func NewRoleAssignmentsClient(tenantId string) *RoleAssignmentsClient {
return &RoleAssignmentsClient{
BaseClient: NewClient(Version10, tenantId),
}
}

// List returns a list of RoleAssignments
func (c *RoleAssignmentsClient) List(ctx context.Context, query odata.Query) (*[]UnifiedRoleAssignment, int, error) {
resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{
OData: query,
ValidStatusCodes: []int{http.StatusOK},
Uri: Uri{
Entity: "/roleManagement/directory/roleAssignments",
HasTenantId: true,
},
})
if err != nil {
return nil, status, fmt.Errorf("RoleAssignmentsClient.BaseClient.Get(): %v", err)
}

defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, status, fmt.Errorf("io.ReadAll(): %v", err)
}

var data struct {
RoleAssignments []UnifiedRoleAssignment `json:"value"`
}
if err := json.Unmarshal(respBody, &data); err != nil {
return nil, status, fmt.Errorf("json.Unmarshal(): %v", err)
}

return &data.RoleAssignments, status, nil
}

// Get retrieves a UnifiedRoleAssignment
func (c *RoleAssignmentsClient) Get(ctx context.Context, id string, query odata.Query) (*UnifiedRoleAssignment, int, error) {
resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{
OData: query,
ValidStatusCodes: []int{http.StatusOK},
Uri: Uri{
Entity: fmt.Sprintf("/roleManagement/directory/roleAssignments/%s", id),
HasTenantId: true,
},
})
if err != nil {
return nil, status, fmt.Errorf("RoleAssignmentsClient.BaseClient.Get(): %v", err)
}

defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, status, fmt.Errorf("io.ReadAll(): %v", err)
}

var dirRole UnifiedRoleAssignment
if err := json.Unmarshal(respBody, &dirRole); err != nil {
return nil, status, fmt.Errorf("json.Unmarshal(): %v", err)
}

return &dirRole, status, nil
}

// Create creates a new UnifiedRoleAssignment.
func (c *RoleAssignmentsClient) Create(ctx context.Context, roleAssignment UnifiedRoleAssignment) (*UnifiedRoleAssignment, int, error) {
var status int

body, err := json.Marshal(roleAssignment)
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},
Uri: Uri{
Entity: "/roleManagement/directory/roleAssignments",
HasTenantId: true,
},
})
if err != nil {
return nil, status, fmt.Errorf("RoleAssignmentsClient.BaseClient.Post(): %v", err)
}

defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, status, fmt.Errorf("io.ReadAll(): %v", err)
}

var newRoleAssignment UnifiedRoleAssignment
if err := json.Unmarshal(respBody, &newRoleAssignment); err != nil {
return nil, status, fmt.Errorf("json.Unmarshal(): %v", err)
}

return &newRoleAssignment, status, nil
}

// Delete removes a UnifiedRoleAssignment.
func (c *RoleAssignmentsClient) Delete(ctx context.Context, id string) (int, error) {
_, status, _, err := c.BaseClient.Delete(ctx, DeleteHttpRequestInput{
ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc,
ValidStatusCodes: []int{http.StatusNoContent},
Uri: Uri{
Entity: fmt.Sprintf("/roleManagement/directory/roleAssignments/%s", id),
HasTenantId: true,
},
})
if err != nil {
return status, fmt.Errorf("RoleAssignments.BaseClient.Get(): %v", err)
}

return status, nil
}
105 changes: 105 additions & 0 deletions msgraph/role_assignments_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package msgraph_test

import (
"fmt"
"testing"

"github.com/manicminer/hamilton/internal/test"
"github.com/manicminer/hamilton/internal/utils"
"github.com/manicminer/hamilton/msgraph"
"github.com/manicminer/hamilton/odata"
)

func TestRoleAssignmentsClient(t *testing.T) {
c := test.NewTest(t)
defer c.CancelFunc()

roleDefinition := testRoleDefinitionsClient_Create(t, c, msgraph.UnifiedRoleDefinition{
Description: utils.StringPtr("testing custom role assignment"),
DisplayName: utils.StringPtr("Test Assignor"),
IsEnabled: utils.BoolPtr(true),
RolePermissions: &[]msgraph.UnifiedRolePermission{
{
AllowedResourceActions: &[]string{
"microsoft.directory/groups/allProperties/read",
},
},
},
Version: utils.StringPtr("1.5"),
})

user := testUsersClient_Create(t, c, msgraph.User{
AccountEnabled: utils.BoolPtr(true),
DisplayName: utils.StringPtr("test-user"),
MailNickname: utils.StringPtr(fmt.Sprintf("test-user-%s", c.RandomString)),
UserPrincipalName: utils.StringPtr(fmt.Sprintf("test-user-%s@%s", c.RandomString, c.Connection.DomainName)),
PasswordProfile: &msgraph.UserPasswordProfile{
Password: utils.StringPtr(fmt.Sprintf("IrPa55w0rd%s", c.RandomString)),
},
})

roleAssignment := testRoleAssignmentsClient_Create(t, c, msgraph.UnifiedRoleAssignment{
DirectoryScopeId: utils.StringPtr("/"),
PrincipalId: user.ID,
RoleDefinitionId: roleDefinition.ID,
})

testRoleAssignmentsClient_Get(t, c, *roleAssignment.ID)
testRoleAssignmentsClient_List(t, c, odata.Query{Filter: fmt.Sprintf("roleDefinitionId eq '%s'", *roleDefinition.ID)})
testRoleAssignmentsClient_Delete(t, c, *roleAssignment.ID)
testRoleDefinitionsClient_Delete(t, c, *roleDefinition.ID)
testUsersClient_Delete(t, c, *user.ID)
testUsersClient_DeletePermanently(t, c, *user.ID)
}

func testRoleAssignmentsClient_Create(t *testing.T, c *test.Test, r msgraph.UnifiedRoleAssignment) (roleAssignment *msgraph.UnifiedRoleAssignment) {
roleAssignment, status, err := c.RoleAssignmentsClient.Create(c.Context, r)
if err != nil {
t.Fatalf("RoleAssignmentsClient.Create(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("RoleAssignmentsClient.Create(): invalid status: %d", status)
}
if roleAssignment == nil {
t.Fatal("RoleAssignmentsClient.Create(): roleAssignment was nil")
}
if roleAssignment.ID == nil {
t.Fatal("RoleAssignmentsClient.Create(): roleAssignment.ID was nil")
}
return
}

func testRoleAssignmentsClient_List(t *testing.T, c *test.Test, query odata.Query) (roleAssignments *[]msgraph.UnifiedRoleAssignment) {
roleAssignments, _, err := c.RoleAssignmentsClient.List(c.Context, query)
if err != nil {
t.Fatalf("RoleAssignmentsClient.List(): %v", err)
}
if roleAssignments == nil {
t.Fatal("RoleAssignmentsClient.List(): roleAssignments was nil")
}
return
}

func testRoleAssignmentsClient_Get(t *testing.T, c *test.Test, id string) (roleAssignment *msgraph.UnifiedRoleAssignment) {
roleAssignment, status, err := c.RoleAssignmentsClient.Get(c.Context, id, odata.Query{})
if err != nil {
t.Fatalf("RoleAssignmentsClient.Get(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("RoleAssignmentsClient.Get(): invalid status: %d", status)
}
if roleAssignment == nil {
t.Fatal("RoleAssignmentsClient.Get(): roleAssignment was nil")
}
return
}

func testRoleAssignmentsClient_Delete(t *testing.T, c *test.Test, id string) {
status, err := c.RoleAssignmentsClient.Delete(c.Context, id)
if err != nil {
t.Fatalf("RoleAssignmentsClient.Delete(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("RoleAssignmentsClient.Delete(): invalid status: %d", status)
}
}
Loading