Skip to content

Commit d1d04be

Browse files
author
Igor Komlew
authored
MS365: Extend microsoft.identityAndAccess with accessReviews (#5796)
1 parent add347d commit d1d04be

File tree

4 files changed

+602
-7
lines changed

4 files changed

+602
-7
lines changed

providers/ms365/resources/identity_and_access.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"strings"
1010

11+
graphidentitygovernance "github.com/microsoftgraph/msgraph-sdk-go/identitygovernance"
1112
"github.com/microsoftgraph/msgraph-sdk-go/models"
1213
graphpolicies "github.com/microsoftgraph/msgraph-sdk-go/policies"
1314
"go.mondoo.com/cnquery/v11/llx"
@@ -321,3 +322,143 @@ func (a *mqlMicrosoftIdentityAndAccessIdentityAndSignInPolicies) identitySecurit
321322
}
322323
return resource.(*mqlMicrosoftIdentityAndAccessIdentityAndSignInPoliciesIdentitySecurityDefaultsEnforcementPolicy), nil
323324
}
325+
326+
// Needs the permission AccessReview.Read.All
327+
func (a *mqlMicrosoft) accessReviews() (*mqlMicrosoftIdentityAndAccessAccessReviews, error) {
328+
mqlResource, err := CreateResource(a.MqlRuntime, "microsoft.identityAndAccess.accessReviews", map[string]*llx.RawData{})
329+
return mqlResource.(*mqlMicrosoftIdentityAndAccessAccessReviews), err
330+
}
331+
332+
// The $filter query parameter with the contains operator is supported on
333+
// the scope property of accessReviewScheduleDefinition. Use the following format for the request:
334+
// filter=contains(scope/microsoft.graph.accessReviewQueryScope/query, '{object}')
335+
// The {object} can have one of the following values:
336+
// /groups: List every accessReviewScheduleDefinition on individual groups (excludes definitions scoped to all Microsoft 365 groups with guests).
337+
// /groups/{group_id}: List every accessReviewScheduleDefinition on a specific group (excludes definitions scoped to all Microsoft 365 groups with guests).
338+
// ./members: List every accessReviewScheduleDefinition scoped to all Microsoft 365 groups with guests.
339+
// accessPackageAssignments: List every accessReviewScheduleDefinition on an access package.
340+
// roleAssignmentScheduleInstances: List every accessReviewScheduleDefinition for principals that are assigned to a privileged role.
341+
func (a *mqlMicrosoftIdentityAndAccessAccessReviews) list() ([]interface{}, error) {
342+
conn := a.MqlRuntime.Connection.(*connection.Ms365Connection)
343+
graphClient, err := conn.GraphClient()
344+
if err != nil {
345+
return nil, err
346+
}
347+
348+
configuration := &graphidentitygovernance.AccessReviewsDefinitionsRequestBuilderGetRequestConfiguration{}
349+
350+
requestFilter := a.Filter.Data
351+
if requestFilter != "" {
352+
requestParameters := &graphidentitygovernance.AccessReviewsDefinitionsRequestBuilderGetQueryParameters{
353+
Filter: &requestFilter,
354+
}
355+
configuration = &graphidentitygovernance.AccessReviewsDefinitionsRequestBuilderGetRequestConfiguration{
356+
QueryParameters: requestParameters,
357+
}
358+
}
359+
360+
definitions, err := graphClient.
361+
IdentityGovernance().
362+
AccessReviews().
363+
Definitions().
364+
Get(context.Background(), configuration)
365+
if err != nil {
366+
return nil, transformError(err)
367+
}
368+
369+
if definitions == nil {
370+
return nil, nil
371+
}
372+
373+
var accessReviewResources []interface{}
374+
for _, accessReviewSchedule := range definitions.GetValue() {
375+
if accessReviewSchedule.GetId() != nil {
376+
reviewResource, err := newMqlAccessReviewDefinition(a.MqlRuntime, accessReviewSchedule)
377+
if err != nil {
378+
return nil, fmt.Errorf("failed to create MQL resource for access review ID %s: %w", *accessReviewSchedule.GetId(), err)
379+
}
380+
accessReviewResources = append(accessReviewResources, reviewResource)
381+
}
382+
}
383+
384+
return accessReviewResources, nil
385+
}
386+
387+
func newMqlAccessReviewDefinition(runtime *plugin.Runtime, d models.AccessReviewScheduleDefinitionable) (*mqlMicrosoftIdentityAndAccessAccessReviewDefinition, error) {
388+
reviewersDict := []interface{}{}
389+
if d.GetReviewers() != nil {
390+
for _, reviewer := range d.GetReviewers() {
391+
reviewerDict := map[string]*llx.RawData{
392+
"reviewer": llx.StringDataPtr(reviewer.GetQuery()),
393+
"queryType": llx.StringDataPtr(reviewer.GetQueryType()),
394+
"queryRoot": llx.StringDataPtr(reviewer.GetQueryRoot()),
395+
}
396+
397+
reviewersDict = append(reviewersDict, reviewerDict)
398+
}
399+
}
400+
401+
var mqlAccessReviewScheduleSettings plugin.Resource
402+
var err error
403+
404+
if d.GetSettings() != nil {
405+
settingsId := *d.GetId() + "_settings"
406+
407+
var patternDict map[string]interface{}
408+
var rangeDict map[string]interface{}
409+
410+
if recurrence := d.GetSettings().GetRecurrence(); recurrence != nil {
411+
if pattern := recurrence.GetPattern(); pattern != nil {
412+
patternDict, err = convert.JsonToDict(pattern)
413+
if err != nil {
414+
return nil, err
415+
}
416+
}
417+
418+
if recurrenceRange := recurrence.GetRangeEscaped(); recurrenceRange != nil {
419+
rangeDict, err = convert.JsonToDict(recurrenceRange)
420+
if err != nil {
421+
return nil, err
422+
}
423+
}
424+
}
425+
426+
recurrenceDict := map[string]*llx.RawData{
427+
"pattern": llx.DictData(patternDict),
428+
"range": llx.DictData(rangeDict),
429+
}
430+
431+
targetData := map[string]*llx.RawData{
432+
"__id": llx.StringData(settingsId),
433+
"autoApplyDecisionsEnabled": llx.BoolDataPtr(d.GetSettings().GetAutoApplyDecisionsEnabled()),
434+
"decisionHistoriesForReviewersEnabled": llx.BoolDataPtr(d.GetSettings().GetDecisionHistoriesForReviewersEnabled()),
435+
"defaultDecision": llx.StringDataPtr(d.GetSettings().GetDefaultDecision()),
436+
"defaultDecisionEnabled": llx.BoolDataPtr(d.GetSettings().GetDefaultDecisionEnabled()),
437+
"instanceDurationInDays": llx.IntDataPtr(d.GetSettings().GetInstanceDurationInDays()),
438+
"justificationRequiredOnApproval": llx.BoolDataPtr(d.GetSettings().GetJustificationRequiredOnApproval()),
439+
"mailNotificationsEnabled": llx.BoolDataPtr(d.GetSettings().GetMailNotificationsEnabled()),
440+
"recommendationsEnabled": llx.BoolDataPtr(d.GetSettings().GetRecommendationsEnabled()),
441+
"recurrence": llx.DictData(recurrenceDict),
442+
}
443+
444+
mqlAccessReviewScheduleSettings, err = CreateResource(runtime, "microsoft.identityAndAccess.accessReviewDefinition.accessReviewScheduleSettings", targetData)
445+
if err != nil {
446+
return nil, err
447+
}
448+
}
449+
450+
resource, err := CreateResource(runtime, "microsoft.identityAndAccess.accessReviewDefinition",
451+
map[string]*llx.RawData{
452+
"__id": llx.StringDataPtr(d.GetId()),
453+
"id": llx.StringDataPtr(d.GetId()),
454+
"displayName": llx.StringDataPtr(d.GetDisplayName()),
455+
"status": llx.StringDataPtr(d.GetStatus()),
456+
"reviewers": llx.DictData(reviewersDict),
457+
"settings": llx.ResourceData(mqlAccessReviewScheduleSettings, "microsoft.identityAndAccess.accessReviewDefinition.accessReviewScheduleSettings"),
458+
})
459+
if err != nil {
460+
return nil, err
461+
}
462+
463+
return resource.(*mqlMicrosoftIdentityAndAccessAccessReviewDefinition), nil
464+
}

providers/ms365/resources/ms365.lr

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,55 @@ microsoft {
3232
tenantDomainName() string
3333
// Identity and Access policies
3434
identityAndAccess() microsoft.identityAndAccess
35+
// Access review definitions
36+
accessReviews() microsoft.identityAndAccess.accessReviews
37+
}
38+
39+
// A filterable list of access review definitions
40+
microsoft.identityAndAccess.accessReviews {
41+
[]microsoft.identityAndAccess.accessReviewDefinition
42+
init(filter? string)
43+
// filter access review definitions. e.g., "contains(scope/microsoft.graph.accessReviewQueryScope/query, './members')"
44+
filter string
45+
}
46+
47+
// Defines a recurring access review schedule and its properties in Microsoft Entra ID.
48+
private microsoft.identityAndAccess.accessReviewDefinition @defaults("id displayName status") {
49+
// The unique identifier for the access review
50+
id string
51+
// The display name for the access review
52+
displayName string
53+
// The status of the access review. Possible values are:
54+
// Initializing, NotStarted, Starting, InProgress, Completing, Completed, AutoReviewing, and AutoReviewed
55+
status string
56+
// This collection of access review scopes is used to define who are the reviewers
57+
reviewers dict
58+
// The settings for an access review series, see type definition below
59+
settings microsoft.identityAndAccess.accessReviewDefinition.accessReviewScheduleSettings
60+
}
61+
62+
// Configures the recurrence, notifications, and decision-handling for an access review series.
63+
private microsoft.identityAndAccess.accessReviewDefinition.accessReviewScheduleSettings @defaults("mailNotificationsEnabled reminderNotificationsEnabled defaultDecision defaultDecisionEnabled") {
64+
// True if decisions are automatically applied
65+
autoApplyDecisionsEnabled bool
66+
// True if decisions on previous access review stages are available for reviewers on an accessReviewInstance with multiple subsequent stages
67+
decisionHistoriesForReviewersEnabled bool
68+
// Decision chosen if defaultDecisionEnabled is enabled. Can be one of Approve, Deny, or Recommendation
69+
defaultDecision string
70+
// True if the default decision is enabled or disabled when reviewers do not respond
71+
defaultDecisionEnabled bool
72+
// Duration of an access review instance in days
73+
instanceDurationInDays int
74+
// True if reminders are enabled or disabled
75+
reminderNotificationsEnabled bool
76+
// True if reviewers are required to provide justification with their decision
77+
justificationRequiredOnApproval bool
78+
// True if emails are enabled or disabled
79+
mailNotificationsEnabled bool
80+
// True if decision recommendations are enabled or disabled
81+
recommendationsEnabled bool
82+
// Detailed settings for recurrence using the standard Outlook recurrence object
83+
recurrence dict
3584
}
3685

3786
// Microsoft groups

0 commit comments

Comments
 (0)