From 9ff8609ca5a5fe61fadea6404d32d5f4af028702 Mon Sep 17 00:00:00 2001 From: Michal Jirman Date: Mon, 3 May 2021 12:24:39 +0100 Subject: [PATCH 1/2] add support for app role assignments for users, groups and service principles --- msgraph/app_role_assignments.go | 52 ++++-- msgraph/app_role_assignments_test.go | 255 +++++++++++++++++++++++---- 2 files changed, 261 insertions(+), 46 deletions(-) diff --git a/msgraph/app_role_assignments.go b/msgraph/app_role_assignments.go index da34cea0..96569d30 100644 --- a/msgraph/app_role_assignments.go +++ b/msgraph/app_role_assignments.go @@ -8,24 +8,50 @@ import ( "net/http" ) +type appRoleAssignmentsResourceType string + +const ( + groupsAppRoleAssignmentsResource appRoleAssignmentsResourceType = "groups" + usersAppRoleAssignmentsResource appRoleAssignmentsResourceType = "users" + servicePrincipalsAppRoleAssignmentsResource appRoleAssignmentsResourceType = "servicePrincipals" +) + // AppRoleAssignmentsClient performs operations on AppRoleAssignments. type AppRoleAssignmentsClient struct { - BaseClient Client + BaseClient Client + resourceType appRoleAssignmentsResourceType +} + +// NewUsersAppRoleAssignmentsClient returns a new AppRoleAssignmentsClient for users assignments +func NewUsersAppRoleAssignmentsClient(tenantId string) *AppRoleAssignmentsClient { + return &AppRoleAssignmentsClient{ + BaseClient: NewClient(Version10, tenantId), + resourceType: usersAppRoleAssignmentsResource, + } +} + +// NewGroupsAppRoleAssignmentsClient returns a new AppRoleAssignmentsClient for groups assignments +func NewGroupsAppRoleAssignmentsClient(tenantId string) *AppRoleAssignmentsClient { + return &AppRoleAssignmentsClient{ + BaseClient: NewClient(Version10, tenantId), + resourceType: groupsAppRoleAssignmentsResource, + } } -// NewAppRoleAssignmentsClient returns a new AppRoleAssignmentsClient -func NewAppRoleAssignmentsClient(tenantId string) *AppRoleAssignmentsClient { +// NewServicePrincipalsAppRoleAssignmentsClient returns a new AppRoleAssignmentsClient for service principal assignments +func NewServicePrincipalsAppRoleAssignmentsClient(tenantId string) *AppRoleAssignmentsClient { return &AppRoleAssignmentsClient{ - BaseClient: NewClient(Version10, tenantId), + BaseClient: NewClient(Version10, tenantId), + resourceType: servicePrincipalsAppRoleAssignmentsResource, } } // List returns a list of app role assignments. -func (c *AppRoleAssignmentsClient) List(ctx context.Context, groupId string) (*[]AppRoleAssignment, int, error) { +func (c *AppRoleAssignmentsClient) List(ctx context.Context, id string) (*[]AppRoleAssignment, int, error) { resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ ValidStatusCodes: []int{http.StatusOK}, Uri: Uri{ - Entity: fmt.Sprintf("/groups/%s/appRoleAssignments", groupId), + Entity: fmt.Sprintf("/%s/%s/appRoleAssignments", c.resourceType, id), HasTenantId: true, }, }) @@ -47,11 +73,11 @@ func (c *AppRoleAssignmentsClient) List(ctx context.Context, groupId string) (*[ } // Remove removes a app role assignment. -func (c *AppRoleAssignmentsClient) Remove(ctx context.Context, groupId, appRoleAssignmentId string) (int, error) { +func (c *AppRoleAssignmentsClient) Remove(ctx context.Context, id, appRoleAssignmentId string) (int, error) { _, status, _, err := c.BaseClient.Delete(ctx, DeleteHttpRequestInput{ ValidStatusCodes: []int{http.StatusNoContent}, Uri: Uri{ - Entity: fmt.Sprintf("/groups/%s/appRoleAssignments/%s", groupId, appRoleAssignmentId), + Entity: fmt.Sprintf("/%s/%s/appRoleAssignments/%s", c.resourceType, id, appRoleAssignmentId), HasTenantId: true, }, }) @@ -61,16 +87,16 @@ func (c *AppRoleAssignmentsClient) Remove(ctx context.Context, groupId, appRoleA return status, nil } -// Assign assigns an app role to a group. -func (c *AppRoleAssignmentsClient) Assign(ctx context.Context, groupId, resourceId, appRoleId string) (*AppRoleAssignment, int, error) { +// Assign assigns an app role to a user, group or service principal depending on client resource type. +func (c *AppRoleAssignmentsClient) Assign(ctx context.Context, clientServicePrincipalId, resourceServicePrincipalId, appRoleId string) (*AppRoleAssignment, int, error) { var status int data := struct { PrincipalId string `json:"principalId"` ResourceId string `json:"resourceId"` AppRoleId string `json:"appRoleId"` }{ - PrincipalId: groupId, - ResourceId: resourceId, + PrincipalId: clientServicePrincipalId, + ResourceId: resourceServicePrincipalId, AppRoleId: appRoleId, } @@ -82,7 +108,7 @@ func (c *AppRoleAssignmentsClient) Assign(ctx context.Context, groupId, resource Body: body, ValidStatusCodes: []int{http.StatusCreated}, Uri: Uri{ - Entity: fmt.Sprintf("/groups/%s/appRoleAssignments", groupId), + Entity: fmt.Sprintf("/%s/%s/appRoleAssignments", c.resourceType, clientServicePrincipalId), HasTenantId: true, }, }) diff --git a/msgraph/app_role_assignments_test.go b/msgraph/app_role_assignments_test.go index bd353b93..8883ea40 100644 --- a/msgraph/app_role_assignments_test.go +++ b/msgraph/app_role_assignments_test.go @@ -18,15 +18,15 @@ type AppRoleAssignmentsClientTest struct { randomString string } -func TestAppRoleAssignmentsClient(t *testing.T) { +func TestGroupsAppRoleAssignmentsClient(t *testing.T) { rs := test.RandomString() - // setup service principle test client - servicePrinciplesClient := ServicePrincipalsClientTest{ + // setup service principal test client + servicePrincipalsClient := ServicePrincipalsClientTest{ connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), randomString: rs, } - servicePrinciplesClient.client = msgraph.NewServicePrincipalsClient(servicePrinciplesClient.connection.AuthConfig.TenantID) - servicePrinciplesClient.client.BaseClient.Authorizer = servicePrinciplesClient.connection.Authorizer + servicePrincipalsClient.client = msgraph.NewServicePrincipalsClient(servicePrincipalsClient.connection.AuthConfig.TenantID) + servicePrincipalsClient.client.BaseClient.Authorizer = servicePrincipalsClient.connection.Authorizer // setup groups test client groupsClient := GroupsClientTest{ @@ -36,12 +36,12 @@ func TestAppRoleAssignmentsClient(t *testing.T) { groupsClient.client = msgraph.NewGroupsClient(groupsClient.connection.AuthConfig.TenantID) groupsClient.client.BaseClient.Authorizer = groupsClient.connection.Authorizer - // setup app role assignments test client + // setup resourceApp role assignments test client appRoleAssignClient := AppRoleAssignmentsClientTest{ connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), randomString: rs, } - appRoleAssignClient.client = msgraph.NewAppRoleAssignmentsClient(appRoleAssignClient.connection.AuthConfig.TenantID) + appRoleAssignClient.client = msgraph.NewGroupsAppRoleAssignmentsClient(appRoleAssignClient.connection.AuthConfig.TenantID) appRoleAssignClient.client.BaseClient.Authorizer = appRoleAssignClient.connection.Authorizer // setup applications test client @@ -61,18 +61,18 @@ func TestAppRoleAssignmentsClient(t *testing.T) { } group := testGroupsClient_Create(t, groupsClient, newGroup) - // pre-generate uuid for a test app role - testAppRoleId, _ := uuid.GenerateUUID() - // create a new test application with a test app role - app := testApplicationsClient_Create(t, appClient, msgraph.Application{ + // pre-generate uuid for a test resourceApp role + testResourceAppRoleId, _ := uuid.GenerateUUID() + // create a new test application with a test resourceApp role + resourceApp := testApplicationsClient_Create(t, appClient, msgraph.Application{ DisplayName: utils.StringPtr(fmt.Sprintf("test-application-%s", appClient.randomString)), AppRoles: &[]msgraph.AppRole{ { - ID: utils.StringPtr(testAppRoleId), - DisplayName: utils.StringPtr(fmt.Sprintf("test-app-role-%s", appClient.randomString)), + ID: utils.StringPtr(testResourceAppRoleId), + DisplayName: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-%s", appClient.randomString)), IsEnabled: utils.BoolPtr(true), - Description: utils.StringPtr(fmt.Sprintf("test-app-role-description-%s", appClient.randomString)), - Value: utils.StringPtr(fmt.Sprintf("test-app-role-value-%s", appClient.randomString)), + Description: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-description-%s", appClient.randomString)), + Value: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-value-%s", appClient.randomString)), AllowedMemberTypes: &[]msgraph.AppRoleAllowedMemberType{ msgraph.AppRoleAllowedMemberTypeUser, msgraph.AppRoleAllowedMemberTypeApplication, @@ -81,34 +81,223 @@ func TestAppRoleAssignmentsClient(t *testing.T) { }, }) - // create a new test service principle - sp := testServicePrincipalsClient_Create(t, servicePrinciplesClient, msgraph.ServicePrincipal{ + // create a new test resource (API) service principal which has defined the resourceApp role (the application permission) + resourceServicePrincipal := testServicePrincipalsClient_Create(t, servicePrincipalsClient, msgraph.ServicePrincipal{ AccountEnabled: utils.BoolPtr(true), - AppId: app.AppId, - // display name needs to match app's display name - DisplayName: app.DisplayName, + AppId: resourceApp.AppId, + // display name needs to match resourceApp's display name + DisplayName: resourceApp.DisplayName, }) - // assign app role to the test group - appRoleAssignment := testAppRoleAssignmentsClient_Assign(t, appRoleAssignClient, *group.ID, *sp.ID, testAppRoleId) + // assign resourceApp role to the test group + appRoleAssignment := testAppRoleAssignmentsClient_Assign(t, appRoleAssignClient, *group.ID, *resourceServicePrincipal.ID, testResourceAppRoleId) - // list app role assignments for a test group + // list resourceApp role assignments for a test group appRoleAssignments := testAppRoleAssignmentsClient_List(t, appRoleAssignClient, *group.ID) if len(*appRoleAssignments) == 0 { - t.Fatal("expected at least one app role assignment assigned to the test group") + t.Fatal("expected at least one resourceApp role assignment assigned to the test group") } - // removes app role assignment previously set to the test group + // removes resourceApp role assignment previously set to the test group testAppRoleAssignmentsClient_Remove(t, appRoleAssignClient, *group.ID, *appRoleAssignment.Id) // remove all test resources to clean up testGroupsClient_Delete(t, groupsClient, *group.ID) - testServicePrincipalsClient_Delete(t, servicePrinciplesClient, *sp.ID) - testApplicationsClient_Delete(t, appClient, *app.ID) + testServicePrincipalsClient_Delete(t, servicePrincipalsClient, *resourceServicePrincipal.ID) + testApplicationsClient_Delete(t, appClient, *resourceApp.ID) } -func testAppRoleAssignmentsClient_List(t *testing.T, c AppRoleAssignmentsClientTest, groupId string) (appRoleAssignments *[]msgraph.AppRoleAssignment) { - appRoleAssignments, _, err := c.client.List(c.connection.Context, groupId) +func TestUsersAppRoleAssignmentsClient(t *testing.T) { + rs := test.RandomString() + // setup service principal test client + servicePrincipalsClient := ServicePrincipalsClientTest{ + connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), + randomString: rs, + } + servicePrincipalsClient.client = msgraph.NewServicePrincipalsClient(servicePrincipalsClient.connection.AuthConfig.TenantID) + servicePrincipalsClient.client.BaseClient.Authorizer = servicePrincipalsClient.connection.Authorizer + + // setup users test client + usersClient := UsersClientTest{ + connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), + randomString: rs, + } + usersClient.client = msgraph.NewUsersClient(usersClient.connection.AuthConfig.TenantID) + usersClient.client.BaseClient.Authorizer = usersClient.connection.Authorizer + + // setup resourceApp role assignments test client + appRoleAssignClient := AppRoleAssignmentsClientTest{ + connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), + randomString: rs, + } + appRoleAssignClient.client = msgraph.NewUsersAppRoleAssignmentsClient(appRoleAssignClient.connection.AuthConfig.TenantID) + appRoleAssignClient.client.BaseClient.Authorizer = appRoleAssignClient.connection.Authorizer + + // setup applications test client + appClient := ApplicationsClientTest{ + connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), + randomString: rs, + } + appClient.client = msgraph.NewApplicationsClient(appClient.connection.AuthConfig.TenantID) + appClient.client.BaseClient.Authorizer = appClient.connection.Authorizer + + // create a new test user + newUser := msgraph.User{ + AccountEnabled: utils.BoolPtr(true), + DisplayName: utils.StringPtr("Test User"), + MailNickname: utils.StringPtr(fmt.Sprintf("test-user-%s", usersClient.randomString)), + UserPrincipalName: utils.StringPtr(fmt.Sprintf("test-user-%s@%s", usersClient.randomString, usersClient.connection.DomainName)), + PasswordProfile: &msgraph.UserPasswordProfile{ + Password: utils.StringPtr(fmt.Sprintf("IrPa55w0rd%s", usersClient.randomString)), + }, + } + user := testUsersClient_Create(t, usersClient, newUser) + + // pre-generate uuid for a test resourceApp role + testResourceAppRoleId, _ := uuid.GenerateUUID() + // create a new test application with a test resourceApp role + resourceApp := testApplicationsClient_Create(t, appClient, msgraph.Application{ + DisplayName: utils.StringPtr(fmt.Sprintf("test-application-%s", appClient.randomString)), + AppRoles: &[]msgraph.AppRole{ + { + ID: utils.StringPtr(testResourceAppRoleId), + DisplayName: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-%s", appClient.randomString)), + IsEnabled: utils.BoolPtr(true), + Description: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-description-%s", appClient.randomString)), + Value: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-value-%s", appClient.randomString)), + AllowedMemberTypes: &[]msgraph.AppRoleAllowedMemberType{ + msgraph.AppRoleAllowedMemberTypeUser, + msgraph.AppRoleAllowedMemberTypeApplication, + }, + }, + }, + }) + + // create a new test resource (API) service principal which has defined the resourceApp role (the application permission) + resourceServicePrincipal := testServicePrincipalsClient_Create(t, servicePrincipalsClient, msgraph.ServicePrincipal{ + AccountEnabled: utils.BoolPtr(true), + AppId: resourceApp.AppId, + // display name needs to match resourceApp's display name + DisplayName: resourceApp.DisplayName, + }) + + // assign resourceApp role to the test user + appRoleAssignment := testAppRoleAssignmentsClient_Assign(t, appRoleAssignClient, *user.ID, *resourceServicePrincipal.ID, testResourceAppRoleId) + + // list resourceApp role assignments for a test user + appRoleAssignments := testAppRoleAssignmentsClient_List(t, appRoleAssignClient, *user.ID) + if len(*appRoleAssignments) == 0 { + t.Fatal("expected at least one resourceApp role assignment assigned to the test user") + } + + // removes resourceApp role assignment previously set to the test user + testAppRoleAssignmentsClient_Remove(t, appRoleAssignClient, *user.ID, *appRoleAssignment.Id) + + // remove all test resources to clean up + testUsersClient_Delete(t, usersClient, *user.ID) + testServicePrincipalsClient_Delete(t, servicePrincipalsClient, *resourceServicePrincipal.ID) + testApplicationsClient_Delete(t, appClient, *resourceApp.ID) +} + +func TestServicePrincipalsAppRoleAssignmentsClient(t *testing.T) { + rs := test.RandomString() + // setup service principal test client + servicePrincipalsClient := ServicePrincipalsClientTest{ + connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), + randomString: rs, + } + servicePrincipalsClient.client = msgraph.NewServicePrincipalsClient(servicePrincipalsClient.connection.AuthConfig.TenantID) + servicePrincipalsClient.client.BaseClient.Authorizer = servicePrincipalsClient.connection.Authorizer + + // setup resourceApp role assignments test client + appRoleAssignClient := AppRoleAssignmentsClientTest{ + connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), + randomString: rs, + } + appRoleAssignClient.client = msgraph.NewServicePrincipalsAppRoleAssignmentsClient(appRoleAssignClient.connection.AuthConfig.TenantID) + appRoleAssignClient.client.BaseClient.Authorizer = appRoleAssignClient.connection.Authorizer + + // setup applications test client + appClient := ApplicationsClientTest{ + connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), + randomString: rs, + } + appClient.client = msgraph.NewApplicationsClient(appClient.connection.AuthConfig.TenantID) + appClient.client.BaseClient.Authorizer = appClient.connection.Authorizer + + // pre-generate uuid for a test resourceApp role + testResourceAppRoleId, _ := uuid.GenerateUUID() + // create a new test application with a test resourceApp role + resourceApp := testApplicationsClient_Create(t, appClient, msgraph.Application{ + DisplayName: utils.StringPtr(fmt.Sprintf("test-application-%s", appClient.randomString)), + AppRoles: &[]msgraph.AppRole{ + { + ID: utils.StringPtr(testResourceAppRoleId), + DisplayName: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-%s", appClient.randomString)), + IsEnabled: utils.BoolPtr(true), + Description: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-description-%s", appClient.randomString)), + Value: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-value-%s", appClient.randomString)), + AllowedMemberTypes: &[]msgraph.AppRoleAllowedMemberType{ + msgraph.AppRoleAllowedMemberTypeUser, + msgraph.AppRoleAllowedMemberTypeApplication, + }, + }, + }, + }) + + // create a new test resource (API) service principal which has defined the resourceApp role (the application permission) + resourceServicePrincipal := testServicePrincipalsClient_Create(t, servicePrincipalsClient, msgraph.ServicePrincipal{ + AccountEnabled: utils.BoolPtr(true), + AppId: resourceApp.AppId, + // display name needs to match resourceApp's display name + DisplayName: resourceApp.DisplayName, + }) + + // create a new test 2 application + clientApp := testApplicationsClient_Create(t, appClient, msgraph.Application{ + DisplayName: utils.StringPtr(fmt.Sprintf("test-2-application-%s", appClient.randomString)), + AppRoles: &[]msgraph.AppRole{ + { + ID: utils.StringPtr(testResourceAppRoleId), + DisplayName: utils.StringPtr(fmt.Sprintf("test-2-resourceApp-role-%s", appClient.randomString)), + IsEnabled: utils.BoolPtr(true), + Description: utils.StringPtr(fmt.Sprintf("test-2-resourceApp-role-description-%s", appClient.randomString)), + Value: utils.StringPtr(fmt.Sprintf("test-2-resourceApp-role-value-%s", appClient.randomString)), + AllowedMemberTypes: &[]msgraph.AppRoleAllowedMemberType{ + msgraph.AppRoleAllowedMemberTypeUser, + msgraph.AppRoleAllowedMemberTypeApplication, + }, + }, + }, + }) + // create a new test client service principal + clientServicePrincipal := testServicePrincipalsClient_Create(t, servicePrincipalsClient, msgraph.ServicePrincipal{ + AccountEnabled: utils.BoolPtr(true), + AppId: clientApp.AppId, + // display name needs to match clientApp's display name + DisplayName: clientApp.DisplayName, + }) + + // assign resourceApp role to the test client service principal + appRoleAssignment := testAppRoleAssignmentsClient_Assign(t, appRoleAssignClient, *clientServicePrincipal.ID, *resourceServicePrincipal.ID, testResourceAppRoleId) + + // list resourceApp role assignments for a test client service principal + appRoleAssignments := testAppRoleAssignmentsClient_List(t, appRoleAssignClient, *clientServicePrincipal.ID) + if len(*appRoleAssignments) == 0 { + t.Fatal("expected at least one resourceApp role assignment assigned to the test client service principal") + } + + // removes resourceApp role assignment previously set to the test client service principal + testAppRoleAssignmentsClient_Remove(t, appRoleAssignClient, *clientServicePrincipal.ID, *appRoleAssignment.Id) + + // remove all test resources to clean up + testServicePrincipalsClient_Delete(t, servicePrincipalsClient, *clientServicePrincipal.ID) + testServicePrincipalsClient_Delete(t, servicePrincipalsClient, *resourceServicePrincipal.ID) + testApplicationsClient_Delete(t, appClient, *resourceApp.ID) +} + +func testAppRoleAssignmentsClient_List(t *testing.T, c AppRoleAssignmentsClientTest, id string) (appRoleAssignments *[]msgraph.AppRoleAssignment) { + appRoleAssignments, _, err := c.client.List(c.connection.Context, id) if err != nil { t.Fatalf("AppRoleAssignmentsClient.List(): %v", err) } @@ -118,8 +307,8 @@ func testAppRoleAssignmentsClient_List(t *testing.T, c AppRoleAssignmentsClientT return } -func testAppRoleAssignmentsClient_Remove(t *testing.T, c AppRoleAssignmentsClientTest, groupId, appRoleAssignmentId string) { - status, err := c.client.Remove(c.connection.Context, groupId, appRoleAssignmentId) +func testAppRoleAssignmentsClient_Remove(t *testing.T, c AppRoleAssignmentsClientTest, id, appRoleAssignmentId string) { + status, err := c.client.Remove(c.connection.Context, id, appRoleAssignmentId) if err != nil { t.Fatalf("AppRoleAssignmentsClient.Delete(): %v", err) } @@ -128,8 +317,8 @@ func testAppRoleAssignmentsClient_Remove(t *testing.T, c AppRoleAssignmentsClien } } -func testAppRoleAssignmentsClient_Assign(t *testing.T, c AppRoleAssignmentsClientTest, groupId, resourceId, appRoleId string) (appRoleAssignment *msgraph.AppRoleAssignment) { - appRoleAssignment, status, err := c.client.Assign(c.connection.Context, groupId, resourceId, appRoleId) +func testAppRoleAssignmentsClient_Assign(t *testing.T, c AppRoleAssignmentsClientTest, principalId, resourceServicePrincipalId, appRoleId string) (appRoleAssignment *msgraph.AppRoleAssignment) { + appRoleAssignment, status, err := c.client.Assign(c.connection.Context, principalId, resourceServicePrincipalId, appRoleId) if err != nil { t.Fatalf("AppRoleAssignmentsClient.Create(): %v", err) } From 04fc39ac7813e532fbbc80f85321df0d6ba29063 Mon Sep 17 00:00:00 2001 From: Michal Jirman Date: Mon, 3 May 2021 16:59:19 +0100 Subject: [PATCH 2/2] add support for app role assignemnts using the appRoleAssignments relationship --- msgraph/serviceprincipals.go | 86 +++++++++++++++++++ msgraph/serviceprincipals_test.go | 132 ++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) diff --git a/msgraph/serviceprincipals.go b/msgraph/serviceprincipals.go index 8879698e..b2fe325b 100644 --- a/msgraph/serviceprincipals.go +++ b/msgraph/serviceprincipals.go @@ -333,3 +333,89 @@ func (c *ServicePrincipalsClient) ListGroupMemberships(ctx context.Context, id s } return &data.Groups, status, nil } + +// ListAppRoleAssignment retrieves a list of appRoleAssignment that users, groups, or client service principals have been granted for the given resource service principal. +func (c *ServicePrincipalsClient) ListAppRoleAssignments(ctx context.Context, resourceId string) (*[]AppRoleAssignment, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/servicePrincipals/%s/appRoleAssignedTo", resourceId), + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("ServicePrincipalsClient.BaseClient.Get(): %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 data struct { + AppRoleAssignments []AppRoleAssignment `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + return &data.AppRoleAssignments, status, nil +} + +// RemoveAppRoleAssignment deletes an appRoleAssignment that a user, group, or client service principal has been granted for a resource service principal. +func (c *ServicePrincipalsClient) RemoveAppRoleAssignment(ctx context.Context, resourceId, appRoleAssignmentId string) (int, error) { + _, status, _, err := c.BaseClient.Delete(ctx, DeleteHttpRequestInput{ + ValidStatusCodes: []int{http.StatusNoContent}, + Uri: Uri{ + Entity: fmt.Sprintf("/servicePrincipals/%s/appRoleAssignedTo/%s", resourceId, appRoleAssignmentId), + HasTenantId: true, + }, + }) + if err != nil { + return status, fmt.Errorf("AppRoleAssignmentsClient.BaseClient.Delete(): %v", err) + } + return status, nil +} + +// AppRoleAssignedTo assigns an app role for a resource service principal, to a user, group, or client service principal. +// To grant an app role assignment, you need three identifiers: +// +// principalId: The id of the user, group or client servicePrincipal to which you are assigning the app role. +// resourceId: The id of the resource servicePrincipal which has defined the app role. +// appRoleId: The id of the appRole (defined on the resource service principal) to assign to a user, group, or service principal. +func (c *ServicePrincipalsClient) AssignAppRoleForResource(ctx context.Context, principalId, resourceId, appRoleId string) (*AppRoleAssignment, int, error) { + var status int + data := struct { + PrincipalId string `json:"principalId"` + ResourceId string `json:"resourceId"` + AppRoleId string `json:"appRoleId"` + }{ + PrincipalId: principalId, + ResourceId: resourceId, + AppRoleId: appRoleId, + } + + 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}, + Uri: Uri{ + Entity: fmt.Sprintf("/servicePrincipals/%s/appRoleAssignedTo", resourceId), + HasTenantId: true, + }, + }) + if err != nil { + return nil, status, fmt.Errorf("ServicePrincipalsClient.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 appRoleAssignment AppRoleAssignment + if err := json.Unmarshal(respBody, &appRoleAssignment); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + return &appRoleAssignment, status, nil +} diff --git a/msgraph/serviceprincipals_test.go b/msgraph/serviceprincipals_test.go index 336f6684..e9b10668 100644 --- a/msgraph/serviceprincipals_test.go +++ b/msgraph/serviceprincipals_test.go @@ -4,6 +4,8 @@ import ( "fmt" "testing" + "github.com/hashicorp/go-uuid" + "github.com/manicminer/hamilton/auth" "github.com/manicminer/hamilton/internal/test" "github.com/manicminer/hamilton/internal/utils" @@ -81,6 +83,98 @@ func TestServicePrincipalsClient(t *testing.T) { testApplicationsClient_Delete(t, a, *app.ID) } +func TestServicePrincipalsClient_AppRoleAssignments(t *testing.T) { + rs := test.RandomString() + c := ServicePrincipalsClientTest{ + connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), + randomString: rs, + } + c.client = msgraph.NewServicePrincipalsClient(c.connection.AuthConfig.TenantID) + c.client.BaseClient.Authorizer = c.connection.Authorizer + + a := ApplicationsClientTest{ + connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), + randomString: rs, + } + a.client = msgraph.NewApplicationsClient(c.connection.AuthConfig.TenantID) + a.client.BaseClient.Authorizer = c.connection.Authorizer + // pre-generate uuid for a test app role + testResourceAppRoleId, _ := uuid.GenerateUUID() + // create a new test application with a test app role + app := testApplicationsClient_Create(t, a, msgraph.Application{ + DisplayName: utils.StringPtr(fmt.Sprintf("test-serviceprincipal-%s", a.randomString)), + AppRoles: &[]msgraph.AppRole{ + { + ID: utils.StringPtr(testResourceAppRoleId), + DisplayName: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-%s", a.randomString)), + IsEnabled: utils.BoolPtr(true), + Description: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-description-%s", a.randomString)), + Value: utils.StringPtr(fmt.Sprintf("test-resourceApp-role-value-%s", a.randomString)), + AllowedMemberTypes: &[]msgraph.AppRoleAllowedMemberType{ + msgraph.AppRoleAllowedMemberTypeUser, + msgraph.AppRoleAllowedMemberTypeApplication, + }, + }, + }, + }) + + sp := testServicePrincipalsClient_Create(t, c, msgraph.ServicePrincipal{ + AccountEnabled: utils.BoolPtr(true), + AppId: app.AppId, + DisplayName: utils.StringPtr(fmt.Sprintf("test-serviceprincipal-%s", c.randomString)), + }) + testServicePrincipalsClient_Get(t, c, *sp.ID) + sp.Tags = &([]string{"TestTag"}) + testServicePrincipalsClient_Update(t, c, *sp) + testServicePrincipalsClient_List(t, c) + + g := GroupsClientTest{ + connection: test.NewConnection(auth.MsGraph, auth.TokenVersion2), + randomString: rs, + } + g.client = msgraph.NewGroupsClient(g.connection.AuthConfig.TenantID) + g.client.BaseClient.Authorizer = g.connection.Authorizer + + newGroupParent := msgraph.Group{ + DisplayName: utils.StringPtr("Test Group Parent"), + MailEnabled: utils.BoolPtr(false), + MailNickname: utils.StringPtr(fmt.Sprintf("test-group-parent-%s", c.randomString)), + SecurityEnabled: utils.BoolPtr(true), + } + newGroupChild := msgraph.Group{ + DisplayName: utils.StringPtr("Test Group Child"), + MailEnabled: utils.BoolPtr(false), + MailNickname: utils.StringPtr(fmt.Sprintf("test-group-child-%s", c.randomString)), + SecurityEnabled: utils.BoolPtr(true), + } + + groupParent := testGroupsClient_Create(t, g, newGroupParent) + groupChild := testGroupsClient_Create(t, g, newGroupChild) + groupParent.AppendMember(g.client.BaseClient.Endpoint, g.client.BaseClient.ApiVersion, *groupChild.ID) + testGroupsClient_AddMembers(t, g, groupParent) + groupChild.AppendMember(g.client.BaseClient.Endpoint, g.client.BaseClient.ApiVersion, *sp.ID) + testGroupsClient_AddMembers(t, g, groupChild) + + testServicePrincipalsClient_ListGroupMemberships(t, c, *sp.ID) + + // App Role Assignments + appRoleAssignment := testServicePrincipalsClient_AssignAppRole(t, c, *groupParent.ID, *sp.ID, testResourceAppRoleId) + // list resourceApp role assignments for a test group + appRoleAssignments := testServicePrincipalsClient_ListAppRoleAssignments(t, c, *sp.ID) + if len(*appRoleAssignments) == 0 { + t.Fatal("expected at least one app role assignment assigned to the test group") + } + // removes app role assignment previously set to the test group + testServicePrincipalsClient_RemoveAppRoleAssignment(t, c, *sp.ID, *appRoleAssignment.Id) + + // remove all test resources + testGroupsClient_Delete(t, g, *groupParent.ID) + testGroupsClient_Delete(t, g, *groupChild.ID) + testServicePrincipalsClient_Delete(t, c, *sp.ID) + testApplicationsClient_Delete(t, a, *app.ID) + +} + func testServicePrincipalsClient_Create(t *testing.T, c ServicePrincipalsClientTest, sp msgraph.ServicePrincipal) (servicePrincipal *msgraph.ServicePrincipal) { servicePrincipal, status, err := c.client.Create(c.connection.Context, sp) if err != nil { @@ -160,3 +254,41 @@ func testServicePrincipalsClient_ListGroupMemberships(t *testing.T, c ServicePri return } + +func testServicePrincipalsClient_AssignAppRole(t *testing.T, c ServicePrincipalsClientTest, principalId, resourceId, appRoleId string) (appRoleAssignment *msgraph.AppRoleAssignment) { + appRoleAssignment, status, err := c.client.AssignAppRoleForResource(c.connection.Context, principalId, resourceId, appRoleId) + if err != nil { + t.Fatalf("ServicePrincipalsClient.Create(): %v", err) + } + if status < 200 || status >= 300 { + t.Fatalf("ServicePrincipalsClient.Create(): invalid status: %d", status) + } + if appRoleAssignment == nil { + t.Fatal("ServicePrincipalsClient.Create(): appRoleAssignment was nil") + } + if appRoleAssignment.Id == nil { + t.Fatal("ServicePrincipalsClient.Create(): appRoleAssignment.Id was nil") + } + return +} + +func testServicePrincipalsClient_ListAppRoleAssignments(t *testing.T, c ServicePrincipalsClientTest, resourceId string) (appRoleAssignments *[]msgraph.AppRoleAssignment) { + appRoleAssignments, _, err := c.client.ListAppRoleAssignments(c.connection.Context, resourceId) + if err != nil { + t.Fatalf("ServicePrincipalsClient.List(): %v", err) + } + if appRoleAssignments == nil { + t.Fatal("ServicePrincipalsClient.List(): appRoleAssignments was nil") + } + return +} + +func testServicePrincipalsClient_RemoveAppRoleAssignment(t *testing.T, c ServicePrincipalsClientTest, resourceId, appRoleAssignmentId string) { + status, err := c.client.RemoveAppRoleAssignment(c.connection.Context, resourceId, appRoleAssignmentId) + if err != nil { + t.Fatalf("ServicePrincipalsClient.Delete(): %v", err) + } + if status < 200 || status >= 300 { + t.Fatalf("ServicePrincipalsClient.Delete(): invalid status: %d", status) + } +}