diff --git a/providers/ms365/resources/applications.go b/providers/ms365/resources/applications.go index 34c2c0ff0a..3ee6bf8c0d 100644 --- a/providers/ms365/resources/applications.go +++ b/providers/ms365/resources/applications.go @@ -32,10 +32,6 @@ func (a *mqlMicrosoft) applications() ([]interface{}, error) { res := []interface{}{} apps := resp.GetValue() for _, app := range apps { - identifierUris := []interface{}{} - for _, uri := range app.GetIdentifierUris() { - identifierUris = append(identifierUris, uri) - } mqlResource, err := CreateResource(a.MqlRuntime, "microsoft.application", map[string]*llx.RawData{ "id": llx.StringData(convert.ToString(app.GetId())), @@ -44,7 +40,7 @@ func (a *mqlMicrosoft) applications() ([]interface{}, error) { "displayName": llx.StringData(convert.ToString(app.GetDisplayName())), "publisherDomain": llx.StringData(convert.ToString(app.GetPublisherDomain())), "signInAudience": llx.StringData(convert.ToString(app.GetSignInAudience())), - "identifierUris": llx.ArrayData(identifierUris, types.String), + "identifierUris": llx.ArrayData(convert.SliceAnyToInterface(app.GetIdentifierUris()), types.String), }) if err != nil { return nil, err diff --git a/providers/ms365/resources/ms365.lr b/providers/ms365/resources/ms365.lr index 1112d87f9a..cd8f85000c 100644 --- a/providers/ms365/resources/ms365.lr +++ b/providers/ms365/resources/ms365.lr @@ -15,6 +15,8 @@ microsoft { applications() []microsoft.application // List of service principals serviceprincipals() []microsoft.serviceprincipal + // List of enterprise applications + enterpriseApplications() []microsoft.serviceprincipal // Microsoft 365 settings settings() dict } @@ -164,9 +166,41 @@ private microsoft.application @defaults("id displayName") { } // Microsoft Service Principal -private microsoft.serviceprincipal @defaults("id") { +private microsoft.serviceprincipal @defaults("name") { // Service principal ID id string + // Service principal type + type string + // Service principal name + name string + // Service principal tags + tags []string + // Whether users can sign into the service principal (application) + enabled bool + // Service principal homepage URL + homepageUrl string + // Service principal terms of service URL + termsOfServiceUrl string + // Service principal reply URLs + replyUrls []string + // Whether users or other apps must be assigned to this service principal before using it + assignmentRequired bool + // Whether the service principal is visible to users + visibleToUsers bool + // Service principal notes + notes string + // The list of assignments (users and groups) this service principal has + assignments []microsoft.serviceprincipal.assignment +} + +// Microsoft Service Principal Assignment +private microsoft.serviceprincipal.assignment @defaults("id") { + // Service Principal Assignment ID + id string + // Service Principal Assignment name + displayName string + // Service Principal Assignment type + type string } // Microsoft Security diff --git a/providers/ms365/resources/ms365.lr.go b/providers/ms365/resources/ms365.lr.go index ed6aedeb5d..ea9c427687 100644 --- a/providers/ms365/resources/ms365.lr.go +++ b/providers/ms365/resources/ms365.lr.go @@ -50,6 +50,10 @@ func init() { // to override args, implement: initMicrosoftServiceprincipal(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) Create: createMicrosoftServiceprincipal, }, + "microsoft.serviceprincipal.assignment": { + // to override args, implement: initMicrosoftServiceprincipalAssignment(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) + Create: createMicrosoftServiceprincipalAssignment, + }, "microsoft.security": { // to override args, implement: initMicrosoftSecurity(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]*llx.RawData, plugin.Resource, error) Create: createMicrosoftSecurity, @@ -184,6 +188,9 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "microsoft.serviceprincipals": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMicrosoft).GetServiceprincipals()).ToDataRes(types.Array(types.Resource("microsoft.serviceprincipal"))) }, + "microsoft.enterpriseApplications": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoft).GetEnterpriseApplications()).ToDataRes(types.Array(types.Resource("microsoft.serviceprincipal"))) + }, "microsoft.settings": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMicrosoft).GetSettings()).ToDataRes(types.Dict) }, @@ -370,6 +377,48 @@ var getDataFields = map[string]func(r plugin.Resource) *plugin.DataRes{ "microsoft.serviceprincipal.id": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMicrosoftServiceprincipal).GetId()).ToDataRes(types.String) }, + "microsoft.serviceprincipal.type": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipal).GetType()).ToDataRes(types.String) + }, + "microsoft.serviceprincipal.name": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipal).GetName()).ToDataRes(types.String) + }, + "microsoft.serviceprincipal.tags": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipal).GetTags()).ToDataRes(types.Array(types.String)) + }, + "microsoft.serviceprincipal.enabled": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipal).GetEnabled()).ToDataRes(types.Bool) + }, + "microsoft.serviceprincipal.homepageUrl": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipal).GetHomepageUrl()).ToDataRes(types.String) + }, + "microsoft.serviceprincipal.termsOfServiceUrl": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipal).GetTermsOfServiceUrl()).ToDataRes(types.String) + }, + "microsoft.serviceprincipal.replyUrls": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipal).GetReplyUrls()).ToDataRes(types.Array(types.String)) + }, + "microsoft.serviceprincipal.assignmentRequired": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipal).GetAssignmentRequired()).ToDataRes(types.Bool) + }, + "microsoft.serviceprincipal.visibleToUsers": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipal).GetVisibleToUsers()).ToDataRes(types.Bool) + }, + "microsoft.serviceprincipal.notes": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipal).GetNotes()).ToDataRes(types.String) + }, + "microsoft.serviceprincipal.assignments": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipal).GetAssignments()).ToDataRes(types.Array(types.Resource("microsoft.serviceprincipal.assignment"))) + }, + "microsoft.serviceprincipal.assignment.id": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipalAssignment).GetId()).ToDataRes(types.String) + }, + "microsoft.serviceprincipal.assignment.displayName": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipalAssignment).GetDisplayName()).ToDataRes(types.String) + }, + "microsoft.serviceprincipal.assignment.type": func(r plugin.Resource) *plugin.DataRes { + return (r.(*mqlMicrosoftServiceprincipalAssignment).GetType()).ToDataRes(types.String) + }, "microsoft.security.secureScores": func(r plugin.Resource) *plugin.DataRes { return (r.(*mqlMicrosoftSecurity).GetSecureScores()).ToDataRes(types.Array(types.Resource("microsoft.security.securityscore"))) }, @@ -617,6 +666,10 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlMicrosoft).Serviceprincipals, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) return }, + "microsoft.enterpriseApplications": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoft).EnterpriseApplications, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) + return + }, "microsoft.settings": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlMicrosoft).Settings, ok = plugin.RawToTValue[interface{}](v.Value, v.Error) return @@ -893,6 +946,66 @@ var setDataFields = map[string]func(r plugin.Resource, v *llx.RawData) bool { r.(*mqlMicrosoftServiceprincipal).Id, ok = plugin.RawToTValue[string](v.Value, v.Error) return }, + "microsoft.serviceprincipal.type": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipal).Type, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.name": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipal).Name, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.tags": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipal).Tags, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.enabled": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipal).Enabled, ok = plugin.RawToTValue[bool](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.homepageUrl": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipal).HomepageUrl, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.termsOfServiceUrl": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipal).TermsOfServiceUrl, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.replyUrls": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipal).ReplyUrls, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.assignmentRequired": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipal).AssignmentRequired, ok = plugin.RawToTValue[bool](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.visibleToUsers": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipal).VisibleToUsers, ok = plugin.RawToTValue[bool](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.notes": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipal).Notes, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.assignments": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipal).Assignments, ok = plugin.RawToTValue[[]interface{}](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.assignment.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipalAssignment).__id, ok = v.Value.(string) + return + }, + "microsoft.serviceprincipal.assignment.id": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipalAssignment).Id, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.assignment.displayName": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipalAssignment).DisplayName, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, + "microsoft.serviceprincipal.assignment.type": func(r plugin.Resource, v *llx.RawData) (ok bool) { + r.(*mqlMicrosoftServiceprincipalAssignment).Type, ok = plugin.RawToTValue[string](v.Value, v.Error) + return + }, "microsoft.security.__id": func(r plugin.Resource, v *llx.RawData) (ok bool) { r.(*mqlMicrosoftSecurity).__id, ok = v.Value.(string) return @@ -1252,6 +1365,7 @@ type mqlMicrosoft struct { Domains plugin.TValue[[]interface{}] Applications plugin.TValue[[]interface{}] Serviceprincipals plugin.TValue[[]interface{}] + EnterpriseApplications plugin.TValue[[]interface{}] Settings plugin.TValue[interface{}] } @@ -1383,6 +1497,22 @@ func (c *mqlMicrosoft) GetServiceprincipals() *plugin.TValue[[]interface{}] { }) } +func (c *mqlMicrosoft) GetEnterpriseApplications() *plugin.TValue[[]interface{}] { + return plugin.GetOrCompute[[]interface{}](&c.EnterpriseApplications, func() ([]interface{}, error) { + if c.MqlRuntime.HasRecording { + d, err := c.MqlRuntime.FieldResourceFromRecording("microsoft", c.__id, "enterpriseApplications") + if err != nil { + return nil, err + } + if d != nil { + return d.Value.([]interface{}), nil + } + } + + return c.enterpriseApplications() + }) +} + func (c *mqlMicrosoft) GetSettings() *plugin.TValue[interface{}] { return plugin.GetOrCompute[interface{}](&c.Settings, func() (interface{}, error) { return c.settings() @@ -1985,6 +2115,17 @@ type mqlMicrosoftServiceprincipal struct { __id string // optional: if you define mqlMicrosoftServiceprincipalInternal it will be used here Id plugin.TValue[string] + Type plugin.TValue[string] + Name plugin.TValue[string] + Tags plugin.TValue[[]interface{}] + Enabled plugin.TValue[bool] + HomepageUrl plugin.TValue[string] + TermsOfServiceUrl plugin.TValue[string] + ReplyUrls plugin.TValue[[]interface{}] + AssignmentRequired plugin.TValue[bool] + VisibleToUsers plugin.TValue[bool] + Notes plugin.TValue[string] + Assignments plugin.TValue[[]interface{}] } // createMicrosoftServiceprincipal creates a new instance of this resource @@ -2028,6 +2169,109 @@ func (c *mqlMicrosoftServiceprincipal) GetId() *plugin.TValue[string] { return &c.Id } +func (c *mqlMicrosoftServiceprincipal) GetType() *plugin.TValue[string] { + return &c.Type +} + +func (c *mqlMicrosoftServiceprincipal) GetName() *plugin.TValue[string] { + return &c.Name +} + +func (c *mqlMicrosoftServiceprincipal) GetTags() *plugin.TValue[[]interface{}] { + return &c.Tags +} + +func (c *mqlMicrosoftServiceprincipal) GetEnabled() *plugin.TValue[bool] { + return &c.Enabled +} + +func (c *mqlMicrosoftServiceprincipal) GetHomepageUrl() *plugin.TValue[string] { + return &c.HomepageUrl +} + +func (c *mqlMicrosoftServiceprincipal) GetTermsOfServiceUrl() *plugin.TValue[string] { + return &c.TermsOfServiceUrl +} + +func (c *mqlMicrosoftServiceprincipal) GetReplyUrls() *plugin.TValue[[]interface{}] { + return &c.ReplyUrls +} + +func (c *mqlMicrosoftServiceprincipal) GetAssignmentRequired() *plugin.TValue[bool] { + return &c.AssignmentRequired +} + +func (c *mqlMicrosoftServiceprincipal) GetVisibleToUsers() *plugin.TValue[bool] { + return &c.VisibleToUsers +} + +func (c *mqlMicrosoftServiceprincipal) GetNotes() *plugin.TValue[string] { + return &c.Notes +} + +func (c *mqlMicrosoftServiceprincipal) GetAssignments() *plugin.TValue[[]interface{}] { + return &c.Assignments +} + +// mqlMicrosoftServiceprincipalAssignment for the microsoft.serviceprincipal.assignment resource +type mqlMicrosoftServiceprincipalAssignment struct { + MqlRuntime *plugin.Runtime + __id string + // optional: if you define mqlMicrosoftServiceprincipalAssignmentInternal it will be used here + Id plugin.TValue[string] + DisplayName plugin.TValue[string] + Type plugin.TValue[string] +} + +// createMicrosoftServiceprincipalAssignment creates a new instance of this resource +func createMicrosoftServiceprincipalAssignment(runtime *plugin.Runtime, args map[string]*llx.RawData) (plugin.Resource, error) { + res := &mqlMicrosoftServiceprincipalAssignment{ + MqlRuntime: runtime, + } + + err := SetAllData(res, args) + if err != nil { + return res, err + } + + if res.__id == "" { + res.__id, err = res.id() + if err != nil { + return nil, err + } + } + + if runtime.HasRecording { + args, err = runtime.ResourceFromRecording("microsoft.serviceprincipal.assignment", res.__id) + if err != nil || args == nil { + return res, err + } + return res, SetAllData(res, args) + } + + return res, nil +} + +func (c *mqlMicrosoftServiceprincipalAssignment) MqlName() string { + return "microsoft.serviceprincipal.assignment" +} + +func (c *mqlMicrosoftServiceprincipalAssignment) MqlID() string { + return c.__id +} + +func (c *mqlMicrosoftServiceprincipalAssignment) GetId() *plugin.TValue[string] { + return &c.Id +} + +func (c *mqlMicrosoftServiceprincipalAssignment) GetDisplayName() *plugin.TValue[string] { + return &c.DisplayName +} + +func (c *mqlMicrosoftServiceprincipalAssignment) GetType() *plugin.TValue[string] { + return &c.Type +} + // mqlMicrosoftSecurity for the microsoft.security resource type mqlMicrosoftSecurity struct { MqlRuntime *plugin.Runtime diff --git a/providers/ms365/resources/ms365.lr.manifest.yaml b/providers/ms365/resources/ms365.lr.manifest.yaml index da0af3a35a..57c0509a1d 100755 --- a/providers/ms365/resources/ms365.lr.manifest.yaml +++ b/providers/ms365/resources/ms365.lr.manifest.yaml @@ -6,6 +6,8 @@ resources: fields: applications: {} domains: {} + enterpriseApplications: + min_mondoo_version: latest groups: {} organizations: {} serviceprincipals: {} @@ -78,6 +80,13 @@ resources: ttl: {} is_private: true min_mondoo_version: 5.15.0 + microsoft.enterpriseApplication: + fields: + id: {} + name: {} + tags: {} + type: {} + min_mondoo_version: latest microsoft.group: fields: displayName: {} @@ -152,9 +161,52 @@ resources: min_mondoo_version: 5.15.0 microsoft.serviceprincipal: fields: + appRoleAssignments: + min_mondoo_version: latest + assignmentRequired: + min_mondoo_version: latest + assignments: + min_mondoo_version: latest + enabled: + min_mondoo_version: latest + homepageUrl: + min_mondoo_version: latest id: {} + name: + min_mondoo_version: latest + notes: + min_mondoo_version: latest + properties: + min_mondoo_version: latest + replyUrls: + min_mondoo_version: latest + tags: + min_mondoo_version: latest + termsOfServiceUrl: + min_mondoo_version: latest + type: + min_mondoo_version: latest + userAccessUrl: + min_mondoo_version: latest + visibleToUsers: + min_mondoo_version: latest is_private: true min_mondoo_version: 5.15.0 + microsoft.serviceprincipal.appRoleAssignment: + fields: + displayName: {} + id: {} + type: {} + min_mondoo_version: latest + microsoft.serviceprincipal.assignment: + fields: + appId: {} + displayName: {} + id: {} + resourceName: {} + type: {} + is_private: true + min_mondoo_version: latest microsoft.user: fields: accountEnabled: {} diff --git a/providers/ms365/resources/serviceprincipals.go b/providers/ms365/resources/serviceprincipals.go index 6a53836336..e46e74f92e 100644 --- a/providers/ms365/resources/serviceprincipals.go +++ b/providers/ms365/resources/serviceprincipals.go @@ -8,23 +8,53 @@ import ( "github.com/microsoftgraph/msgraph-sdk-go/serviceprincipals" "go.mondoo.com/cnquery/v9/llx" + "go.mondoo.com/cnquery/v9/providers-sdk/v1/plugin" "go.mondoo.com/cnquery/v9/providers-sdk/v1/util/convert" "go.mondoo.com/cnquery/v9/providers/ms365/connection" + "go.mondoo.com/cnquery/v9/types" + "go.mondoo.com/cnquery/v9/utils/stringx" ) func (m *mqlMicrosoftServiceprincipal) id() (string, error) { return m.Id.Data, nil } +func (m *mqlMicrosoftServiceprincipalAssignment) id() (string, error) { + return m.Id.Data, nil +} + +// enterprise applications are just service principals with a special tag, attached to them +// this is the same way the portal UI fetches the enterprise apps by looking for the tag +func (a *mqlMicrosoft) enterpriseApplications() ([]interface{}, error) { + conn := a.MqlRuntime.Connection.(*connection.Ms365Connection) + top := int32(999) + filter := "tags/Any(x: x eq 'WindowsAzureActiveDirectoryIntegratedApp')" + params := &serviceprincipals.ServicePrincipalsRequestBuilderGetQueryParameters{ + Top: &top, + Filter: &filter, + Expand: []string{"appRoleAssignedTo"}, + } + return fetchServicePrincipals(a.MqlRuntime, conn, params) +} + func (a *mqlMicrosoft) serviceprincipals() ([]interface{}, error) { conn := a.MqlRuntime.Connection.(*connection.Ms365Connection) + top := int32(999) + params := &serviceprincipals.ServicePrincipalsRequestBuilderGetQueryParameters{ + Top: &top, + } + return fetchServicePrincipals(a.MqlRuntime, conn, params) +} + +func fetchServicePrincipals(runtime *plugin.Runtime, conn *connection.Ms365Connection, params *serviceprincipals.ServicePrincipalsRequestBuilderGetQueryParameters) ([]interface{}, error) { graphClient, err := graphClient(conn) if err != nil { return nil, err - } - // TODO: we need to use Top, there are more than 100 SPs. + } // TODO: what if we have more than 1k SPs? ctx := context.Background() - resp, err := graphClient.ServicePrincipals().Get(ctx, &serviceprincipals.ServicePrincipalsRequestBuilderGetRequestConfiguration{}) + resp, err := graphClient.ServicePrincipals().Get(ctx, &serviceprincipals.ServicePrincipalsRequestBuilderGetRequestConfiguration{ + QueryParameters: params, + }) if err != nil { return nil, transformError(err) } @@ -32,10 +62,37 @@ func (a *mqlMicrosoft) serviceprincipals() ([]interface{}, error) { res := []interface{}{} sps := resp.GetValue() for _, sp := range sps { - mqlResource, err := CreateResource(a.MqlRuntime, "microsoft.serviceprincipal", - map[string]*llx.RawData{ - "id": llx.StringData(convert.ToString(sp.GetId())), + hideApp := stringx.Contains(sp.GetTags(), "HideApp") + assignments := []interface{}{} + for _, a := range sp.GetAppRoleAssignedTo() { + assignment, err := CreateResource(runtime, "microsoft.serviceprincipal.assignment", map[string]*llx.RawData{ + "id": llx.StringDataPtr(a.GetId()), + "displayName": llx.StringDataPtr(a.GetPrincipalDisplayName()), + "type": llx.StringDataPtr(a.GetPrincipalType()), }) + if err != nil { + return nil, err + } + assignments = append(assignments, assignment) + } + args := map[string]*llx.RawData{ + "id": llx.StringDataPtr(sp.GetId()), + "name": llx.StringDataPtr(sp.GetDisplayName()), + "type": llx.StringDataPtr(sp.GetServicePrincipalType()), + "tags": llx.ArrayData(convert.SliceAnyToInterface(sp.GetTags()), types.String), + "enabled": llx.BoolDataPtr(sp.GetAccountEnabled()), + "homepageUrl": llx.StringDataPtr(sp.GetHomepage()), + "replyUrls": llx.ArrayData(convert.SliceAnyToInterface(sp.GetReplyUrls()), types.String), + "assignmentRequired": llx.BoolDataPtr(sp.GetAppRoleAssignmentRequired()), + "visibleToUsers": llx.BoolData(!hideApp), + "notes": llx.StringDataPtr(sp.GetNotes()), + "assignments": llx.ArrayData(assignments, types.ResourceLike), + } + info := sp.GetInfo() + if info != nil { + args["termsOfServiceUrl"] = llx.StringDataPtr(info.GetTermsOfServiceUrl()) + } + mqlResource, err := CreateResource(runtime, "microsoft.serviceprincipal", args) if err != nil { return nil, err }