Skip to content

Commit

Permalink
Merge pull request #48 from tsologub/feature/list-deleted
Browse files Browse the repository at this point in the history
List deleted API for apps, groups, and users
  • Loading branch information
manicminer authored May 27, 2021
2 parents d50fc4c + bb876d9 commit 72a97cf
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 72 deletions.
28 changes: 28 additions & 0 deletions msgraph/applications.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,34 @@ func (c *ApplicationsClient) Delete(ctx context.Context, id string) (int, error)
return status, nil
}

// ListDeleted retrieves a list of recently deleted applications, optionally filtered using OData.
func (c *ApplicationsClient) ListDeleted(ctx context.Context, filter string) (*[]Application, int, error) {
params := url.Values{}
if filter != "" {
params.Add("$filter", filter)
}
resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{
ValidStatusCodes: []int{http.StatusOK},
Uri: Uri{
Entity: "/directory/deleteditems/microsoft.graph.application",
Params: params,
HasTenantId: true,
},
})
if err != nil {
return nil, status, err
}
defer resp.Body.Close()
respBody, _ := ioutil.ReadAll(resp.Body)
var data struct {
DeletedApps []Application `json:"value"`
}
if err = json.Unmarshal(respBody, &data); err != nil {
return nil, status, err
}
return &data.DeletedApps, status, nil
}

// AddPassword appends a new password credential to an Application.
func (c *ApplicationsClient) AddPassword(ctx context.Context, applicationId string, passwordCredential PasswordCredential) (*PasswordCredential, int, error) {
var status int
Expand Down
28 changes: 28 additions & 0 deletions msgraph/applications_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func TestApplicationsClient(t *testing.T) {
testApplicationsClient_RemovePassword(t, c, app, pwd)
testApplicationsClient_List(t, c)
testApplicationsClient_Delete(t, c, *app.ID)
testApplicationsClient_ListDeleted(t, c, *app.ID)
}

func TestApplicationsClient_groupMembershipClaims(t *testing.T) {
Expand Down Expand Up @@ -198,3 +199,30 @@ func testApplicationsClient_RemovePassword(t *testing.T, c ApplicationsClientTes
t.Fatalf("ApplicationsClient.RemovePassword(): invalid status: %d", status)
}
}

func testApplicationsClient_ListDeleted(t *testing.T, c ApplicationsClientTest, expectedId string) (deletedApps *[]msgraph.Application) {
deletedApps, status, err := c.client.ListDeleted(c.connection.Context, "")
if err != nil {
t.Fatalf("ApplicationsClient.ListDeleted(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("ApplicationsClient.ListDeleted(): invalid status: %d", status)
}
if deletedApps == nil {
t.Fatal("ApplicationsClient.ListDeleted(): deletedApps was nil")
}
if len(*deletedApps) == 0 {
t.Fatal("ApplicationsClient.ListDeleted(): expected at least 1 deleted application. was: 0")
}
found := false
for _, app := range *deletedApps {
if app.ID != nil && *app.ID == expectedId {
found = true
break
}
}
if !found {
t.Fatalf("ApplicationsClient.ListDeleted(): expected appId %q in result", expectedId)
}
return
}
29 changes: 29 additions & 0 deletions msgraph/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,35 @@ func (c *GroupsClient) Delete(ctx context.Context, id string) (int, error) {
return status, nil
}

// ListDeleted retrieves a list of recently deleted O365 groups, optionally filtered using OData.
// TODO: add test coverage once API supports creating O365 groups
func (c *GroupsClient) ListDeleted(ctx context.Context, filter string) (*[]Group, int, error) {
params := url.Values{}
if filter != "" {
params.Add("$filter", filter)
}
resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{
ValidStatusCodes: []int{http.StatusOK},
Uri: Uri{
Entity: "/directory/deleteditems/microsoft.graph.group",
Params: params,
HasTenantId: true,
},
})
if err != nil {
return nil, status, err
}
defer resp.Body.Close()
respBody, _ := ioutil.ReadAll(resp.Body)
var data struct {
DeletedGroups []Group `json:"value"`
}
if err = json.Unmarshal(respBody, &data); err != nil {
return nil, status, err
}
return &data.DeletedGroups, status, nil
}

// ListMembers retrieves the members of the specified Group.
// id is the object ID of the group.
func (c *GroupsClient) ListMembers(ctx context.Context, id string) (*[]string, int, error) {
Expand Down
159 changes: 87 additions & 72 deletions msgraph/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,38 @@ type ApiPreAuthorizedApplication struct {

// Application describes an Application object.
type Application struct {
ID *string `json:"id,omitempty"`
AddIns *[]AddIn `json:"addIns,omitempty"`
Api *ApplicationApi `json:"api,omitempty"`
AppId *string `json:"appId,omitempty"`
AppRoles *[]AppRole `json:"appRoles,omitempty"`
CreatedDateTime *time.Time `json:"createdDateTime,omitempty"`
DeletedDateTime *time.Time `json:"deletedDateTime,omitempty"`
DisplayName *string `json:"displayName,omitempty"`
GroupMembershipClaims *[]GroupMembershipClaim `json:"groupMembershipClaims,omitempty"`
IdentifierUris *[]string `json:"identifierUris,omitempty"`
Info *InformationalUrl `json:"info,omitempty"`
IsFallbackPublicClient *bool `json:"isFallbackPublicCLient,omitempty"`
KeyCredentials *[]KeyCredential `json:"keyCredentials,omitempty"`
Oauth2RequiredPostResponse *bool `json:"oauth2RequiredPostResponse,omitempty"`
OnPremisesPublishing *OnPremisesPublishing `json:"onPremisePublishing,omitempty"`
OptionalClaims *OptionalClaims `json:"optionalClaims,omitempty"`
ParentalControlSettings *ParentalControlSettings `json:"parentalControlSettings,omitempty"`
PasswordCredentials *[]PasswordCredential `json:"passwordCredentials,omitempty"`
PublicClient *PublicClient `json:"publicClient,omitempty"`
PublisherDomain *string `json:"publisherDomain,omitempty"`
RequiredResourceAccess *[]RequiredResourceAccess `json:"requiredResourceAccess,omitempty"`
SignInAudience SignInAudience `json:"signInAudience,omitempty"`
Tags *[]string `json:"tags,omitempty"`
TokenEncryptionKeyId *string `json:"tokenEncryptionKeyId,omitempty"`
Web *ApplicationWeb `json:"web,omitempty"`
ID *string `json:"id,omitempty"`
AddIns *[]AddIn `json:"addIns,omitempty"`
Api *ApplicationApi `json:"api,omitempty"`
AppId *string `json:"appId,omitempty"`
AppRoles *[]AppRole `json:"appRoles,omitempty"`
CreatedDateTime *time.Time `json:"createdDateTime,omitempty"`
DefaultRedirectUri *string `json:"defaultRedirectUri,omitempty"`
DeletedDateTime *time.Time `json:"deletedDateTime,omitempty"`
DisabledByMicrosoftStatus interface{} `json:"disabledByMicrosoftStatus,omitempty"`
DisplayName *string `json:"displayName,omitempty"`
GroupMembershipClaims *[]GroupMembershipClaim `json:"groupMembershipClaims,omitempty"`
IdentifierUris *[]string `json:"identifierUris,omitempty"`
Info *InformationalUrl `json:"info,omitempty"`
IsAuthorizationServiceEnabled *bool `json:"isAuthorizationServiceEnabled,omitempty"`
IsDeviceOnlyAuthSupported *bool `json:"isDeviceOnlyAuthSupported,omitempty"`
IsFallbackPublicClient *bool `json:"isFallbackPublicClient,omitempty"`
IsManagementRestricted *bool `json:"isManagementRestricted,omitempty"`
KeyCredentials *[]KeyCredential `json:"keyCredentials,omitempty"`
Oauth2RequirePostResponse *bool `json:"oauth2RequirePostResponse,omitempty"`
OnPremisesPublishing *OnPremisesPublishing `json:"onPremisePublishing,omitempty"`
OptionalClaims *OptionalClaims `json:"optionalClaims,omitempty"`
ParentalControlSettings *ParentalControlSettings `json:"parentalControlSettings,omitempty"`
PasswordCredentials *[]PasswordCredential `json:"passwordCredentials,omitempty"`
PublicClient *PublicClient `json:"publicClient,omitempty"`
PublisherDomain *string `json:"publisherDomain,omitempty"`
RequiredResourceAccess *[]RequiredResourceAccess `json:"requiredResourceAccess,omitempty"`
SignInAudience SignInAudience `json:"signInAudience,omitempty"`
Tags *[]string `json:"tags,omitempty"`
TokenEncryptionKeyId *string `json:"tokenEncryptionKeyId,omitempty"`
UniqueName *string `json:"uniqueName,omitempty"`
VerifiedPublisher *VerifiedPublisher `json:"verifiedPublisher,omitempty"`
Web *ApplicationWeb `json:"web,omitempty"`

Owners *[]string `json:"[email protected],omitempty"`
}
Expand Down Expand Up @@ -819,53 +826,61 @@ type SingleSignOnField struct {

// User describes a User object.
type User struct {
ID *string `json:"id,omitempty"`
AboutMe *string `json:"aboutMe,omitempty"`
AccountEnabled *bool `json:"accountEnabled,omitempty"`
BusinessPhones *[]string `json:"businessPhones,omitempty"`
City *string `json:"city,omitempty"`
CompanyName *string `json:"companyName,omitempty"`
Country *string `json:"country,omitempty"`
CreationType *string `json:"creationType,omitempty"`
Department *string `json:"department,omitempty"`
DisplayName *string `json:"displayName,omitempty"`
EmployeeId *string `json:"employeeId,omitempty"`
ExternalUserState *string `json:"externalUserState,omitempty"`
FaxNumber *string `json:"faxNumber,omitempty"`
GivenName *string `json:"givenName,omitempty"`
ImAddresses *[]string `json:"imAddresses,omitempty"`
Interests *[]string `json:"interests,omitempty"`
JobTitle *string `json:"jobTitle,omitempty"`
Mail *string `json:"mail,omitempty"`
MailNickname *string `json:"mailNickname,omitempty"`
MobilePhone *string `json:"mobilePhone,omitempty"`
MySite *string `json:"mySite,omitempty"`
OfficeLocation *string `json:"officeLocation,omitempty"`
OnPremisesDistinguishedName *string `json:"onPremisesDistinguishedName,omitempty"`
OnPremisesDomainName *string `json:"onPremisesDomainName,omitempty"`
OnPremisesImmutableId *string `json:"onPremisesImmutableId,omitempty"`
OnPremisesSamAccountName *string `json:"onPremisesSamAccountName,omitempty"`
OnPremisesSecurityIdentifier *string `json:"onPremisesSecurityIdentifier,omitempty"`
OnPremisesSyncEnabled *bool `json:"onPremisesSyncEnabled,omitempty"`
OnPremisesUserPrincipalName *string `json:"onPremisesUserPrincipalName,omitempty"`
OtherMails *[]string `json:"otherMails,omitempty"`
PasswordPolicies *string `json:"passwordPolicies,omitempty"`
PastProjects *[]string `json:"pastProjects,omitempty"`
PostalCode *string `json:"postalCode,omitempty"`
PreferredDataLocation *string `json:"preferredDataLocation,omitempty"`
PreferredLanguage *string `json:"preferredLanguage,omitempty"`
PreferredName *string `json:"preferredName,omitempty"`
ProxyAddresses *[]string `json:"proxyAddresses,omitempty"`
Responsibilities *[]string `json:"responsibilities,omitempty"`
Schools *[]string `json:"schools,omitempty"`
ShowInAddressList *bool `json:"showInAddressList,omitempty"`
Skills *[]string `json:"skills,omitempty"`
State *string `json:"state,omitempty"`
StreetAddress *string `json:"streetAddress,omitempty"`
Surname *string `json:"surname,omitempty"`
UsageLocation *string `json:"usageLocation,omitempty"`
UserPrincipalName *string `json:"userPrincipalName,omitempty"`
UserType *string `json:"userType,omitempty"`
ID *string `json:"id,omitempty"`
AboutMe *string `json:"aboutMe,omitempty"`
AccountEnabled *bool `json:"accountEnabled,omitempty"`
BusinessPhones *[]string `json:"businessPhones,omitempty"`
City *string `json:"city,omitempty"`
CompanyName *string `json:"companyName,omitempty"`
Country *string `json:"country,omitempty"`
CreatedDateTime *time.Time `json:"createdDateTime,omitempty"`
CreationType *string `json:"creationType,omitempty"`
DeletedDateTime *time.Time `json:"deletedDateTime,omitempty"`
Department *string `json:"department,omitempty"`
DisplayName *string `json:"displayName,omitempty"`
EmployeeHireDate *time.Time `json:"employeeHireDate,omitempty"`
EmployeeId *string `json:"employeeId,omitempty"`
EmployeeType *string `json:"employeeType,omitempty"`
ExternalUserState *string `json:"externalUserState,omitempty"`
FaxNumber *string `json:"faxNumber,omitempty"`
GivenName *string `json:"givenName,omitempty"`
ImAddresses *[]string `json:"imAddresses,omitempty"`
Interests *[]string `json:"interests,omitempty"`
IsManagementRestricted *bool `json:"isManagementRestricted,omitempty"`
IsResourceAccount *bool `json:"isResourceAccount,omitempty"`
JobTitle *string `json:"jobTitle,omitempty"`
Mail *string `json:"mail,omitempty"`
MailNickname *string `json:"mailNickname,omitempty"`
MobilePhone *string `json:"mobilePhone,omitempty"`
MySite *string `json:"mySite,omitempty"`
OfficeLocation *string `json:"officeLocation,omitempty"`
OnPremisesDistinguishedName *string `json:"onPremisesDistinguishedName,omitempty"`
OnPremisesDomainName *string `json:"onPremisesDomainName,omitempty"`
OnPremisesLastSyncDateTime *string `json:"onPremisesLastSyncDateTime,omitempty"`
OnPremisesSamAccountName *string `json:"onPremisesSamAccountName,omitempty"`
OnPremisesSecurityIdentifier *string `json:"onPremisesSecurityIdentifier,omitempty"`
OnPremisesSyncEnabled *bool `json:"onPremisesSyncEnabled,omitempty"`
OnPremisesUserPrincipalName *string `json:"onPremisesUserPrincipalName,omitempty"`
OtherMails *[]string `json:"otherMails,omitempty"`
PasswordPolicies *string `json:"passwordPolicies,omitempty"`
PastProjects *[]string `json:"pastProjects,omitempty"`
PostalCode *string `json:"postalCode,omitempty"`
PreferredDataLocation *string `json:"preferredDataLocation,omitempty"`
PreferredLanguage *string `json:"preferredLanguage,omitempty"`
PreferredName *string `json:"preferredName,omitempty"`
ProxyAddresses *[]string `json:"proxyAddresses,omitempty"`
RefreshTokensValidFromDateTime *time.Time `json:"refreshTokensValidFromDateTime,omitempty"`
Responsibilities *[]string `json:"responsibilities,omitempty"`
Schools *[]string `json:"schools,omitempty"`
ShowInAddressList *bool `json:"showInAddressList,omitempty"`
SignInSessionsValidFromDateTime *time.Time `json:"signInSessionsValidFromDateTime,omitempty"`
Skills *[]string `json:"skills,omitempty"`
State *string `json:"state,omitempty"`
StreetAddress *string `json:"streetAddress,omitempty"`
Surname *string `json:"surname,omitempty"`
UsageLocation *string `json:"usageLocation,omitempty"`
UserPrincipalName *string `json:"userPrincipalName,omitempty"`
UserType *string `json:"userType,omitempty"`

PasswordProfile *UserPasswordProfile `json:"passwordProfile,omitempty"`
}
Expand Down
28 changes: 28 additions & 0 deletions msgraph/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,34 @@ func (c *UsersClient) Delete(ctx context.Context, id string) (int, error) {
return status, nil
}

// ListDeleted retrieves a list of recently deleted users, optionally filtered using OData.
func (c *UsersClient) ListDeleted(ctx context.Context, filter string) (*[]User, int, error) {
params := url.Values{}
if filter != "" {
params.Add("$filter", filter)
}
resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{
ValidStatusCodes: []int{http.StatusOK},
Uri: Uri{
Entity: "/directory/deleteditems/microsoft.graph.user",
Params: params,
HasTenantId: true,
},
})
if err != nil {
return nil, status, err
}
defer resp.Body.Close()
respBody, _ := ioutil.ReadAll(resp.Body)
var data struct {
DeletedUsers []User `json:"value"`
}
if err = json.Unmarshal(respBody, &data); err != nil {
return nil, status, err
}
return &data.DeletedUsers, status, nil
}

// ListGroupMemberships returns a list of Groups the user is member of, optionally filtered using OData.
func (c *UsersClient) ListGroupMemberships(ctx context.Context, id string, filter string) (*[]Group, int, error) {
params := url.Values{}
Expand Down
28 changes: 28 additions & 0 deletions msgraph/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func TestUsersClient(t *testing.T) {
testGroupsClient_Delete(t, g, *groupChild.ID)

testUsersClient_Delete(t, c, *user.ID)
testUsersClient_ListDeleted(t, c, *user.ID)
}

func testUsersClient_Create(t *testing.T, c UsersClientTest, u msgraph.User) (user *msgraph.User) {
Expand Down Expand Up @@ -151,3 +152,30 @@ func testUsersClient_ListGroupMemberships(t *testing.T, c UsersClientTest, id st

return
}

func testUsersClient_ListDeleted(t *testing.T, c UsersClientTest, expectedId string) (deletedUsers *[]msgraph.User) {
deletedUsers, status, err := c.client.ListDeleted(c.connection.Context, "")
if err != nil {
t.Fatalf("UsersClient.ListDeleted(): %v", err)
}
if status < 200 || status >= 300 {
t.Fatalf("UsersClient.ListDeleted(): invalid status: %d", status)
}
if deletedUsers == nil {
t.Fatal("UsersClient.ListDeleted(): deletedUsers was nil")
}
if len(*deletedUsers) == 0 {
t.Fatal("UsersClient.ListDeleted(): expected at least 1 deleted user. was: 0")
}
found := false
for _, user := range *deletedUsers {
if user.ID != nil && *user.ID == expectedId {
found = true
break
}
}
if !found {
t.Fatalf("UsersClient.ListDeleted(): expected userId %q in result", expectedId)
}
return
}

0 comments on commit 72a97cf

Please sign in to comment.