From b10ef9f341fc22cf15a3f919dea1ebf428f344ca Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Mon, 18 Jul 2022 17:08:14 +0530 Subject: [PATCH 01/21] Update plugin to use microsoft graph SDK --- azuread/plugin.go | 2 + azuread/service.go | 146 +++++++++++ azuread/table_azuread_group1.go | 446 ++++++++++++++++++++++++++++++++ azuread/table_azuread_user1.go | 266 +++++++++++++++++++ azuread/utils.go | 27 ++ go.mod | 35 ++- go.sum | 70 ++++- 7 files changed, 975 insertions(+), 17 deletions(-) create mode 100644 azuread/table_azuread_group1.go create mode 100644 azuread/table_azuread_user1.go diff --git a/azuread/plugin.go b/azuread/plugin.go index 1f0d573..7811377 100644 --- a/azuread/plugin.go +++ b/azuread/plugin.go @@ -31,6 +31,8 @@ func Plugin(ctx context.Context) *plugin.Plugin { "azuread_service_principal": tableAzureAdServicePrincipal(), "azuread_sign_in_report": tableAzureAdSignInReport(), "azuread_user": tableAzureAdUser(), + "azuread_user_test": tableAzureAdUserTest(), + "azuread_group_test": tableAzureAdGroupTest(), }, } diff --git a/azuread/service.go b/azuread/service.go index ff2efaa..11f695e 100644 --- a/azuread/service.go +++ b/azuread/service.go @@ -3,15 +3,24 @@ package azuread import ( "bytes" "context" + "crypto/x509" "encoding/json" + "encoding/pem" "fmt" + "io/ioutil" "log" "os" "os/exec" "runtime" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/manicminer/hamilton/auth" "github.com/manicminer/hamilton/environments" + a "github.com/microsoft/kiota-authentication-azure-go" + msgraphsdkgo "github.com/microsoftgraph/msgraph-sdk-go" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" ) @@ -229,3 +238,140 @@ func getTenantFromCLI() (string, error) { return tokenResponse.Tenant, nil } + +func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.GraphServiceClient, *msgraphsdkgo.GraphRequestAdapter, error) { + logger := plugin.Logger(ctx) + + // Have we already created and cached the session? + // Hamilton SDK already acquires a new token when expired, so don't handle here again + sessionCacheKey := "GetNewSession" + if cachedData, ok := d.ConnectionManager.Cache.Get(sessionCacheKey); ok { + return cachedData.(*msgraphsdkgo.GraphServiceClient), nil, nil + } + + var tenantID, environment, clientID, clientSecret, certificatePath, certificatePassword string + + azureADConfig := GetConfig(d.Connection) + if azureADConfig.TenantID != nil { + tenantID = *azureADConfig.TenantID + } else { + tenantID = os.Getenv("AZURE_TENANT_ID") + } + + if azureADConfig.Environment != nil { + environment = *azureADConfig.Environment + } else { + environment = os.Getenv("AZURE_ENVIRONMENT") + } + + var enableMsi bool + if azureADConfig.EnableMsi != nil { + enableMsi = *azureADConfig.EnableMsi + } + + // 1. Client secret credentials + if azureADConfig.ClientID != nil { + clientID = *azureADConfig.ClientID + } else { + clientID = os.Getenv("AZURE_CLIENT_ID") + } + + if azureADConfig.ClientSecret != nil { + clientSecret = *azureADConfig.ClientSecret + } else { + clientSecret = os.Getenv("AZURE_CLIENT_SECRET") + } + + // 2. Client certificate credentials + if azureADConfig.CertificatePath != nil { + certificatePath = *azureADConfig.CertificatePath + } else { + certificatePath = os.Getenv("AZURE_CERTIFICATE_PATH") + } + + if azureADConfig.CertificatePassword != nil { + certificatePassword = *azureADConfig.CertificatePassword + } else { + certificatePassword = os.Getenv("AZURE_CERTIFICATE_PASSWORD") + } + + var cloudConfiguration cloud.Configuration + switch environment { + case "AZURECHINACLOUD": + cloudConfiguration = cloud.AzureChina + case "AZUREUSGOVERNMENTCLOUD": + cloudConfiguration = cloud.AzureGovernment + // case "AZUREGERMANCLOUD": + // cloudConfiguration = environments.Germany + default: + cloudConfiguration = cloud.AzurePublic + } + + var cred azcore.TokenCredential + var err error + if tenantID == "" { + cred, err = azidentity.NewAzureCLICredential( + &azidentity.AzureCLICredentialOptions{}, + ) + if err != nil { + logger.Error("GetGraphClient", "credential_error", err) + return nil, nil, fmt.Errorf("error creating credentials: %w", err) + } + } else if tenantID != "" && clientID != "" && clientSecret != "" { + cred, err = azidentity.NewClientSecretCredential( + tenantID, + clientID, + clientSecret, + &azidentity.ClientSecretCredentialOptions{ + ClientOptions: policy.ClientOptions{ + Cloud: cloudConfiguration, + }, + }, + ) + if err != nil { + logger.Error("GetGraphClient", "credential_error", err) + return nil, nil, fmt.Errorf("error creating credentials: %w", err) + } + } else if tenantID != "" && clientID != "" && certificatePath != "" && certificatePassword != "" { // Need to validate + loadFile, err := ioutil.ReadFile(certificatePath) + if err != nil { + return nil, nil, fmt.Errorf("error reading certificate from %s: %v", certificatePath, err) + } + block, _ := pem.Decode(loadFile) + cert, err := x509.ParseCertificate(block.Bytes) + var certs []*x509.Certificate + certs = append(certs, cert) + + cred, err = azidentity.NewClientCertificateCredential( + tenantID, + clientID, + certs, + "", + &azidentity.ClientCertificateCredentialOptions{ + ClientOptions: policy.ClientOptions{ + Cloud: cloudConfiguration, + }, + }, + ) + } else if enableMsi { + cred, err = azidentity.NewManagedIdentityCredential( + &azidentity.ManagedIdentityCredentialOptions{}, + ) + } + + auth, err := a.NewAzureIdentityAuthenticationProvider(cred) + if err != nil { + panic(fmt.Errorf("error creating authentication provider: %w", err)) + } + + adapter, err := msgraphsdkgo.NewGraphRequestAdapter(auth) + if err != nil { + panic(fmt.Errorf("error creating graph adapter: %w", err)) + } + client := msgraphsdkgo.NewGraphServiceClient(adapter) + + // Save session into cache + d.ConnectionManager.Cache.Set(sessionCacheKey, client) + + return client, adapter, nil +} diff --git a/azuread/table_azuread_group1.go b/azuread/table_azuread_group1.go new file mode 100644 index 0000000..cf36f8d --- /dev/null +++ b/azuread/table_azuread_group1.go @@ -0,0 +1,446 @@ +package azuread + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/iancoleman/strcase" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/groups" + "github.com/microsoftgraph/msgraph-sdk-go/groups/item" + "github.com/microsoftgraph/msgraph-sdk-go/groups/item/members" + "github.com/microsoftgraph/msgraph-sdk-go/groups/item/owners" + "github.com/microsoftgraph/msgraph-sdk-go/models" + + "github.com/turbot/go-kit/helpers" + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" +) + +//// TABLE DEFINITION + +func tableAzureAdGroupTest() *plugin.Table { + return &plugin.Table{ + Name: "azuread_group_test", + Description: "Represents an Azure AD user account.", + Get: &plugin.GetConfig{ + Hydrate: getAdGroupTest, + ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), + KeyColumns: plugin.SingleColumn("id"), + }, + List: &plugin.ListConfig{ + Hydrate: listAdGroupsTest, + KeyColumns: plugin.KeyColumnSlice{ + // Key fields + {Name: "display_name", Require: plugin.Optional}, + {Name: "filter", Require: plugin.Optional}, + {Name: "mail", Require: plugin.Optional}, + {Name: "mail_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, + {Name: "on_premises_sync_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, + {Name: "security_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, + }, + }, + Columns: []*plugin.Column{ + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name."}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the group.", Transform: transform.FromGo()}, + {Name: "description", Type: proto.ColumnType_STRING, Description: "An optional description for the group."}, + {Name: "filter", Type: proto.ColumnType_STRING, Transform: transform.FromQual("filter"), Description: "Odata query to search for groups."}, + + // Other fields + {Name: "classification", Type: proto.ColumnType_STRING, Description: "Describes a classification for the group (such as low, medium or high business impact)."}, + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the group was created."}, + {Name: "expiration_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group is set to expire."}, + {Name: "is_assignable_to_role", Type: proto.ColumnType_BOOL, Description: "Indicates whether this group can be assigned to an Azure Active Directory role or not."}, + + // Getting below error while requesting value for isSubscribedByMail + // { + // "error": { + // "code": "ErrorInvalidGroup", + // "message": "The requested group '[id@tenantId]' is invalid.", + // "innerError": { + // "date": "2022-07-13T11:06:23", + // "request-id": "63a83d86-a007-4c68-be75-21cea61d830e", + // "client-request-id": "d69d6667-e818-a322-c694-1fec40b438a8" + // } + // } + // } + // {Name: "is_subscribed_by_mail", Type: proto.ColumnType_BOOL, Description: "Indicates whether the signed-in user is subscribed to receive email conversations. Default value is true."}, + + {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the group, for example, \"serviceadmins@contoso.onmicrosoft.com\"."}, + {Name: "mail_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is mail-enabled."}, + {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user."}, + {Name: "membership_rule", Type: proto.ColumnType_STRING, Description: "The mail alias for the group, unique in the organization."}, + {Name: "membership_rule_processing_state", Type: proto.ColumnType_STRING, Description: "Indicates whether the dynamic membership processing is on or paused. Possible values are On or Paused."}, + {Name: "on_premises_domain_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises Domanin name synchronized from the on-premises directory."}, + {Name: "on_premises_last_sync_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Indicates the last time at which the group was synced with the on-premises directory."}, + {Name: "on_premises_net_bios_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises NetBiosName synchronized from the on-premises directory."}, + {Name: "on_premises_sam_account_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises SAM account name synchronized from the on-premises directory."}, + {Name: "on_premises_security_identifier", Type: proto.ColumnType_STRING, Description: "Contains the on-premises security identifier (SID) for the group that was synchronized from on-premises to the cloud."}, + {Name: "on_premises_sync_enabled", Type: proto.ColumnType_BOOL, Description: "True if this group is synced from an on-premises directory; false if this group was originally synced from an on-premises directory but is no longer synced; null if this object has never been synced from an on-premises directory (default)."}, + {Name: "renewed_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group was last renewed. This cannot be modified directly and is only updated via the renew service action."}, + {Name: "security_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is a security group."}, + {Name: "security_identifier", Type: proto.ColumnType_STRING, Description: "Security identifier of the group, used in Windows scenarios."}, + {Name: "visibility", Type: proto.ColumnType_STRING, Description: "Specifies the group join policy and group content visibility for groups. Possible values are: Private, Public, or Hiddenmembership."}, + + // Json fields + {Name: "assigned_labels", Type: proto.ColumnType_JSON, Description: "The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group."}, + {Name: "group_types", Type: proto.ColumnType_JSON, Description: "Specifies the group type and its membership. If the collection contains Unified, the group is a Microsoft 365 group; otherwise, it's either a security group or distribution group. For details, see [groups overview](https://docs.microsoft.com/en-us/graph/api/resources/groups-overview?view=graph-rest-1.0)."}, + {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupMembers, Transform: transform.FromValue(), Description: "Id of Users and groups that are members of this group."}, + {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupOwners, Transform: transform.FromValue(), Description: "Id od the owners of the group. The owners are a set of non-admin users who are allowed to modify this object."}, + {Name: "proxy_addresses", Type: proto.ColumnType_JSON, Description: "Email addresses for the group that direct to the same group mailbox. For example: [\"SMTP: bob@contoso.com\", \"smtp: bob@sales.contoso.com\"]. The any operator is required to filter expressions on multi-valued properties."}, + // {Name: "resource_behavior_options", Type: proto.ColumnType_JSON, Description: "Specifies the group behaviors that can be set for a Microsoft 365 group during creation. Possible values are AllowOnlyMembersToPost, HideGroupInOutlook, SubscribeNewGroupMembers, WelcomeEmailDisabled."}, + // {Name: "resource_provisioning_options", Type: proto.ColumnType_JSON, Description: "Specifies the group resources that are provisioned as part of Microsoft 365 group creation, that are not normally part of default group creation. Possible value is Team."}, + + // Standard columns + {Name: "tags", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTags, Transform: transform.From(adGroupTags)}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "ID")}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, + }, + } +} + +//// LIST FUNCTION + +func listAdGroupsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + panic(fmt.Errorf("error creating credentials: %w", err)) + } + + // List operations + input := &groups.GroupsRequestBuilderGetQueryParameters{} + + // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow + limit := d.QueryContext.Limit + if limit != nil { + if *limit > 0 && *limit <= 999 { + l := int32(*limit) + input.Top = &l + } + } + + equalQuals := d.KeyColumnQuals + quals := d.Quals + + // Check for query context and requests only for queried columns + givenColumns := d.QueryContext.Columns + selectColumns := buildGroupRequestFields(ctx, givenColumns) + + input.Select = selectColumns + + var queryFilter string + filter := buildGroupQueryFilter(equalQuals) + filter = append(filter, buildGroupBoolNEFilter(quals)...) + + if equalQuals["filter"] != nil { + queryFilter = equalQuals["filter"].GetStringValue() + } + + if queryFilter != "" { + input.Filter = &queryFilter + } else if len(filter) > 0 { + joinStr := strings.Join(filter, " and ") + input.Filter = &joinStr + } + + options := &groups.GroupsRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + result, err := client.Groups().GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list groups. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateGroupCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + group := pageItem.(models.Groupable) + + result := map[string]interface{}{ + "DisplayName": group.GetDisplayName(), + "ID": group.GetId(), + "Description": group.GetDescription(), + "Classification": group.GetClassification(), + "CreatedDateTime": group.GetCreatedDateTime(), + "ExpirationDateTime": group.GetExpirationDateTime(), + "IsAssignableToRole": group.GetIsAssignableToRole(), + //"IsSubscribedByMail": group.GetIsSubscribedByMail(), + "Mail": group.GetMail(), + "MailEnabled": group.GetMailEnabled(), + "MailNickname": group.GetMailNickname(), + "MembershipRule": group.GetMembershipRule(), + "MembershipRuleProcessingState": group.GetMembershipRuleProcessingState(), + "OnPremisesDomainName": group.GetOnPremisesDomainName(), + "OnPremisesLastSyncDateTime": group.GetOnPremisesLastSyncDateTime(), + "OnPremisesNetBiosName": group.GetOnPremisesNetBiosName(), + "OnPremisesSamAccountName": group.GetOnPremisesSamAccountName(), + "OnPremisesSecurityIdentifier": group.GetOnPremisesSecurityIdentifier(), + "OnPremisesSyncEnabled": group.GetOnPremisesSyncEnabled(), + "RenewedDateTime": group.GetRenewedDateTime(), + "SecurityEnabled": group.GetSecurityEnabled(), + "SecurityIdentifier": group.GetSecurityIdentifier(), + "Visibility": group.GetVisibility(), + "AssignedLabels": group.GetAssignedLabels(), + "GroupTypes": group.GetGroupTypes(), + "ProxyAddresses": group.GetProxyAddresses(), + } + + d.StreamListItem(ctx, result) + + return true + }) + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getAdGroupTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + + groupId := d.KeyColumnQuals["id"].GetStringValue() + if groupId == "" { + return nil, nil + } + + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + panic(fmt.Errorf("error creating credentials: %w", err)) + } + + // Check for query context and requests only for queried columns + givenColumns := d.QueryContext.Columns + selectColumns := buildGroupRequestFields(ctx, givenColumns) + + input := &item.GroupItemRequestBuilderGetQueryParameters{} + input.Select = selectColumns + + options := &item.GroupItemRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + group, err := client.GroupsById(groupId).GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + if isResourceNotFound(errObj) { + return nil, nil + } + + return nil, errors.New(fmt.Sprintf("failed to get group. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + result := map[string]interface{}{ + "DisplayName": group.GetDisplayName(), + "ID": group.GetId(), + "Description": group.GetDescription(), + "Classification": group.GetClassification(), + "CreatedDateTime": group.GetCreatedDateTime(), + "ExpirationDateTime": group.GetExpirationDateTime(), + "IsAssignableToRole": group.GetIsAssignableToRole(), + //"IsSubscribedByMail": group.GetIsSubscribedByMail(), + "Mail": group.GetMail(), + "MailEnabled": group.GetMailEnabled(), + "MailNickname": group.GetMailNickname(), + "MembershipRule": group.GetMembershipRule(), + "MembershipRuleProcessingState": group.GetMembershipRuleProcessingState(), + "OnPremisesDomainName": group.GetOnPremisesDomainName(), + "OnPremisesLastSyncDateTime": group.GetOnPremisesLastSyncDateTime(), + "OnPremisesNetBiosName": group.GetOnPremisesNetBiosName(), + "OnPremisesSamAccountName": group.GetOnPremisesSamAccountName(), + "OnPremisesSecurityIdentifier": group.GetOnPremisesSecurityIdentifier(), + "OnPremisesSyncEnabled": group.GetOnPremisesSyncEnabled(), + "RenewedDateTime": group.GetRenewedDateTime(), + "SecurityEnabled": group.GetSecurityEnabled(), + "SecurityIdentifier": group.GetSecurityIdentifier(), + "Visibility": group.GetVisibility(), + "AssignedLabels": group.GetAssignedLabels(), + "GroupTypes": group.GetGroupTypes(), + "ProxyAddresses": group.GetProxyAddresses(), + } + + return result, nil +} + +func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + panic(fmt.Errorf("error creating credentials: %w", err)) + } + + group := h.Item.(map[string]interface{}) + groupID := group["ID"].(*string) + + headers := map[string]string{ + "ConsistencyLevel": "eventual", + } + + includeCount := true + requestParameters := &members.MembersRequestBuilderGetQueryParameters{ + Count: &includeCount, + } + + config := &members.MembersRequestBuilderGetRequestConfiguration{ + Headers: headers, + QueryParameters: requestParameters, + } + + memberIds := []*string{} + members, err := client.GroupsById(*groupID).Members().GetWithRequestConfigurationAndResponseHandler(config, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list group members. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(members, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(func(pageItem interface{}) bool { + member := pageItem.(models.DirectoryObjectable) + memberIds = append(memberIds, member.GetId()) + + return true + }) + + return memberIds, nil +} + +func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + panic(fmt.Errorf("error creating credentials: %w", err)) + } + + group := h.Item.(map[string]interface{}) + groupID := group["ID"].(*string) + + headers := map[string]string{ + "ConsistencyLevel": "eventual", + } + + includeCount := true + requestParameters := &owners.OwnersRequestBuilderGetQueryParameters{ + Count: &includeCount, + } + + config := &owners.OwnersRequestBuilderGetRequestConfiguration{ + Headers: headers, + QueryParameters: requestParameters, + } + + ownerIds := []*string{} + owners, err := client.GroupsById(*groupID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list group owners. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(func(pageItem interface{}) bool { + member := pageItem.(models.DirectoryObjectable) + ownerIds = append(ownerIds, member.GetId()) + + return true + }) + + return ownerIds, nil +} + +//// TRANSFORM FUNCTIONS + +func adGroupTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { + group := d.HydrateItem.(map[string]interface{}) + assignedLabels := group["AssignedLabels"].([]models.AssignedLabelable) + + if assignedLabels == nil { + return nil, nil + } + var tags = map[string]string{} + for _, i := range assignedLabels { + tags[*i.GetLabelId()] = *i.GetDisplayName() + } + return tags, nil +} + +func buildGroupRequestFields(ctx context.Context, queryColumns []string) []string { + var selectColumns []string + + if !helpers.StringSliceContains(queryColumns, "id") { + queryColumns = append(queryColumns, "id") + } + + if !helpers.StringSliceContains(queryColumns, "assignedLabels") && helpers.StringSliceContains(queryColumns, "tags") { + queryColumns = append(queryColumns, "assignedLabels") + } + + for _, columnName := range queryColumns { + if columnName == "title" || columnName == "tags" || columnName == "filter" || columnName == "tenant_id" { + continue + } + + // Uses separate hydrate functions + if columnName == "member_ids" || columnName == "owner_ids" { + continue + } + + selectColumns = append(selectColumns, strcase.ToLowerCamel(columnName)) + } + + return selectColumns +} + +func buildGroupQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { + filters := []string{} + + filterQuals := map[string]string{ + "display_name": "string", + "mail": "string", + "mail_enabled": "bool", + "on_premises_sync_enabled": "bool", + "security_enabled": "bool", + } + + for qual, qualType := range filterQuals { + switch qualType { + case "string": + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) + } + case "bool": + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), equalQuals[qual].GetBoolValue())) + } + } + } + + return filters +} + +func buildGroupBoolNEFilter(quals plugin.KeyColumnQualMap) []string { + filters := []string{} + + filterQuals := []string{ + "mail_enabled", + "on_premises_sync_enabled", + "security_enabled", + } + + for _, qual := range filterQuals { + if quals[qual] != nil { + for _, q := range quals[qual].Quals { + value := q.Value.GetBoolValue() + if q.Operator == "<>" { + filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), !value)) + break + } + } + } + } + + return filters +} diff --git a/azuread/table_azuread_user1.go b/azuread/table_azuread_user1.go new file mode 100644 index 0000000..e8d21b8 --- /dev/null +++ b/azuread/table_azuread_user1.go @@ -0,0 +1,266 @@ +package azuread + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/iancoleman/strcase" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/users" + "github.com/microsoftgraph/msgraph-sdk-go/users/item" + + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" +) + +//// TABLE DEFINITION + +func tableAzureAdUserTest() *plugin.Table { + return &plugin.Table{ + Name: "azuread_user_test", + Description: "Represents an Azure AD user account.", + Get: &plugin.GetConfig{ + Hydrate: getAdUserTest, + KeyColumns: plugin.SingleColumn("id"), + }, + List: &plugin.ListConfig{ + Hydrate: listAdUsersTest, + KeyColumns: plugin.KeyColumnSlice{ + // Key fields + {Name: "user_principal_name", Require: plugin.Optional}, + {Name: "filter", Require: plugin.Optional}, + + // Other fields for filtering OData + {Name: "user_type", Require: plugin.Optional}, + {Name: "account_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, + {Name: "display_name", Require: plugin.Optional}, + {Name: "surname", Require: plugin.Optional}, + }, + }, + + Columns: []*plugin.Column{ + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name."}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the user. Should be treated as an opaque identifier.", Transform: transform.FromGo()}, + {Name: "user_principal_name", Type: proto.ColumnType_STRING, Description: "Principal email of the active directory user."}, + {Name: "account_enabled", Type: proto.ColumnType_BOOL, Description: "True if the account is enabled; otherwise, false."}, + {Name: "user_type", Type: proto.ColumnType_STRING, Description: "A string value that can be used to classify user types in your directory."}, + {Name: "given_name", Type: proto.ColumnType_STRING, Description: "The given name (first name) of the user."}, + {Name: "surname", Type: proto.ColumnType_STRING, Description: "Family name or last name of the active directory user."}, + + {Name: "filter", Type: proto.ColumnType_STRING, Transform: transform.FromQual("filter"), Description: "Odata query to search for resources."}, + + // // Other fields + {Name: "on_premises_immutable_id", Type: proto.ColumnType_STRING, Description: "Used to associate an on-premises Active Directory user account with their Azure AD user object."}, + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the user was created."}, + {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the user, for example, jeff@contoso.onmicrosoft.com."}, + {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user."}, + {Name: "password_policies", Type: proto.ColumnType_STRING, Description: "Specifies password policies for the user. This value is an enumeration with one possible value being DisableStrongPassword, which allows weaker passwords than the default policy to be specified. DisablePasswordExpiration can also be specified. The two may be specified together; for example: DisablePasswordExpiration, DisableStrongPassword."}, + // {Name: "refresh_tokens_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph)."}, + {Name: "sign_in_sessions_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph)."}, + {Name: "usage_location", Type: proto.ColumnType_STRING, Description: "A two letter country code (ISO standard 3166), required for users that will be assigned licenses due to legal requirement to check for availability of services in countries."}, + + // // Json fields + {Name: "member_of", Type: proto.ColumnType_JSON, Description: "A list the groups and directory roles that the user is a direct member of."}, + // {Name: "additional_properties", Type: proto.ColumnType_JSON, Description: "A list of unmatched properties from the message are deserialized this collection."}, + {Name: "im_addresses", Type: proto.ColumnType_JSON, Description: "The instant message voice over IP (VOIP) session initiation protocol (SIP) addresses for the user."}, + {Name: "other_mails", Type: proto.ColumnType_JSON, Description: "A list of additional email addresses for the user."}, + {Name: "password_profile", Type: proto.ColumnType_JSON, Description: "Specifies the password profile for the user. The profile contains the user’s password. This property is required when a user is created."}, + + // Standard columns + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "UserPrincipalName")}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, + }, + } +} + +//// LIST FUNCTION + +func listAdUsersTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + panic(fmt.Errorf("error creating credentials: %w", err)) + } + + // List operations + input := &users.UsersRequestBuilderGetQueryParameters{} + + // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow + limit := d.QueryContext.Limit + if limit != nil { + if *limit > 0 && *limit <= 999 { + l := int32(*limit) + input.Top = &l + } + } + + equalQuals := d.KeyColumnQuals + quals := d.Quals + + // Check for query context and requests only for queried columns + givenColumns := d.QueryContext.Columns + selectColumns, expandColumns := buildUserRequestFields(ctx, givenColumns) + + input.Select = selectColumns + input.Expand = expandColumns + + var queryFilter string + filter := buildQueryFilter(equalQuals) + filter = append(filter, buildBoolNEFilter(quals)...) + + if equalQuals["filter"] != nil { + queryFilter = equalQuals["filter"].GetStringValue() + } + + if queryFilter != "" { + input.Filter = &queryFilter + } else if len(filter) > 0 { + joinStr := strings.Join(filter, " and ") + input.Filter = &joinStr + } + + options := &users.UsersRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + result, err := client.Users().GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list users. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateUserCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + user := pageItem.(models.Userable) + + result := map[string]interface{}{ + "DisplayName": user.GetDisplayName(), + "ID": user.GetId(), + "UserPrincipalName": user.GetUserPrincipalName(), + "AccountEnabled": user.GetAccountEnabled(), + "UserType": user.GetUserType(), + "GivenName": user.GetGivenName(), + "Surname": user.GetSurname(), + "OnPremisesImmutableId": user.GetOnPremisesImmutableId(), + "CreatedDateTime": user.GetCreatedDateTime(), + "Mail": user.GetMail(), + "MailNickname": user.GetMailNickname(), + "PasswordPolicies": user.GetPasswordPolicies(), + "SignInSessionsValidFromDateTime": user.GetSignInSessionsValidFromDateTime(), + "UsageLocation": user.GetUsageLocation(), + "ImAddresses": user.GetImAddresses(), + "OtherMails": user.GetOtherMails(), + "PasswordProfile": user.GetPasswordProfile(), + } + + memberIds := []string{} + for _, i := range user.GetMemberOf() { + memberIds = append(memberIds, *i.GetId()) + } + result["MemberOf"] = memberIds + + d.StreamListItem(ctx, result) + + return true + }) + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getAdUserTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + panic(fmt.Errorf("error creating credentials: %w", err)) + } + + userId := d.KeyColumnQuals["id"].GetStringValue() + if userId == "" { + return nil, nil + } + + // Check for query context and requests only for queried columns + givenColumns := d.QueryContext.Columns + selectColumns, expandColumns := buildUserRequestFields(ctx, givenColumns) + + input := &item.UserItemRequestBuilderGetQueryParameters{} + input.Select = selectColumns + input.Expand = expandColumns + + options := &item.UserItemRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + user, err := client.UsersById(userId).GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + if isResourceNotFound(errObj) { + return nil, nil + } + + return nil, errors.New(fmt.Sprintf("failed to get user. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + result := map[string]interface{}{ + "DisplayName": user.GetDisplayName(), + "ID": user.GetId(), + "UserPrincipalName": user.GetUserPrincipalName(), + "AccountEnabled": user.GetAccountEnabled(), + "UserType": user.GetUserType(), + "GivenName": user.GetGivenName(), + "Surname": user.GetSurname(), + "OnPremisesImmutableId": user.GetOnPremisesImmutableId(), + "CreatedDateTime": user.GetCreatedDateTime(), + "Mail": user.GetMail(), + "MailNickname": user.GetMailNickname(), + "PasswordPolicies": user.GetPasswordPolicies(), + "SignInSessionsValidFromDateTime": user.GetSignInSessionsValidFromDateTime(), + "UsageLocation": user.GetUsageLocation(), + "ImAddresses": user.GetImAddresses(), + "OtherMails": user.GetOtherMails(), + "PasswordProfile": user.GetPasswordProfile(), + } + + memberIds := []string{} + for _, i := range user.GetMemberOf() { + memberIds = append(memberIds, *i.GetId()) + } + result["MemberOf"] = memberIds + + return result, nil +} + +func buildUserRequestFields(ctx context.Context, queryColumns []string) ([]string, []string) { + var selectColumns, expandColumns []string + + for _, columnName := range queryColumns { + if columnName == "title" || columnName == "filter" || columnName == "tenant_id" { + continue + } + + if columnName == "member_of" { + expandColumns = append(expandColumns, fmt.Sprintf("%s($select=id,displayName)", strcase.ToLowerCamel(columnName))) + continue + } + + selectColumns = append(selectColumns, strcase.ToLowerCamel(columnName)) + } + + return selectColumns, expandColumns +} + +func getTenant(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + tenantID, err := getTenantFromCLI() + if err != nil { + return nil, err + } + return tenantID, nil +} diff --git a/azuread/utils.go b/azuread/utils.go index 2d6221e..31ab8c5 100644 --- a/azuread/utils.go +++ b/azuread/utils.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" ) @@ -20,6 +21,32 @@ func isNotFoundError(err error) bool { return strings.Contains(err.Error(), "Request_ResourceNotFound") } +// New +type RequestError struct { + Code string + Message string +} + +func getErrorObject(err error) *RequestError { + if oDataError, ok := err.(*odataerrors.ODataError); ok { + if terr := oDataError.GetError(); terr != nil { + return &RequestError{ + Code: *terr.GetCode(), + Message: *terr.GetMessage(), + } + } + } + + return nil +} + +func isResourceNotFound(errObj *RequestError) bool { + if errObj != nil && errObj.Code == "Request_ResourceNotFound" { + return true + } + return false +} + func isNotFoundErrorPredicate(notFoundErrors []string) plugin.ErrorPredicate { return func(err error) bool { if err != nil { diff --git a/go.mod b/go.mod index f25466e..109bf05 100644 --- a/go.mod +++ b/go.mod @@ -3,25 +3,37 @@ module github.com/turbot/steampipe-plugin-azuread go 1.18 require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 github.com/ettle/strcase v0.1.1 + github.com/iancoleman/strcase v0.2.0 github.com/manicminer/hamilton v0.24.0 + github.com/microsoft/kiota-authentication-azure-go v0.3.1 + github.com/microsoftgraph/msgraph-sdk-go v0.30.0 + github.com/microsoftgraph/msgraph-sdk-go-core v0.26.2 github.com/turbot/go-kit v0.3.0 github.com/turbot/steampipe-plugin-sdk/v3 v3.1.0 ) require ( + github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/agext/levenshtein v1.2.1 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/btubbs/datetime v0.1.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cjlapao/common-go v0.0.20 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect - github.com/fatih/color v1.7.0 // indirect + github.com/fatih/color v1.13.0 // indirect github.com/gertd/go-pluralize v0.2.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect + github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/uuid v1.3.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v0.15.0 // indirect github.com/hashicorp/go-plugin v1.4.3 // indirect @@ -30,22 +42,30 @@ require ( github.com/hashicorp/go-version v1.4.0 // indirect github.com/hashicorp/hcl/v2 v2.11.1 // indirect github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect - github.com/iancoleman/strcase v0.2.0 // indirect - github.com/mattn/go-colorable v0.1.4 // indirect - github.com/mattn/go-isatty v0.0.10 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/microsoft/kiota-abstractions-go v0.8.1 // indirect + github.com/microsoft/kiota-http-go v0.5.2 // indirect + github.com/microsoft/kiota-serialization-json-go v0.5.5 // indirect + github.com/microsoft/kiota-serialization-text-go v0.4.1 // indirect github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/oklog/run v1.0.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sethvargo/go-retry v0.1.0 // indirect github.com/stevenle/topsort v0.0.0-20130922064739-8130c1d7596b // indirect + github.com/stretchr/testify v1.7.2 // indirect github.com/tkrajina/go-reflector v0.5.4 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/zclconf/go-cty v1.10.0 // indirect - golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect - golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect + golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect golang.org/x/text v0.3.7 // indirect @@ -53,5 +73,6 @@ require ( google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect google.golang.org/grpc v1.44.0 // indirect google.golang.org/protobuf v1.28.0 // indirect - gopkg.in/yaml.v2 v2.2.3 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 0f5d17f..1ce7609 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,14 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 h1:Ut0ZGdOwJDw0npYEg+TLlPls3Pq6JiZaP2/aGKir7Zw= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7KY7zH2mqygeUD0t8hNFXe08p1Pb3/jKE= +github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -52,6 +60,8 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cjlapao/common-go v0.0.20 h1:AIZBtuxwJPXFqyxFOlvVDWZgbsuTSquZEUw4KMx8pNg= +github.com/cjlapao/common-go v0.0.20/go.mod h1:EVLEXIxsBHblPMrhJYOvL4yBCcBj7IYDdW88VlfxpPM= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -67,6 +77,7 @@ github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/Lu github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -77,8 +88,9 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/gertd/go-pluralize v0.2.0 h1:VzWNnxkUo3wkW2Nmp+3ieHSTQQ0LBHeSVxlKsQPQ+UY= github.com/gertd/go-pluralize v0.2.0/go.mod h1:4ouO1Ndf/r7sZMorwp4Sbfw80lUni+sd+o3qJR8L9To= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -88,6 +100,9 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= +github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= @@ -140,7 +155,10 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -179,27 +197,49 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/manicminer/hamilton v0.24.0 h1:KUa8+NYP61esmb6QKGPy7t4zrxB0sujhWDRhiKpoScE= github.com/manicminer/hamilton v0.24.0/go.mod h1:QryxpD/4+cdKuXNi0UjLDvgxYdP0LLmYz7dYU7DAX4U= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/microsoft/kiota-abstractions-go v0.8.1 h1:ACCwRwddJYOx+SRqfgcR8Wo8PZTd4g+JMa8lY8ABy+4= +github.com/microsoft/kiota-abstractions-go v0.8.1/go.mod h1:05aCidCKhzer+yfhGeePaMUY3MH+wrAkQztBVEreTtc= +github.com/microsoft/kiota-authentication-azure-go v0.3.1 h1:jKZcFPCaNk4WSqS39mmnPvpXlBxc4DhX0zIQVYu2b4U= +github.com/microsoft/kiota-authentication-azure-go v0.3.1/go.mod h1:gUNv91OtbPfD0R7K7R4IjVo6yPXbcWltnCKvwCiAN7Y= +github.com/microsoft/kiota-http-go v0.5.2 h1:BS/bK2xHLT8TT+p0uZKxwu+lkXDAPByugYP2n1nV0Uo= +github.com/microsoft/kiota-http-go v0.5.2/go.mod h1:WqEFNw3rMEatymG4Xh3rLSTxaKq80rJdQ/CSSh7m6jI= +github.com/microsoft/kiota-serialization-json-go v0.5.5 h1:B0iKBKOdi+9NKFlormLRqduQ1+77MPGRsZ7xnd74EqQ= +github.com/microsoft/kiota-serialization-json-go v0.5.5/go.mod h1:GI9vrssO1EvqzDtvMKuhjALn40phZOWkeeaMgtCk6xE= +github.com/microsoft/kiota-serialization-text-go v0.4.1 h1:6QPH7+geUPCpaSZkKCQw0Scngx2IF0vKodrvvWWiu2A= +github.com/microsoft/kiota-serialization-text-go v0.4.1/go.mod h1:DsriFnVBDCc4D84qxG3j8q/1Sxu16JILfhxMZm3kdfw= +github.com/microsoftgraph/msgraph-sdk-go v0.30.0 h1:HsQHy5YBHQABxQOfzWovkZw7ee4zQBYytjB0lF7HGyM= +github.com/microsoftgraph/msgraph-sdk-go v0.30.0/go.mod h1:HsVKM/WvckKfF9CM1y5JNCfE9DmFKxz5/vJ3fHcZJkU= +github.com/microsoftgraph/msgraph-sdk-go-core v0.26.2 h1:rYNyzWdTM9W7ePbiUX3pO0rpBgGFo6CxoN0Qh3QqDI0= +github.com/microsoftgraph/msgraph-sdk-go-core v0.26.2/go.mod h1:kcTY0sEZ/LOJiSj/1OMxcs0T51uodJ/bOeVfWo4lo/s= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -219,8 +259,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/tkrajina/go-reflector v0.5.4 h1:dS9aJEa/eYNQU/fwsb5CSiATOxcNyA/gG/A7a582D5s= github.com/tkrajina/go-reflector v0.5.4/go.mod h1:9PyLgEOzc78ey/JmQQHbW8cQJ1oucLlNQsg8yFvkVk8= github.com/turbot/go-kit v0.3.0 h1:o4zZIO1ovdmJ2bHWOdXnnt8jJMIDGqYSkZvBREzFeMQ= @@ -230,6 +271,8 @@ github.com/turbot/steampipe-plugin-sdk/v3 v3.1.0/go.mod h1:F43pC9q0w4AxISFMr1O2O github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -250,8 +293,9 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 h1:Tgea0cVUD0ivh5ADBX4WwuI12DUd2to3nCYe2eayMIw= +golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -312,8 +356,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -345,6 +389,7 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -359,7 +404,10 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -513,10 +561,12 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 0d4c83f5f28adbf5e4554d6cf56906be932e1165 Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Tue, 19 Jul 2022 12:37:32 +0530 Subject: [PATCH 02/21] Update getTenant to read tenant from config and env variables --- azuread/table_azuread_user1.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/azuread/table_azuread_user1.go b/azuread/table_azuread_user1.go index e8d21b8..f22fbb2 100644 --- a/azuread/table_azuread_user1.go +++ b/azuread/table_azuread_user1.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "os" "strings" "github.com/iancoleman/strcase" @@ -258,9 +259,24 @@ func buildUserRequestFields(ctx context.Context, queryColumns []string) ([]strin } func getTenant(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - tenantID, err := getTenantFromCLI() - if err != nil { - return nil, err + var tenantID string + var err error + + // Read tenantID from config, or environment variables + azureADConfig := GetConfig(d.Connection) + if azureADConfig.TenantID != nil { + tenantID = *azureADConfig.TenantID + } else { + tenantID = os.Getenv("AZURE_TENANT_ID") } + + // If not set in config, get tenantID from CLI + if tenantID != "" { + tenantID, err = getTenantFromCLI() + if err != nil { + return nil, err + } + } + return tenantID, nil } From 6513f4c54a9a82bbdc8c0109bd123fdd14b9d3df Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Tue, 19 Jul 2022 12:41:15 +0530 Subject: [PATCH 03/21] Fix getTenant function logic --- azuread/table_azuread_user1.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azuread/table_azuread_user1.go b/azuread/table_azuread_user1.go index f22fbb2..972e00d 100644 --- a/azuread/table_azuread_user1.go +++ b/azuread/table_azuread_user1.go @@ -271,7 +271,7 @@ func getTenant(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) } // If not set in config, get tenantID from CLI - if tenantID != "" { + if tenantID == "" { tenantID, err = getTenantFromCLI() if err != nil { return nil, err From 48c05885e48b93e48823d9b079163b6c98d26cb5 Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Fri, 22 Jul 2022 21:22:17 +0530 Subject: [PATCH 04/21] Update client certificate authentication and migrate resources as per new SDK --- azuread/helpers.go | 537 ++++++++++++++++++++ azuread/plugin.go | 6 + azuread/service.go | 38 +- azuread/table_azuread_application1.go | 247 +++++++++ azuread/table_azuread_directory_role1.go | 162 ++++++ azuread/table_azuread_domain1.go | 131 +++++ azuread/table_azuread_group1.go | 162 +++--- azuread/table_azuread_identity_provider1.go | 143 ++++++ azuread/table_azuread_service_principal1.go | 285 +++++++++++ azuread/table_azuread_sign_in_report1.go | 139 +++++ azuread/table_azuread_user1.go | 120 ++--- 11 files changed, 1781 insertions(+), 189 deletions(-) create mode 100644 azuread/helpers.go create mode 100644 azuread/table_azuread_application1.go create mode 100644 azuread/table_azuread_directory_role1.go create mode 100644 azuread/table_azuread_domain1.go create mode 100644 azuread/table_azuread_identity_provider1.go create mode 100644 azuread/table_azuread_service_principal1.go create mode 100644 azuread/table_azuread_sign_in_report1.go diff --git a/azuread/helpers.go b/azuread/helpers.go new file mode 100644 index 0000000..ddbb62f --- /dev/null +++ b/azuread/helpers.go @@ -0,0 +1,537 @@ +package azuread + +import "github.com/microsoftgraph/msgraph-sdk-go/models" + +type ADApplicationInfo struct { + models.Applicationable +} + +type ADGroupInfo struct { + models.Groupable +} + +type ADServicePrincipalInfo struct { + models.ServicePrincipalable +} + +type ADSignInReportInfo struct { + models.SignInable +} + +type ADUserInfo struct { + models.Userable +} + +func (application *ADApplicationInfo) ApplicationAPI() map[string]interface{} { + if application.GetApi() == nil { + return nil + } + + apiData := map[string]interface{}{ + "knownClientApplications": application.GetApi().GetKnownClientApplications(), + } + + if application.GetApi().GetAcceptMappedClaims() != nil { + apiData["acceptMappedClaims"] = *application.GetApi().GetAcceptMappedClaims() + } + if application.GetApi().GetRequestedAccessTokenVersion() != nil { + apiData["requestedAccessTokenVersion"] = *application.GetApi().GetRequestedAccessTokenVersion() + } + + oauth2PermissionScopes := []map[string]interface{}{} + for _, p := range application.GetApi().GetOauth2PermissionScopes() { + data := map[string]interface{}{} + if p.GetAdminConsentDescription() != nil { + data["adminConsentDescription"] = *p.GetAdminConsentDescription() + } + if p.GetAdminConsentDisplayName() != nil { + data["adminConsentDisplayName"] = *p.GetAdminConsentDisplayName() + } + if p.GetId() != nil { + data["id"] = *p.GetId() + } + if p.GetIsEnabled() != nil { + data["isEnabled"] = *p.GetIsEnabled() + } + if p.GetOrigin() != nil { + data["origin"] = *p.GetOrigin() + } + if p.GetType() != nil { + data["type"] = *p.GetType() + } + if p.GetUserConsentDescription() != nil { + data["userConsentDescription"] = p.GetUserConsentDescription() + } + if p.GetUserConsentDisplayName() != nil { + data["userConsentDisplayName"] = p.GetUserConsentDisplayName() + } + if p.GetValue() != nil { + data["value"] = *p.GetValue() + } + oauth2PermissionScopes = append(oauth2PermissionScopes, data) + } + apiData["oauth2PermissionScopes"] = oauth2PermissionScopes + + preAuthorizedApplications := []map[string]interface{}{} + for _, p := range application.GetApi().GetPreAuthorizedApplications() { + data := map[string]interface{}{ + "delegatedPermissionIds": p.GetDelegatedPermissionIds(), + } + if p.GetAppId() != nil { + data["appId"] = *p.GetAppId() + } + preAuthorizedApplications = append(preAuthorizedApplications, data) + } + apiData["preAuthorizedApplications"] = preAuthorizedApplications + + return apiData +} + +func (application *ADApplicationInfo) ApplicationInfo() map[string]interface{} { + if application.GetInfo() == nil { + return nil + } + + return map[string]interface{}{ + "logoUrl": application.GetInfo().GetLogoUrl(), + "marketingUrl": application.GetInfo().GetMarketingUrl(), + "privacyStatementUrl": application.GetInfo().GetPrivacyStatementUrl(), + "supportUrl": application.GetInfo().GetSupportUrl(), + "termsOfServiceUrl": application.GetInfo().GetTermsOfServiceUrl(), + } +} + +func (application *ADApplicationInfo) ApplicationKeyCredentials() []map[string]interface{} { + if application.GetKeyCredentials() == nil { + return nil + } + + keyCredentials := []map[string]interface{}{} + for _, p := range application.GetKeyCredentials() { + keyCredentialData := map[string]interface{}{} + if p.GetDisplayName() != nil { + keyCredentialData["displayName"] = *p.GetDisplayName() + } + if p.GetEndDateTime() != nil { + keyCredentialData["endDateTime"] = *p.GetEndDateTime() + } + if p.GetKeyId() != nil { + keyCredentialData["keyId"] = *p.GetKeyId() + } + if p.GetStartDateTime() != nil { + keyCredentialData["startDateTime"] = *p.GetStartDateTime() + } + if p.GetType() != nil { + keyCredentialData["type"] = *p.GetType() + } + if p.GetUsage() != nil { + keyCredentialData["usage"] = *p.GetUsage() + } + if p.GetCustomKeyIdentifier() != nil { + keyCredentialData["customKeyIdentifier"] = p.GetCustomKeyIdentifier() + } + if p.GetKey() != nil { + keyCredentialData["key"] = p.GetKey() + } + keyCredentials = append(keyCredentials, keyCredentialData) + } + + return keyCredentials +} + +func (application *ADApplicationInfo) ApplicationParentalControlSettings() map[string]interface{} { + if application.GetParentalControlSettings() == nil { + return nil + } + + parentalControlSettingData := map[string]interface{}{ + "countriesBlockedForMinors": application.GetParentalControlSettings().GetCountriesBlockedForMinors(), + } + if application.GetParentalControlSettings().GetLegalAgeGroupRule() != nil { + parentalControlSettingData["legalAgeGroupRule"] = *application.GetParentalControlSettings().GetLegalAgeGroupRule() + } + + return parentalControlSettingData +} + +func (application *ADApplicationInfo) ApplicationPasswordCredentials() []map[string]interface{} { + if application.GetPasswordCredentials() == nil { + return nil + } + + passwordCredentials := []map[string]interface{}{} + for _, p := range application.GetPasswordCredentials() { + passwordCredentialData := map[string]interface{}{} + if p.GetDisplayName() != nil { + passwordCredentialData["displayName"] = *p.GetDisplayName() + } + if p.GetHint() != nil { + passwordCredentialData["hint"] = *p.GetHint() + } + if p.GetSecretText() != nil { + passwordCredentialData["secretText"] = *p.GetSecretText() + } + if p.GetKeyId() != nil { + passwordCredentialData["keyId"] = *p.GetKeyId() + } + if p.GetEndDateTime() != nil { + passwordCredentialData["endDateTime"] = *p.GetEndDateTime() + } + if p.GetStartDateTime() != nil { + passwordCredentialData["startDateTime"] = *p.GetStartDateTime() + } + if p.GetCustomKeyIdentifier() != nil { + passwordCredentialData["customKeyIdentifier"] = p.GetCustomKeyIdentifier() + } + passwordCredentials = append(passwordCredentials, passwordCredentialData) + } + + return passwordCredentials +} + +func (application *ADApplicationInfo) ApplicationSpa() map[string]interface{} { + if application.GetSpa() == nil { + return nil + } + + return map[string]interface{}{ + "redirectUris": application.GetSpa().GetRedirectUris(), + } +} + +func (application *ADApplicationInfo) ApplicationWeb() map[string]interface{} { + if application.GetWeb() == nil { + return nil + } + + webData := map[string]interface{}{} + if application.GetWeb().GetHomePageUrl() != nil { + webData["homePageUrl"] = *application.GetWeb().GetHomePageUrl() + } + if application.GetWeb().GetLogoutUrl() != nil { + webData["logoutUrl"] = *application.GetWeb().GetLogoutUrl() + } + if application.GetWeb().GetRedirectUris() != nil { + webData["redirectUris"] = application.GetWeb().GetRedirectUris() + } + if application.GetWeb().GetImplicitGrantSettings() != nil { + implicitGrantSettingsData := map[string]*bool{} + + if application.GetWeb().GetImplicitGrantSettings().GetEnableAccessTokenIssuance() != nil { + implicitGrantSettingsData["enableAccessTokenIssuance"] = application.GetWeb().GetImplicitGrantSettings().GetEnableAccessTokenIssuance() + } + if application.GetWeb().GetImplicitGrantSettings().GetEnableIdTokenIssuance() != nil { + implicitGrantSettingsData["enableIdTokenIssuance"] = application.GetWeb().GetImplicitGrantSettings().GetEnableIdTokenIssuance() + } + webData["implicitGrantSettings"] = implicitGrantSettingsData + } + + return webData +} + +func (group *ADGroupInfo) GroupAssignedLabels() []map[string]*string { + if group.GetAssignedLabels() == nil { + return nil + } + + assignedLabels := []map[string]*string{} + for _, i := range group.GetAssignedLabels() { + label := map[string]*string{ + "labelId": i.GetLabelId(), + "displayName": i.GetDisplayName(), + } + assignedLabels = append(assignedLabels, label) + } + return assignedLabels +} + +func (servicePrincipal *ADServicePrincipalInfo) ServicePrincipalAddIns() []map[string]interface{} { + if servicePrincipal.GetAddIns() == nil { + return nil + } + + addIns := []map[string]interface{}{} + for _, p := range servicePrincipal.GetAddIns() { + addInData := map[string]interface{}{} + if p.GetId() != nil { + addInData["id"] = *p.GetId() + } + if p.GetType() != nil { + addInData["type"] = *p.GetType() + } + + addInProperties := []map[string]interface{}{} + for _, k := range p.GetProperties() { + addInPropertyData := map[string]interface{}{} + if k.GetKey() != nil { + addInPropertyData["key"] = *k.GetKey() + } + if k.GetValue() != nil { + addInPropertyData["value"] = *k.GetValue() + } + addInProperties = append(addInProperties, addInPropertyData) + } + addInData["properties"] = addInProperties + + addIns = append(addIns, addInData) + } + return addIns +} + +func (servicePrincipal *ADServicePrincipalInfo) ServicePrincipalAppRoles() []map[string]interface{} { + if servicePrincipal.GetAppRoles() == nil { + return nil + } + + appRoles := []map[string]interface{}{} + for _, p := range servicePrincipal.GetAppRoles() { + appRoleData := map[string]interface{}{ + "allowedMemberTypes": p.GetAllowedMemberTypes(), + } + if p.GetDescription() != nil { + appRoleData["description"] = *p.GetDescription() + } + if p.GetDisplayName() != nil { + appRoleData["displayName"] = *p.GetDisplayName() + } + if p.GetId() != nil { + appRoleData["id"] = *p.GetId() + } + if p.GetIsEnabled() != nil { + appRoleData["isEnabled"] = *p.GetIsEnabled() + } + if p.GetOrigin() != nil { + appRoleData["origin"] = *p.GetOrigin() + } + if p.GetValue() != nil { + appRoleData["value"] = *p.GetValue() + } + appRoles = append(appRoles, appRoleData) + } + return appRoles +} + +func (servicePrincipal *ADServicePrincipalInfo) ServicePrincipalInfo() map[string]interface{} { + if servicePrincipal.GetInfo() == nil { + return nil + } + + return map[string]interface{}{ + "logoUrl": servicePrincipal.GetInfo().GetLogoUrl(), + "marketingUrl": servicePrincipal.GetInfo().GetMarketingUrl(), + "privacyStatementUrl": servicePrincipal.GetInfo().GetPrivacyStatementUrl(), + "supportUrl": servicePrincipal.GetInfo().GetSupportUrl(), + "termsOfServiceUrl": servicePrincipal.GetInfo().GetTermsOfServiceUrl(), + } +} + +func (servicePrincipal *ADServicePrincipalInfo) ServicePrincipalKeyCredentials() []map[string]interface{} { + if servicePrincipal.GetKeyCredentials() == nil { + return nil + } + + keyCredentials := []map[string]interface{}{} + for _, p := range servicePrincipal.GetKeyCredentials() { + keyCredentialData := map[string]interface{}{} + if p.GetDisplayName() != nil { + keyCredentialData["displayName"] = *p.GetDisplayName() + } + if p.GetEndDateTime() != nil { + keyCredentialData["endDateTime"] = *p.GetEndDateTime() + } + if p.GetKeyId() != nil { + keyCredentialData["keyId"] = *p.GetKeyId() + } + if p.GetStartDateTime() != nil { + keyCredentialData["startDateTime"] = *p.GetStartDateTime() + } + if p.GetType() != nil { + keyCredentialData["type"] = *p.GetType() + } + if p.GetUsage() != nil { + keyCredentialData["usage"] = *p.GetUsage() + } + if p.GetCustomKeyIdentifier() != nil { + keyCredentialData["customKeyIdentifier"] = p.GetCustomKeyIdentifier() + } + if p.GetKey() != nil { + keyCredentialData["key"] = p.GetKey() + } + keyCredentials = append(keyCredentials, keyCredentialData) + } + return keyCredentials +} + +func (servicePrincipal *ADServicePrincipalInfo) ServicePrincipalPasswordCredentials() []map[string]interface{} { + if servicePrincipal.GetPasswordCredentials() == nil { + return nil + } + + passwordCredentials := []map[string]interface{}{} + for _, p := range servicePrincipal.GetPasswordCredentials() { + passwordCredentialData := map[string]interface{}{} + if p.GetDisplayName() != nil { + passwordCredentialData["displayName"] = *p.GetDisplayName() + } + if p.GetHint() != nil { + passwordCredentialData["hint"] = *p.GetHint() + } + if p.GetSecretText() != nil { + passwordCredentialData["secretText"] = *p.GetSecretText() + } + if p.GetKeyId() != nil { + passwordCredentialData["keyId"] = *p.GetKeyId() + } + if p.GetEndDateTime() != nil { + passwordCredentialData["endDateTime"] = *p.GetEndDateTime() + } + if p.GetStartDateTime() != nil { + passwordCredentialData["startDateTime"] = *p.GetStartDateTime() + } + if p.GetCustomKeyIdentifier() != nil { + passwordCredentialData["customKeyIdentifier"] = p.GetCustomKeyIdentifier() + } + passwordCredentials = append(passwordCredentials, passwordCredentialData) + } + return passwordCredentials +} + +func (signIn *ADSignInReportInfo) SignInAppliedConditionalAccessPolicies() []map[string]interface{} { + if signIn.GetAppliedConditionalAccessPolicies() == nil { + return nil + } + + policies := []map[string]interface{}{} + for _, p := range signIn.GetAppliedConditionalAccessPolicies() { + policyData := map[string]interface{}{ + "enforcedGrantControls": p.GetEnforcedGrantControls(), + "enforcedSessionControls": p.GetEnforcedSessionControls(), + } + if p.GetDisplayName() != nil { + policyData["displayName"] = *p.GetDisplayName() + } + if p.GetId() != nil { + policyData["id"] = *p.GetId() + } + if p.GetResult() != nil { + policyData["result"] = p.GetResult() + } + policies = append(policies, policyData) + } + + return policies +} + +func (signIn *ADSignInReportInfo) SignInDeviceDetail() map[string]interface{} { + if signIn.GetDeviceDetail() == nil { + return nil + } + + deviceDetailInfo := map[string]interface{}{} + if signIn.GetDeviceDetail().GetBrowser() != nil { + deviceDetailInfo["browser"] = *signIn.GetDeviceDetail().GetBrowser() + } + if signIn.GetDeviceDetail().GetDeviceId() != nil { + deviceDetailInfo["deviceId"] = *signIn.GetDeviceDetail().GetDeviceId() + } + if signIn.GetDeviceDetail().GetDisplayName() != nil { + deviceDetailInfo["displayName"] = *signIn.GetDeviceDetail().GetDisplayName() + } + if signIn.GetDeviceDetail().GetIsCompliant() != nil { + deviceDetailInfo["isCompliant"] = *signIn.GetDeviceDetail().GetIsCompliant() + } + if signIn.GetDeviceDetail().GetIsManaged() != nil { + deviceDetailInfo["isManaged"] = *signIn.GetDeviceDetail().GetIsManaged() + } + if signIn.GetDeviceDetail().GetOperatingSystem() != nil { + deviceDetailInfo["operatingSystem"] = *signIn.GetDeviceDetail().GetOperatingSystem() + } + if signIn.GetDeviceDetail().GetTrustType() != nil { + deviceDetailInfo["trustType"] = *signIn.GetDeviceDetail().GetTrustType() + } + return deviceDetailInfo +} + +func (signIn *ADSignInReportInfo) SignInStatus() map[string]interface{} { + if signIn.GetStatus() == nil { + return nil + } + + statusInfo := map[string]interface{}{} + if signIn.GetStatus().GetErrorCode() != nil { + statusInfo["errorCode"] = *signIn.GetStatus().GetErrorCode() + } + if signIn.GetStatus().GetFailureReason() != nil { + statusInfo["failureReason"] = *signIn.GetStatus().GetFailureReason() + } + if signIn.GetStatus().GetAdditionalDetails() != nil { + statusInfo["additionalDetails"] = *signIn.GetStatus().GetAdditionalDetails() + } + return statusInfo +} + +func (signIn *ADSignInReportInfo) SignInLocation() map[string]interface{} { + if signIn.GetLocation() == nil { + return nil + } + + locationInfo := map[string]interface{}{} + if signIn.GetLocation().GetCity() != nil { + locationInfo["city"] = *signIn.GetLocation().GetCity() + } + if signIn.GetLocation().GetCountryOrRegion() != nil { + locationInfo["countryOrRegion"] = *signIn.GetLocation().GetCountryOrRegion() + } + if signIn.GetLocation().GetState() != nil { + locationInfo["state"] = *signIn.GetLocation().GetState() + } + if signIn.GetLocation().GetGeoCoordinates() != nil { + coordinateInfo := map[string]interface{}{} + if signIn.GetLocation().GetGeoCoordinates().GetAltitude() != nil { + coordinateInfo["altitude"] = *signIn.GetLocation().GetGeoCoordinates().GetAltitude() + } + if signIn.GetLocation().GetGeoCoordinates().GetLatitude() != nil { + coordinateInfo["latitude"] = *signIn.GetLocation().GetGeoCoordinates().GetLatitude() + } + if signIn.GetLocation().GetGeoCoordinates().GetLongitude() != nil { + coordinateInfo["longitude"] = *signIn.GetLocation().GetGeoCoordinates().GetLongitude() + } + locationInfo["geoCoordinates"] = coordinateInfo + } + return locationInfo +} + +func (user *ADUserInfo) UserMemberOf() []map[string]interface{} { + if user.GetMemberOf() == nil { + return nil + } + + members := []map[string]interface{}{} + for _, i := range user.GetMemberOf() { + member := map[string]interface{}{ + "@odata.type": i.GetType(), + "id": i.GetId(), + } + members = append(members, member) + } + return members +} + +func (user *ADUserInfo) UserPasswordProfile() map[string]interface{} { + if user.GetPasswordProfile() == nil { + return nil + } + + passwordProfileData := map[string]interface{}{} + if user.GetPasswordProfile().GetForceChangePasswordNextSignIn() != nil { + passwordProfileData["forceChangePasswordNextSignIn"] = *user.GetPasswordProfile().GetForceChangePasswordNextSignIn() + } + if user.GetPasswordProfile().GetForceChangePasswordNextSignInWithMfa() != nil { + passwordProfileData["forceChangePasswordNextSignInWithMfa"] = *user.GetPasswordProfile().GetForceChangePasswordNextSignInWithMfa() + } + if user.GetPasswordProfile().GetPassword() != nil { + passwordProfileData["password"] = *user.GetPasswordProfile().GetPassword() + } + + return passwordProfileData +} diff --git a/azuread/plugin.go b/azuread/plugin.go index 7811377..a145989 100644 --- a/azuread/plugin.go +++ b/azuread/plugin.go @@ -33,6 +33,12 @@ func Plugin(ctx context.Context) *plugin.Plugin { "azuread_user": tableAzureAdUser(), "azuread_user_test": tableAzureAdUserTest(), "azuread_group_test": tableAzureAdGroupTest(), + "azuread_domain_test": tableAzureAdDomainTest(), + "azuread_identity_provider_test": tableAzureAdIdentityProviderTest(), + "azuread_directory_role_test": tableAzureAdDirectoryRoleTest(), + "azuread_application_test": tableAzureAdApplicationTest(), + "azuread_service_principal_test": tableAzureAdServicePrincipalTest(), + "azuread_sign_in_report_test": tableAzureAdSignInReportTest(), }, } diff --git a/azuread/service.go b/azuread/service.go index 11f695e..e086359 100644 --- a/azuread/service.go +++ b/azuread/service.go @@ -3,11 +3,11 @@ package azuread import ( "bytes" "context" + "crypto" "crypto/x509" "encoding/json" - "encoding/pem" + "errors" "fmt" - "io/ioutil" "log" "os" "os/exec" @@ -243,8 +243,7 @@ func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.Gra logger := plugin.Logger(ctx) // Have we already created and cached the session? - // Hamilton SDK already acquires a new token when expired, so don't handle here again - sessionCacheKey := "GetNewSession" + sessionCacheKey := "GetGraphClient" if cachedData, ok := d.ConnectionManager.Cache.Get(sessionCacheKey); ok { return cachedData.(*msgraphsdkgo.GraphServiceClient), nil, nil } @@ -309,7 +308,7 @@ func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.Gra var cred azcore.TokenCredential var err error - if tenantID == "" { + if tenantID == "" { // CLI authentication cred, err = azidentity.NewAzureCLICredential( &azidentity.AzureCLICredentialOptions{}, ) @@ -317,7 +316,7 @@ func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.Gra logger.Error("GetGraphClient", "credential_error", err) return nil, nil, fmt.Errorf("error creating credentials: %w", err) } - } else if tenantID != "" && clientID != "" && clientSecret != "" { + } else if tenantID != "" && clientID != "" && clientSecret != "" { // Client secret authentication cred, err = azidentity.NewClientSecretCredential( tenantID, clientID, @@ -332,28 +331,37 @@ func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.Gra logger.Error("GetGraphClient", "credential_error", err) return nil, nil, fmt.Errorf("error creating credentials: %w", err) } - } else if tenantID != "" && clientID != "" && certificatePath != "" && certificatePassword != "" { // Need to validate - loadFile, err := ioutil.ReadFile(certificatePath) + } else if tenantID != "" && clientID != "" && certificatePath != "" { // Client certificate authentication + // Load certificate from given path + loadFile, err := os.ReadFile(certificatePath) if err != nil { return nil, nil, fmt.Errorf("error reading certificate from %s: %v", certificatePath, err) } - block, _ := pem.Decode(loadFile) - cert, err := x509.ParseCertificate(block.Bytes) + var certs []*x509.Certificate - certs = append(certs, cert) + var key crypto.PrivateKey + if certificatePassword == "" { + certs, key, err = azidentity.ParseCertificates(loadFile, nil) + } else { + certs, key, err = azidentity.ParseCertificates(loadFile, []byte(certificatePassword)) + } + + if err != nil { + return nil, nil, fmt.Errorf("error parsing certificate from %s: %v", certificatePath, err) + } cred, err = azidentity.NewClientCertificateCredential( tenantID, clientID, certs, - "", + key, &azidentity.ClientCertificateCredentialOptions{ ClientOptions: policy.ClientOptions{ Cloud: cloudConfiguration, }, }, ) - } else if enableMsi { + } else if enableMsi { // Managed identity authentication cred, err = azidentity.NewManagedIdentityCredential( &azidentity.ManagedIdentityCredentialOptions{}, ) @@ -361,12 +369,12 @@ func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.Gra auth, err := a.NewAzureIdentityAuthenticationProvider(cred) if err != nil { - panic(fmt.Errorf("error creating authentication provider: %w", err)) + return nil, nil, errors.New(fmt.Sprintf("error creating authentication provider: %v", err)) } adapter, err := msgraphsdkgo.NewGraphRequestAdapter(auth) if err != nil { - panic(fmt.Errorf("error creating graph adapter: %w", err)) + return nil, nil, errors.New(fmt.Sprintf("error creating graph adapter: %v", err)) } client := msgraphsdkgo.NewGraphServiceClient(adapter) diff --git a/azuread/table_azuread_application1.go b/azuread/table_azuread_application1.go new file mode 100644 index 0000000..49f9c53 --- /dev/null +++ b/azuread/table_azuread_application1.go @@ -0,0 +1,247 @@ +package azuread + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/iancoleman/strcase" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/applications" + "github.com/microsoftgraph/msgraph-sdk-go/applications/item/owners" + "github.com/microsoftgraph/msgraph-sdk-go/models" + + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" +) + +//// TABLE DEFINITION + +func tableAzureAdApplicationTest() *plugin.Table { + return &plugin.Table{ + Name: "azuread_application_test", + Description: "Represents an Azure Active Directory (Azure AD) application", + Get: &plugin.GetConfig{ + Hydrate: getAdApplicationTest, + KeyColumns: plugin.SingleColumn("id"), + }, + List: &plugin.ListConfig{ + Hydrate: listAdApplicationsTest, + //ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Request_UnsupportedQuery"}), + KeyColumns: plugin.KeyColumnSlice{ + // Key fields + {Name: "app_id", Require: plugin.Optional}, + {Name: "display_name", Require: plugin.Optional}, + {Name: "publisher_domain", Require: plugin.Optional}, + }, + }, + + Columns: []*plugin.Column{ + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the application.", Transform: transform.FromMethod("GetDisplayName")}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the application.", Transform: transform.FromMethod("GetId")}, + {Name: "app_id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the application that is assigned to an application by Azure AD.", Transform: transform.FromMethod("GetAppId")}, + + // Other fields + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The date and time the application was registered. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time.", Transform: transform.FromMethod("GetCreatedDateTime")}, + {Name: "description", Type: proto.ColumnType_STRING, Description: "Free text field to provide a description of the application object to end users.", Transform: transform.FromMethod("GetDescription")}, + // {Name: "is_authorization_service_enabled", Type: proto.ColumnType_BOOL, Description: "Is authorization service enabled."}, + {Name: "oauth2_require_post_response", Type: proto.ColumnType_BOOL, Description: "Specifies whether, as part of OAuth 2.0 token requests, Azure AD allows POST requests, as opposed to GET requests. The default is false, which specifies that only GET requests are allowed.", Transform: transform.FromMethod("GetOauth2RequirePostResponse")}, + {Name: "publisher_domain", Type: proto.ColumnType_STRING, Description: "The verified publisher domain for the application.", Transform: transform.FromMethod("GetPublisherDomain")}, + {Name: "sign_in_audience", Type: proto.ColumnType_STRING, Description: "Specifies the Microsoft accounts that are supported for the current application.", Transform: transform.FromMethod("GetSignInAudience")}, + + // Json fields + {Name: "api", Type: proto.ColumnType_JSON, Description: "Specifies settings for an application that implements a web API.", Transform: transform.FromMethod("ApplicationAPI")}, + {Name: "identifier_uris", Type: proto.ColumnType_JSON, Description: "The URIs that identify the application within its Azure AD tenant, or within a verified custom domain if the application is multi-tenant.", Transform: transform.FromMethod("GetIdentifierUris")}, + {Name: "info", Type: proto.ColumnType_JSON, Description: "Basic profile information of the application such as app's marketing, support, terms of service and privacy statement URLs. The terms of service and privacy statement are surfaced to users through the user consent experience.", Transform: transform.FromMethod("ApplicationInfo")}, + {Name: "key_credentials", Type: proto.ColumnType_JSON, Description: "The collection of key credentials associated with the application.", Transform: transform.FromMethod("ApplicationKeyCredentials")}, + {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getAdApplicationOwners, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, + {Name: "parental_control_settings", Type: proto.ColumnType_JSON, Description: "Specifies parental control settings for an application.", Transform: transform.FromMethod("ApplicationParentalControlSettings")}, + {Name: "password_credentials", Type: proto.ColumnType_JSON, Description: "The collection of password credentials associated with the application.", Transform: transform.FromMethod("ApplicationPasswordCredentials")}, + {Name: "spa", Type: proto.ColumnType_JSON, Description: "Specifies settings for a single-page application, including sign out URLs and redirect URIs for authorization codes and access tokens.", Transform: transform.FromMethod("ApplicationSpa")}, + {Name: "tags_src", Type: proto.ColumnType_JSON, Description: "Custom strings that can be used to categorize and identify the application.", Transform: transform.FromMethod("GetTags")}, + {Name: "web", Type: proto.ColumnType_JSON, Description: "Specifies settings for a web application.", Transform: transform.FromMethod("ApplicationWeb")}, + + // Standard columns + {Name: "tags", Type: proto.ColumnType_JSON, Description: ColumnDescriptionTags, Transform: transform.From(adApplicationTags)}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adApplicationTitle)}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, + }, + } +} + +//// LIST FUNCTION + +func listAdApplicationsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + // List operations + input := &applications.ApplicationsRequestBuilderGetQueryParameters{} + + // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow + // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. + limit := d.QueryContext.Limit + if limit != nil { + if *limit > 0 && *limit <= 999 { + l := int32(*limit) + input.Top = &l + } + } + + var queryFilter string + equalQuals := d.KeyColumnQuals + filter := buildApplicationQueryFilter(equalQuals) + + if equalQuals["filter"] != nil { + queryFilter = equalQuals["filter"].GetStringValue() + } + + if queryFilter != "" { + input.Filter = &queryFilter + } else if len(filter) > 0 { + joinStr := strings.Join(filter, " and ") + input.Filter = &joinStr + } + + options := &applications.ApplicationsRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + result, err := client.Applications().GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list applications. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateApplicationCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + application := pageItem.(models.Applicationable) + + d.StreamListItem(ctx, &ADApplicationInfo{application}) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.QueryStatus.RowsRemaining(ctx) == 0 { + return false + } + + return true + }) + + return nil, err +} + +//// HYDRATE FUNCTIONS + +func getAdApplicationTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + + applicationId := d.KeyColumnQuals["id"].GetStringValue() + if applicationId == "" { + return nil, nil + } + + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + application, err := client.ApplicationsById(applicationId).Get() + if err != nil { + errObj := getErrorObject(err) + if isResourceNotFound(errObj) { + return nil, nil + } + + return nil, errors.New(fmt.Sprintf("failed to get application. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + return &ADApplicationInfo{application}, nil +} + +func getAdApplicationOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + application := h.Item.(*ADApplicationInfo) + applicationID := application.GetId() + + headers := map[string]string{ + "ConsistencyLevel": "eventual", + } + + includeCount := true + requestParameters := &owners.OwnersRequestBuilderGetQueryParameters{ + Count: &includeCount, + } + + config := &owners.OwnersRequestBuilderGetRequestConfiguration{ + Headers: headers, + QueryParameters: requestParameters, + } + + ownerIds := []*string{} + owners, err := client.ApplicationsById(*applicationID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list application owners. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(func(pageItem interface{}) bool { + owner := pageItem.(models.DirectoryObjectable) + ownerIds = append(ownerIds, owner.GetId()) + + return true + }) + + return ownerIds, nil +} + +//// TRANSFORM FUNCTIONS + +func adApplicationTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { + application := d.HydrateItem.(*ADApplicationInfo) + tags := application.GetTags() + return TagsToMap(tags) +} + +func adApplicationTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADApplicationInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetId() + } + + return title, nil +} + +func buildApplicationQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { + filters := []string{} + + filterQuals := map[string]string{ + "display_name": "string", + "app_id": "string", + "publisher_domain": "string", + } + + for qual, _ := range filterQuals { + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) + } + } + + return filters +} diff --git a/azuread/table_azuread_directory_role1.go b/azuread/table_azuread_directory_role1.go new file mode 100644 index 0000000..df55325 --- /dev/null +++ b/azuread/table_azuread_directory_role1.go @@ -0,0 +1,162 @@ +package azuread + +import ( + "context" + "errors" + "fmt" + + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/directoryroles/item/members" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" + + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" +) + +//// TABLE DEFINITION + +func tableAzureAdDirectoryRoleTest() *plugin.Table { + return &plugin.Table{ + Name: "azuread_directory_role_test", + Description: "Represents an Azure Active Directory (Azure AD) directory role", + Get: &plugin.GetConfig{ + Hydrate: getAdDirectoryRoleTest, + KeyColumns: plugin.SingleColumn("id"), + }, + List: &plugin.ListConfig{ + Hydrate: listAdDirectoryRolesTest, + }, + + Columns: []*plugin.Column{ + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the directory role.", Transform: transform.FromMethod("GetId")}, + {Name: "description", Type: proto.ColumnType_STRING, Description: "The description for the directory role.", Transform: transform.FromMethod("GetDescription")}, + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the directory role.", Transform: transform.FromMethod("GetDisplayName")}, + + // Other fields + {Name: "role_template_id", Type: proto.ColumnType_STRING, Description: "The id of the directoryRoleTemplate that this role is based on. The property must be specified when activating a directory role in a tenant with a POST operation. After the directory role has been activated, the property is read only.", Transform: transform.FromMethod("GetRoleTemplateId")}, + + // Json fields + {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getDirectoryRoleMembers, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, + + // Standard columns + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adDirectoryRoleTitle)}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, + }, + } +} + +type ADDirectoryRoleInfo struct { + models.DirectoryRoleable +} + +//// LIST FUNCTION + +func listAdDirectoryRolesTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + result, err := client.DirectoryRoles().Get() + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list groups. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + for _, directoryRole := range result.GetValue() { + d.StreamListItem(ctx, &ADDirectoryRoleInfo{directoryRole}) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.QueryStatus.RowsRemaining(ctx) == 0 { + return nil, nil + } + } + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getAdDirectoryRoleTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + directoryRoleId := d.KeyColumnQuals["id"].GetStringValue() + if directoryRoleId == "" { + return nil, nil + } + + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + directoryRole, err := client.DirectoryRolesById(directoryRoleId).Get() + if err != nil { + errObj := getErrorObject(err) + if isResourceNotFound(errObj) { + return nil, nil + } + return nil, errors.New(fmt.Sprintf("failed to get directory role. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + return &ADDirectoryRoleInfo{directoryRole}, nil +} + +func getDirectoryRoleMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + directoryRole := h.Item.(*ADDirectoryRoleInfo) + directoryRoleID := directoryRole.GetId() + + headers := map[string]string{ + "ConsistencyLevel": "eventual", + } + + includeCount := true + requestParameters := &members.MembersRequestBuilderGetQueryParameters{ + Count: &includeCount, + } + + config := &members.MembersRequestBuilderGetRequestConfiguration{ + Headers: headers, + QueryParameters: requestParameters, + } + + memberIds := []*string{} + members, err := client.DirectoryRolesById(*directoryRoleID).Members().GetWithRequestConfigurationAndResponseHandler(config, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list directory role members. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(members, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(func(pageItem interface{}) bool { + member := pageItem.(models.DirectoryObjectable) + memberIds = append(memberIds, member.GetId()) + + return true + }) + + return memberIds, nil +} + +//// TRANSFORM FUNCTIONS + +func adDirectoryRoleTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADDirectoryRoleInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetId() + } + + return title, nil +} diff --git a/azuread/table_azuread_domain1.go b/azuread/table_azuread_domain1.go new file mode 100644 index 0000000..aa378e2 --- /dev/null +++ b/azuread/table_azuread_domain1.go @@ -0,0 +1,131 @@ +package azuread + +import ( + "context" + "errors" + "fmt" + + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" + + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/domains" + "github.com/microsoftgraph/msgraph-sdk-go/models" + + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" +) + +//// TABLE DEFINITION + +func tableAzureAdDomainTest() *plugin.Table { + return &plugin.Table{ + Name: "azuread_domain_test", + Description: "Represents an Azure Active Directory (Azure AD) domain", + Get: &plugin.GetConfig{ + Hydrate: getAdDomainTest, + KeyColumns: plugin.SingleColumn("id"), + }, + List: &plugin.ListConfig{ + Hydrate: listAdDomainsTest, + }, + + Columns: []*plugin.Column{ + {Name: "id", Type: proto.ColumnType_STRING, Description: "The fully qualified name of the domain.", Transform: transform.FromMethod("GetId")}, + {Name: "authentication_type", Type: proto.ColumnType_STRING, Description: "Indicates the configured authentication type for the domain. The value is either Managed or Federated. Managed indicates a cloud managed domain where Azure AD performs user authentication. Federated indicates authentication is federated with an identity provider such as the tenant's on-premises Active Directory via Active Directory Federation Services.", Transform: transform.FromMethod("GetAuthenticationType")}, + + // Other fields + {Name: "is_default", Type: proto.ColumnType_BOOL, Description: "true if this is the default domain that is used for user creation. There is only one default domain per company.", Transform: transform.FromMethod("GetIsDefault")}, + {Name: "is_admin_managed", Type: proto.ColumnType_BOOL, Description: "The value of the property is false if the DNS record management of the domain has been delegated to Microsoft 365. Otherwise, the value is true.", Transform: transform.FromMethod("GetIsAdminManaged")}, + {Name: "is_initial", Type: proto.ColumnType_BOOL, Description: "true if this is the initial domain created by Microsoft Online Services (companyname.onmicrosoft.com). There is only one initial domain per company.", Transform: transform.FromMethod("GetIsInitial")}, + {Name: "is_root", Type: proto.ColumnType_BOOL, Description: "true if the domain is a verified root domain. Otherwise, false if the domain is a subdomain or unverified.", Transform: transform.FromMethod("GetIsRoot")}, + {Name: "is_verified", Type: proto.ColumnType_BOOL, Description: "true if the domain has completed domain ownership verification.", Transform: transform.FromMethod("GetIsVerified")}, + + // Json fields + {Name: "supported_services", Type: proto.ColumnType_JSON, Description: "The capabilities assigned to the domain. Can include 0, 1 or more of following values: Email, Sharepoint, EmailInternalRelayOnly, OfficeCommunicationsOnline, SharePointDefaultDomain, FullRedelegation, SharePointPublic, OrgIdAuthentication, Yammer, Intune. The values which you can add/remove using Graph API include: Email, OfficeCommunicationsOnline, Yammer.", Transform: transform.FromMethod("GetSupportedServices")}, + + // Standard columns + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromMethod("GetId")}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, + }, + } +} + +type ADDomainInfo struct { + models.Domainable +} + +//// LIST FUNCTION + +func listAdDomainsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + // List operations + input := &domains.DomainsRequestBuilderGetQueryParameters{} + + // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow + // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. + limit := d.QueryContext.Limit + if limit != nil { + if *limit > 0 && *limit <= 999 { + l := int32(*limit) + input.Top = &l + } + } + + options := &domains.DomainsRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + result, err := client.Domains().GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list domains. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateDomainCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + domain := pageItem.(models.Domainable) + + d.StreamListItem(ctx, &ADDomainInfo{domain}) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.QueryStatus.RowsRemaining(ctx) == 0 { + return false + } + + return true + }) + + return nil, nil +} + +//// HYDRATE FUNCTIONS + +func getAdDomainTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + domainId := d.KeyColumnQuals["id"].GetStringValue() + if domainId == "" { + return nil, nil + } + + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + domain, err := client.DomainsById(domainId).Get() + if err != nil { + errObj := getErrorObject(err) + if isResourceNotFound(errObj) { + return nil, nil + } + return nil, errors.New(fmt.Sprintf("failed to get domain. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + return &ADDomainInfo{domain}, nil +} diff --git a/azuread/table_azuread_group1.go b/azuread/table_azuread_group1.go index cf36f8d..b4bf366 100644 --- a/azuread/table_azuread_group1.go +++ b/azuread/table_azuread_group1.go @@ -44,16 +44,16 @@ func tableAzureAdGroupTest() *plugin.Table { }, }, Columns: []*plugin.Column{ - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name."}, - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the group.", Transform: transform.FromGo()}, - {Name: "description", Type: proto.ColumnType_STRING, Description: "An optional description for the group."}, + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name.", Transform: transform.FromMethod("GetDisplayName")}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the group.", Transform: transform.FromMethod("GetId")}, + {Name: "description", Type: proto.ColumnType_STRING, Description: "An optional description for the group.", Transform: transform.FromMethod("GetDescription")}, {Name: "filter", Type: proto.ColumnType_STRING, Transform: transform.FromQual("filter"), Description: "Odata query to search for groups."}, // Other fields - {Name: "classification", Type: proto.ColumnType_STRING, Description: "Describes a classification for the group (such as low, medium or high business impact)."}, - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the group was created."}, - {Name: "expiration_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group is set to expire."}, - {Name: "is_assignable_to_role", Type: proto.ColumnType_BOOL, Description: "Indicates whether this group can be assigned to an Azure Active Directory role or not."}, + {Name: "classification", Type: proto.ColumnType_STRING, Description: "Describes a classification for the group (such as low, medium or high business impact).", Transform: transform.FromMethod("GetClassification")}, + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the group was created.", Transform: transform.FromMethod("GetCreatedDateTime")}, + {Name: "expiration_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group is set to expire.", Transform: transform.FromMethod("GetExpirationDateTime")}, + {Name: "is_assignable_to_role", Type: proto.ColumnType_BOOL, Description: "Indicates whether this group can be assigned to an Azure Active Directory role or not.", Transform: transform.FromMethod("GetIsAssignableToRole")}, // Getting below error while requesting value for isSubscribedByMail // { @@ -69,34 +69,34 @@ func tableAzureAdGroupTest() *plugin.Table { // } // {Name: "is_subscribed_by_mail", Type: proto.ColumnType_BOOL, Description: "Indicates whether the signed-in user is subscribed to receive email conversations. Default value is true."}, - {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the group, for example, \"serviceadmins@contoso.onmicrosoft.com\"."}, - {Name: "mail_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is mail-enabled."}, - {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user."}, - {Name: "membership_rule", Type: proto.ColumnType_STRING, Description: "The mail alias for the group, unique in the organization."}, - {Name: "membership_rule_processing_state", Type: proto.ColumnType_STRING, Description: "Indicates whether the dynamic membership processing is on or paused. Possible values are On or Paused."}, - {Name: "on_premises_domain_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises Domanin name synchronized from the on-premises directory."}, - {Name: "on_premises_last_sync_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Indicates the last time at which the group was synced with the on-premises directory."}, - {Name: "on_premises_net_bios_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises NetBiosName synchronized from the on-premises directory."}, - {Name: "on_premises_sam_account_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises SAM account name synchronized from the on-premises directory."}, - {Name: "on_premises_security_identifier", Type: proto.ColumnType_STRING, Description: "Contains the on-premises security identifier (SID) for the group that was synchronized from on-premises to the cloud."}, - {Name: "on_premises_sync_enabled", Type: proto.ColumnType_BOOL, Description: "True if this group is synced from an on-premises directory; false if this group was originally synced from an on-premises directory but is no longer synced; null if this object has never been synced from an on-premises directory (default)."}, - {Name: "renewed_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group was last renewed. This cannot be modified directly and is only updated via the renew service action."}, - {Name: "security_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is a security group."}, - {Name: "security_identifier", Type: proto.ColumnType_STRING, Description: "Security identifier of the group, used in Windows scenarios."}, - {Name: "visibility", Type: proto.ColumnType_STRING, Description: "Specifies the group join policy and group content visibility for groups. Possible values are: Private, Public, or Hiddenmembership."}, + {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the group, for example, \"serviceadmins@contoso.onmicrosoft.com\".", Transform: transform.FromMethod("GetMail")}, + {Name: "mail_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is mail-enabled.", Transform: transform.FromMethod("GetMailEnabled")}, + {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user.", Transform: transform.FromMethod("GetMailNickname")}, + {Name: "membership_rule", Type: proto.ColumnType_STRING, Description: "The mail alias for the group, unique in the organization.", Transform: transform.FromMethod("GetMembershipRule")}, + {Name: "membership_rule_processing_state", Type: proto.ColumnType_STRING, Description: "Indicates whether the dynamic membership processing is on or paused. Possible values are On or Paused.", Transform: transform.FromMethod("GetMembershipRuleProcessingState")}, + {Name: "on_premises_domain_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises Domanin name synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesDomainName")}, + {Name: "on_premises_last_sync_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Indicates the last time at which the group was synced with the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesLastSyncDateTime")}, + {Name: "on_premises_net_bios_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises NetBiosName synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesNetBiosName")}, + {Name: "on_premises_sam_account_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises SAM account name synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesSamAccountName")}, + {Name: "on_premises_security_identifier", Type: proto.ColumnType_STRING, Description: "Contains the on-premises security identifier (SID) for the group that was synchronized from on-premises to the cloud.", Transform: transform.FromMethod("GetOnPremisesSecurityIdentifier")}, + {Name: "on_premises_sync_enabled", Type: proto.ColumnType_BOOL, Description: "True if this group is synced from an on-premises directory; false if this group was originally synced from an on-premises directory but is no longer synced; null if this object has never been synced from an on-premises directory (default).", Transform: transform.FromMethod("GetOnPremisesSyncEnabled")}, + {Name: "renewed_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group was last renewed. This cannot be modified directly and is only updated via the renew service action.", Transform: transform.FromMethod("GetRenewedDateTime")}, + {Name: "security_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is a security group.", Transform: transform.FromMethod("GetSecurityEnabled")}, + {Name: "security_identifier", Type: proto.ColumnType_STRING, Description: "Security identifier of the group, used in Windows scenarios.", Transform: transform.FromMethod("GetSecurityIdentifier")}, + {Name: "visibility", Type: proto.ColumnType_STRING, Description: "Specifies the group join policy and group content visibility for groups. Possible values are: Private, Public, or Hiddenmembership.", Transform: transform.FromMethod("GetVisibility")}, // Json fields - {Name: "assigned_labels", Type: proto.ColumnType_JSON, Description: "The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group."}, - {Name: "group_types", Type: proto.ColumnType_JSON, Description: "Specifies the group type and its membership. If the collection contains Unified, the group is a Microsoft 365 group; otherwise, it's either a security group or distribution group. For details, see [groups overview](https://docs.microsoft.com/en-us/graph/api/resources/groups-overview?view=graph-rest-1.0)."}, + {Name: "assigned_labels", Type: proto.ColumnType_JSON, Description: "The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group.", Transform: transform.FromMethod("GroupAssignedLabels")}, + {Name: "group_types", Type: proto.ColumnType_JSON, Description: "Specifies the group type and its membership. If the collection contains Unified, the group is a Microsoft 365 group; otherwise, it's either a security group or distribution group. For details, see [groups overview](https://docs.microsoft.com/en-us/graph/api/resources/groups-overview?view=graph-rest-1.0).", Transform: transform.FromMethod("GetGroupTypes")}, {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupMembers, Transform: transform.FromValue(), Description: "Id of Users and groups that are members of this group."}, {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupOwners, Transform: transform.FromValue(), Description: "Id od the owners of the group. The owners are a set of non-admin users who are allowed to modify this object."}, - {Name: "proxy_addresses", Type: proto.ColumnType_JSON, Description: "Email addresses for the group that direct to the same group mailbox. For example: [\"SMTP: bob@contoso.com\", \"smtp: bob@sales.contoso.com\"]. The any operator is required to filter expressions on multi-valued properties."}, + {Name: "proxy_addresses", Type: proto.ColumnType_JSON, Description: "Email addresses for the group that direct to the same group mailbox. For example: [\"SMTP: bob@contoso.com\", \"smtp: bob@sales.contoso.com\"]. The any operator is required to filter expressions on multi-valued properties.", Transform: transform.FromMethod("GetProxyAddresses")}, // {Name: "resource_behavior_options", Type: proto.ColumnType_JSON, Description: "Specifies the group behaviors that can be set for a Microsoft 365 group during creation. Possible values are AllowOnlyMembersToPost, HideGroupInOutlook, SubscribeNewGroupMembers, WelcomeEmailDisabled."}, // {Name: "resource_provisioning_options", Type: proto.ColumnType_JSON, Description: "Specifies the group resources that are provisioned as part of Microsoft 365 group creation, that are not normally part of default group creation. Possible value is Team."}, // Standard columns {Name: "tags", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTags, Transform: transform.From(adGroupTags)}, - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "ID")}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adGroupTitle)}, {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } @@ -108,7 +108,7 @@ func listAdGroupsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydrat // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - panic(fmt.Errorf("error creating credentials: %w", err)) + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } // List operations @@ -162,36 +162,12 @@ func listAdGroupsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydrat err = pageIterator.Iterate(func(pageItem interface{}) bool { group := pageItem.(models.Groupable) - result := map[string]interface{}{ - "DisplayName": group.GetDisplayName(), - "ID": group.GetId(), - "Description": group.GetDescription(), - "Classification": group.GetClassification(), - "CreatedDateTime": group.GetCreatedDateTime(), - "ExpirationDateTime": group.GetExpirationDateTime(), - "IsAssignableToRole": group.GetIsAssignableToRole(), - //"IsSubscribedByMail": group.GetIsSubscribedByMail(), - "Mail": group.GetMail(), - "MailEnabled": group.GetMailEnabled(), - "MailNickname": group.GetMailNickname(), - "MembershipRule": group.GetMembershipRule(), - "MembershipRuleProcessingState": group.GetMembershipRuleProcessingState(), - "OnPremisesDomainName": group.GetOnPremisesDomainName(), - "OnPremisesLastSyncDateTime": group.GetOnPremisesLastSyncDateTime(), - "OnPremisesNetBiosName": group.GetOnPremisesNetBiosName(), - "OnPremisesSamAccountName": group.GetOnPremisesSamAccountName(), - "OnPremisesSecurityIdentifier": group.GetOnPremisesSecurityIdentifier(), - "OnPremisesSyncEnabled": group.GetOnPremisesSyncEnabled(), - "RenewedDateTime": group.GetRenewedDateTime(), - "SecurityEnabled": group.GetSecurityEnabled(), - "SecurityIdentifier": group.GetSecurityIdentifier(), - "Visibility": group.GetVisibility(), - "AssignedLabels": group.GetAssignedLabels(), - "GroupTypes": group.GetGroupTypes(), - "ProxyAddresses": group.GetProxyAddresses(), - } + d.StreamListItem(ctx, &ADGroupInfo{group}) - d.StreamListItem(ctx, result) + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.QueryStatus.RowsRemaining(ctx) == 0 { + return false + } return true }) @@ -211,7 +187,7 @@ func getAdGroupTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateD // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - panic(fmt.Errorf("error creating credentials: %w", err)) + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } // Check for query context and requests only for queried columns @@ -235,47 +211,18 @@ func getAdGroupTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateD return nil, errors.New(fmt.Sprintf("failed to get group. Code: %s Message: %s", errObj.Code, errObj.Message)) } - result := map[string]interface{}{ - "DisplayName": group.GetDisplayName(), - "ID": group.GetId(), - "Description": group.GetDescription(), - "Classification": group.GetClassification(), - "CreatedDateTime": group.GetCreatedDateTime(), - "ExpirationDateTime": group.GetExpirationDateTime(), - "IsAssignableToRole": group.GetIsAssignableToRole(), - //"IsSubscribedByMail": group.GetIsSubscribedByMail(), - "Mail": group.GetMail(), - "MailEnabled": group.GetMailEnabled(), - "MailNickname": group.GetMailNickname(), - "MembershipRule": group.GetMembershipRule(), - "MembershipRuleProcessingState": group.GetMembershipRuleProcessingState(), - "OnPremisesDomainName": group.GetOnPremisesDomainName(), - "OnPremisesLastSyncDateTime": group.GetOnPremisesLastSyncDateTime(), - "OnPremisesNetBiosName": group.GetOnPremisesNetBiosName(), - "OnPremisesSamAccountName": group.GetOnPremisesSamAccountName(), - "OnPremisesSecurityIdentifier": group.GetOnPremisesSecurityIdentifier(), - "OnPremisesSyncEnabled": group.GetOnPremisesSyncEnabled(), - "RenewedDateTime": group.GetRenewedDateTime(), - "SecurityEnabled": group.GetSecurityEnabled(), - "SecurityIdentifier": group.GetSecurityIdentifier(), - "Visibility": group.GetVisibility(), - "AssignedLabels": group.GetAssignedLabels(), - "GroupTypes": group.GetGroupTypes(), - "ProxyAddresses": group.GetProxyAddresses(), - } - - return result, nil + return &ADGroupInfo{group}, nil } func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - panic(fmt.Errorf("error creating credentials: %w", err)) + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - group := h.Item.(map[string]interface{}) - groupID := group["ID"].(*string) + group := h.Item.(*ADGroupInfo) + groupID := group.GetId() headers := map[string]string{ "ConsistencyLevel": "eventual", @@ -313,11 +260,11 @@ func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - panic(fmt.Errorf("error creating credentials: %w", err)) + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - group := h.Item.(map[string]interface{}) - groupID := group["ID"].(*string) + group := h.Item.(*ADGroupInfo) + groupID := group.GetId() headers := map[string]string{ "ConsistencyLevel": "eventual", @@ -354,19 +301,38 @@ func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat //// TRANSFORM FUNCTIONS func adGroupTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { - group := d.HydrateItem.(map[string]interface{}) - assignedLabels := group["AssignedLabels"].([]models.AssignedLabelable) + group := d.HydrateItem.(*ADGroupInfo) + if group == nil { + return nil, nil + } - if assignedLabels == nil { + assignedLabels := group.GroupAssignedLabels() + if assignedLabels == nil || len(assignedLabels) == 0 { return nil, nil } - var tags = map[string]string{} + + var tags = map[*string]*string{} for _, i := range assignedLabels { - tags[*i.GetLabelId()] = *i.GetDisplayName() + tags[i["labelId"]] = i["displayName"] } + return tags, nil } +func adGroupTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADGroupInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetId() + } + + return title, nil +} + func buildGroupRequestFields(ctx context.Context, queryColumns []string) []string { var selectColumns []string diff --git a/azuread/table_azuread_identity_provider1.go b/azuread/table_azuread_identity_provider1.go new file mode 100644 index 0000000..80eeb25 --- /dev/null +++ b/azuread/table_azuread_identity_provider1.go @@ -0,0 +1,143 @@ +package azuread + +import ( + "context" + "errors" + "fmt" + + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" + + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/identity/identityproviders" + "github.com/microsoftgraph/msgraph-sdk-go/models" + + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" +) + +//// TABLE DEFINITION + +func tableAzureAdIdentityProviderTest() *plugin.Table { + return &plugin.Table{ + Name: "azuread_identity_provider_test", + Description: "Represents an Azure Active Directory (Azure AD) identity provider", + Get: &plugin.GetConfig{ + Hydrate: getAdIdentityProviderTest, + ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), + KeyColumns: plugin.SingleColumn("id"), + }, + List: &plugin.ListConfig{ + Hydrate: listAdIdentityProvidersTest, + }, + + Columns: []*plugin.Column{ + {Name: "id", Type: proto.ColumnType_STRING, Description: "The ID of the identity provider.", Transform: transform.FromGo()}, + {Name: "name", Type: proto.ColumnType_STRING, Description: "The display name of the identity provider."}, + + // Other fields + {Name: "type", Type: proto.ColumnType_STRING, Description: "The identity provider type is a required field. For B2B scenario: Google, Facebook. For B2C scenario: Microsoft, Google, Amazon, LinkedIn, Facebook, GitHub, Twitter, Weibo, QQ, WeChat, OpenIDConnect."}, + // {Name: "client_id", Type: proto.ColumnType_STRING, Description: "The client ID for the application. This is the client ID obtained when registering the application with the identity provider."}, + // {Name: "client_secret", Type: proto.ColumnType_STRING, Description: "The client secret for the application. This is the client secret obtained when registering the application with the identity provider. This is write-only. A read operation will return ****."}, + + // Standard columns + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "ID")}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, + }, + } +} + +//// LIST FUNCTION + +func listAdIdentityProvidersTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + // List operations + input := &identityproviders.IdentityProvidersRequestBuilderGetQueryParameters{} + + limit := d.QueryContext.Limit + if limit != nil { + l := int32(*limit) + input.Top = &l + } + + options := &identityproviders.IdentityProvidersRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + result, err := client.Identity().IdentityProviders().GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list identity providers. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateIdentityProviderCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + identityProvider := pageItem.(models.IdentityProviderBaseable) + + result := map[string]interface{}{ + "ID": identityProvider.GetId(), + "Name": identityProvider.GetDisplayName(), + "Type": identityProvider.GetType(), + // "ClientId": identityProvider.GetClientId(), + // "ClientSecret": identityProvider.GetClientSecret(), + } + + d.StreamListItem(ctx, result) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.QueryStatus.RowsRemaining(ctx) == 0 { + return false + } + + return true + }) + + return nil, nil +} + +//// Hydrate Functions + +// Need to validate. +// Getting following error +// Code: AADB2C90063 Message: There is a problem with the service. +func getAdIdentityProviderTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + identityProviderId := d.KeyColumnQuals["id"].GetStringValue() + if identityProviderId == "" { + return nil, nil + } + + if identityProviderId == "" { + return nil, nil + } + + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + identityProvider, err := client.Identity().IdentityProvidersById(identityProviderId).Get() + if err != nil { + errObj := getErrorObject(err) + if isResourceNotFound(errObj) { + return nil, nil + } + + return nil, errors.New(fmt.Sprintf("failed to get identity provider. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + result := map[string]interface{}{ + "ID": identityProvider.GetId(), + "Name": identityProvider.GetDisplayName(), + "Type": identityProvider.GetType(), + // "ClientId": identityProvider.GetClientId(), + // "ClientSecret": identityProvider.GetClientSecret(), + } + + return result, nil +} diff --git a/azuread/table_azuread_service_principal1.go b/azuread/table_azuread_service_principal1.go new file mode 100644 index 0000000..a3eeb45 --- /dev/null +++ b/azuread/table_azuread_service_principal1.go @@ -0,0 +1,285 @@ +package azuread + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/iancoleman/strcase" + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" + + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/serviceprincipals" + "github.com/microsoftgraph/msgraph-sdk-go/serviceprincipals/item/owners" +) + +//// TABLE DEFINITION + +func tableAzureAdServicePrincipalTest() *plugin.Table { + return &plugin.Table{ + Name: "azuread_service_principal_test", + Description: "Represents an Azure Active Directory (Azure AD) service principal", + Get: &plugin.GetConfig{ + Hydrate: getAdServicePrincipalTest, + KeyColumns: plugin.SingleColumn("id"), + }, + List: &plugin.ListConfig{ + Hydrate: listAdServicePrincipalsTest, + //ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Request_UnsupportedQuery"}), + KeyColumns: plugin.KeyColumnSlice{ + // Key fields + {Name: "display_name", Require: plugin.Optional}, + {Name: "account_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, + {Name: "service_principal_type", Require: plugin.Optional}, + }, + }, + + Columns: []*plugin.Column{ + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the service principal.", Transform: transform.FromMethod("GetId")}, + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the service principal.", Transform: transform.FromMethod("GetDisplayName")}, + {Name: "app_id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the associated application (its appId property).", Transform: transform.FromMethod("GetAppId")}, + + // Other fields + {Name: "account_enabled", Type: proto.ColumnType_BOOL, Description: "true if the service principal account is enabled; otherwise, false.", Transform: transform.FromMethod("GetAccountEnabled")}, + {Name: "app_display_name", Type: proto.ColumnType_STRING, Description: "The display name exposed by the associated application.", Transform: transform.FromMethod("GetAppDisplayName")}, + {Name: "app_owner_organization_id", Type: proto.ColumnType_STRING, Description: "Contains the tenant id where the application is registered. This is applicable only to service principals backed by applications.", Transform: transform.FromMethod("GetAppOwnerOrganizationId")}, + {Name: "app_role_assignment_required", Type: proto.ColumnType_BOOL, Description: "Specifies whether users or other service principals need to be granted an app role assignment for this service principal before users can sign in or apps can get tokens. The default value is false.", Transform: transform.FromMethod("GetAppRoleAssignmentRequired")}, + {Name: "service_principal_type", Type: proto.ColumnType_STRING, Description: "Identifies whether the service principal represents an application, a managed identity, or a legacy application. This is set by Azure AD internally.", Transform: transform.FromMethod("GetServicePrincipalType")}, + {Name: "sign_in_audience", Type: proto.ColumnType_STRING, Description: "Specifies the Microsoft accounts that are supported for the current application. Supported values are: AzureADMyOrg, AzureADMultipleOrgs, AzureADandPersonalMicrosoftAccount, PersonalMicrosoftAccount.", Transform: transform.FromMethod("GetSignInAudience")}, + {Name: "app_description", Type: proto.ColumnType_STRING, Description: "The description exposed by the associated application.", Transform: transform.FromMethod("GetAppDescription")}, + {Name: "description", Type: proto.ColumnType_STRING, Description: "Free text field to provide an internal end-user facing description of the service principal.", Transform: transform.FromMethod("GetDescription")}, + {Name: "login_url", Type: proto.ColumnType_STRING, Description: "Specifies the URL where the service provider redirects the user to Azure AD to authenticate. Azure AD uses the URL to launch the application from Microsoft 365 or the Azure AD My Apps. When blank, Azure AD performs IdP-initiated sign-on for applications configured with SAML-based single sign-on.", Transform: transform.FromMethod("GetLoginUrl")}, + {Name: "logout_url", Type: proto.ColumnType_STRING, Description: "Specifies the URL that will be used by Microsoft's authorization service to logout an user using OpenId Connect front-channel, back-channel or SAML logout protocols.", Transform: transform.FromMethod("GetLogoutUrl")}, + + // Json fields + {Name: "add_ins", Type: proto.ColumnType_JSON, Description: "Defines custom behavior that a consuming service can use to call an app in specific contexts.", Transform: transform.FromMethod("ServicePrincipalAddIns")}, + {Name: "alternative_names", Type: proto.ColumnType_JSON, Description: "Used to retrieve service principals by subscription, identify resource group and full resource ids for managed identities.", Transform: transform.FromMethod("GetAlternativeNames")}, + {Name: "app_roles", Type: proto.ColumnType_JSON, Description: "The roles exposed by the application which this service principal represents.", Transform: transform.FromMethod("ServicePrincipalAppRoles")}, + {Name: "info", Type: proto.ColumnType_JSON, Description: "Basic profile information of the acquired application such as app's marketing, support, terms of service and privacy statement URLs.", Transform: transform.FromMethod("ServicePrincipalInfo")}, + {Name: "key_credentials", Type: proto.ColumnType_JSON, Description: "The collection of key credentials associated with the service principal.", Transform: transform.FromMethod("ServicePrincipalKeyCredentials")}, + {Name: "notification_email_addresses", Type: proto.ColumnType_JSON, Description: "Specifies the list of email addresses where Azure AD sends a notification when the active certificate is near the expiration date. This is only for the certificates used to sign the SAML token issued for Azure AD Gallery applications.", Transform: transform.FromMethod("GetNotificationEmailAddresses")}, + {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getServicePrincipalOwners, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, + {Name: "password_credentials", Type: proto.ColumnType_JSON, Description: "Represents a password credential associated with a service principal.", Transform: transform.FromMethod("ServicePrincipalPasswordCredentials")}, + // {Name: "published_permission_scopes", Type: proto.ColumnType_JSON, Description: "The published permission scopes."}, + {Name: "reply_urls", Type: proto.ColumnType_JSON, Description: "The URLs that user tokens are sent to for sign in with the associated application, or the redirect URIs that OAuth 2.0 authorization codes and access tokens are sent to for the associated application.", Transform: transform.FromMethod("GetReplyUrls")}, + {Name: "service_principal_names", Type: proto.ColumnType_JSON, Description: "Contains the list of identifiersUris, copied over from the associated application. Additional values can be added to hybrid applications. These values can be used to identify the permissions exposed by this app within Azure AD.", Transform: transform.FromMethod("GetServicePrincipalNames")}, + {Name: "tags_src", Type: proto.ColumnType_JSON, Description: "Custom strings that can be used to categorize and identify the service principal.", Transform: transform.FromMethod("GetTags")}, + // {Name: "verified_publisher", Type: proto.ColumnType_JSON, Description: "Specifies the verified publisher of the application which this service principal represents."}, + + // Standard columns + {Name: "tags", Type: proto.ColumnType_JSON, Description: ColumnDescriptionTags, Transform: transform.From(adServicePrincipalTags)}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adServicePrincipalTitle)}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, + }, + } +} + +//// LIST FUNCTION + +func listAdServicePrincipalsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + // List operations + input := &serviceprincipals.ServicePrincipalsRequestBuilderGetQueryParameters{} + + // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow + // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. + limit := d.QueryContext.Limit + if limit != nil { + if *limit > 0 && *limit <= 999 { + l := int32(*limit) + input.Top = &l + } + } + + var queryFilter string + equalQuals := d.KeyColumnQuals + quals := d.Quals + filter := buildServicePrincipalQueryFilter(equalQuals) + filter = append(filter, buildServicePrincipalBoolNEFilter(quals)...) + + if equalQuals["filter"] != nil { + queryFilter = equalQuals["filter"].GetStringValue() + } + + if queryFilter != "" { + input.Filter = &queryFilter + } else if len(filter) > 0 { + joinStr := strings.Join(filter, " and ") + input.Filter = &joinStr + } + + options := &serviceprincipals.ServicePrincipalsRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + result, err := client.ServicePrincipals().GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list service principals. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateServicePrincipalCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + servicePrincipal := pageItem.(models.ServicePrincipalable) + + d.StreamListItem(ctx, &ADServicePrincipalInfo{servicePrincipal}) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.QueryStatus.RowsRemaining(ctx) == 0 { + return false + } + + return true + }) + + return nil, nil +} + +//// Hydrate Functions + +func getAdServicePrincipalTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + + servicePrincipalID := d.KeyColumnQuals["id"].GetStringValue() + if servicePrincipalID == "" { + return nil, nil + } + + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + servicePrincipal, err := client.ServicePrincipalsById(servicePrincipalID).Get() + if err != nil { + errObj := getErrorObject(err) + if isResourceNotFound(errObj) { + return nil, nil + } + + return nil, errors.New(fmt.Sprintf("failed to get service principal. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + return &ADServicePrincipalInfo{servicePrincipal}, nil +} + +func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + servicePrincipal := h.Item.(*ADServicePrincipalInfo) + servicePrincipalID := servicePrincipal.GetId() + + headers := map[string]string{ + "ConsistencyLevel": "eventual", + } + + includeCount := true + requestParameters := &owners.OwnersRequestBuilderGetQueryParameters{ + Count: &includeCount, + } + + config := &owners.OwnersRequestBuilderGetRequestConfiguration{ + Headers: headers, + QueryParameters: requestParameters, + } + + ownerIds := []*string{} + owners, err := client.ServicePrincipalsById(*servicePrincipalID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list service principal owners. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(func(pageItem interface{}) bool { + owner := pageItem.(models.DirectoryObjectable) + ownerIds = append(ownerIds, owner.GetId()) + + return true + }) + + return ownerIds, nil +} + +//// Transform Function + +func adServicePrincipalTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { + servicePrincipal := d.HydrateItem.(*ADServicePrincipalInfo) + tags := servicePrincipal.GetTags() + return TagsToMap(tags) +} + +func adServicePrincipalTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADServicePrincipalInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetId() + } + + return title, nil +} + +func buildServicePrincipalQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { + filters := []string{} + + filterQuals := map[string]string{ + "display_name": "string", + "account_enabled": "bool", + "service_principal_type": "string", + } + + for qual, qualType := range filterQuals { + switch qualType { + case "string": + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) + } + case "bool": + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), equalQuals[qual].GetBoolValue())) + } + } + } + + return filters +} + +func buildServicePrincipalBoolNEFilter(quals plugin.KeyColumnQualMap) []string { + filters := []string{} + + filterQuals := []string{ + "account_enabled", + } + + for _, qual := range filterQuals { + if quals[qual] != nil { + for _, q := range quals[qual].Quals { + value := q.Value.GetBoolValue() + if q.Operator == "<>" { + filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), !value)) + break + } + } + } + } + + return filters +} diff --git a/azuread/table_azuread_sign_in_report1.go b/azuread/table_azuread_sign_in_report1.go new file mode 100644 index 0000000..194371f --- /dev/null +++ b/azuread/table_azuread_sign_in_report1.go @@ -0,0 +1,139 @@ +package azuread + +import ( + "context" + "errors" + "fmt" + + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/auditlogs/signins" + "github.com/microsoftgraph/msgraph-sdk-go/models" + + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" +) + +//// TABLE DEFINITION + +func tableAzureAdSignInReportTest() *plugin.Table { + return &plugin.Table{ + Name: "azuread_sign_in_report_test", + Description: "Represents an Azure Active Directory (Azure AD) sign in report", + Get: &plugin.GetConfig{ + Hydrate: getAdSignInReportTest, + KeyColumns: plugin.SingleColumn("id"), + }, + List: &plugin.ListConfig{ + Hydrate: listAdSignInReportsTest, + }, + + Columns: []*plugin.Column{ + {Name: "id", Type: proto.ColumnType_STRING, Description: "Unique ID representing the sign-in activity.", Transform: transform.FromMethod("GetId")}, + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Date and time (UTC) the sign-in was initiated.", Transform: transform.FromMethod("GetCreatedDateTime")}, + {Name: "user_display_name", Type: proto.ColumnType_STRING, Description: "Display name of the user that initiated the sign-in.", Transform: transform.FromMethod("GetUserDisplayName")}, + {Name: "user_principal_name", Type: proto.ColumnType_STRING, Description: "User principal name of the user that initiated the sign-in.", Transform: transform.FromMethod("GetUserPrincipalName")}, + {Name: "user_id", Type: proto.ColumnType_STRING, Description: "ID of the user that initiated the sign-in.", Transform: transform.FromMethod("GetUserId")}, + {Name: "app_id", Type: proto.ColumnType_STRING, Description: "Unique GUID representing the app ID in the Azure Active Directory.", Transform: transform.FromMethod("GetAppId")}, + {Name: "app_display_name", Type: proto.ColumnType_STRING, Description: "App name displayed in the Azure Portal.", Transform: transform.FromMethod("GetAppDisplayName")}, + {Name: "ip_address", Type: proto.ColumnType_IPADDR, Description: "IP address of the client used to sign in.", Transform: transform.FromMethod("GetIpAddress")}, + {Name: "client_app_used", Type: proto.ColumnType_STRING, Description: "Identifies the legacy client used for sign-in activity.", Transform: transform.FromMethod("GetClientAppUsed")}, + {Name: "correlation_id", Type: proto.ColumnType_STRING, Description: "The request ID sent from the client when the sign-in is initiated; used to troubleshoot sign-in activity.", Transform: transform.FromMethod("GetCorrelationId")}, + {Name: "conditional_access_status", Type: proto.ColumnType_STRING, Description: "Reports status of an activated conditional access policy. Possible values are: success, failure, notApplied, and unknownFutureValue.", Transform: transform.FromMethod("GetConditionalAccessStatus")}, + {Name: "is_interactive", Type: proto.ColumnType_BOOL, Description: "Indicates if a sign-in is interactive or not.", Transform: transform.FromMethod("GetIsInteractive")}, + {Name: "risk_detail", Type: proto.ColumnType_STRING, Description: "Provides the 'reason' behind a specific state of a risky user, sign-in or a risk event. The possible values are: none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, unknownFutureValue.", Transform: transform.FromMethod("GetRiskDetail")}, + {Name: "risk_level_aggregated", Type: proto.ColumnType_STRING, Description: "Aggregated risk level. The possible values are: none, low, medium, high, hidden, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskLevelAggregated")}, + {Name: "risk_level_during_sign_in", Type: proto.ColumnType_STRING, Description: "Risk level during sign-in. The possible values are: none, low, medium, high, hidden, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskLevelDuringSignIn")}, + {Name: "risk_state", Type: proto.ColumnType_STRING, Description: "Reports status of the risky user, sign-in, or a risk event. The possible values are: none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised, unknownFutureValue.", Transform: transform.FromMethod("GetRiskState")}, + {Name: "resource_display_name", Type: proto.ColumnType_STRING, Description: "Name of the resource the user signed into.", Transform: transform.FromMethod("GetResourceDisplayName")}, + {Name: "resource_id", Type: proto.ColumnType_STRING, Description: "ID of the resource that the user signed into.", Transform: transform.FromMethod("GetResourceId")}, + + // Json fields + {Name: "risk_event_types", Type: proto.ColumnType_JSON, Description: "Risk event types associated with the sign-in. The possible values are: unlikelyTravel, anonymizedIPAddress, maliciousIPAddress, unfamiliarFeatures, malwareInfectedIPAddress, suspiciousIPAddress, leakedCredentials, investigationsThreatIntelligence, generic, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskEventTypes")}, + {Name: "status", Type: proto.ColumnType_JSON, Description: "Sign-in status. Includes the error code and description of the error (in case of a sign-in failure).", Transform: transform.FromMethod("SignInStatus")}, + {Name: "device_detail", Type: proto.ColumnType_JSON, Description: "Device information from where the sign-in occurred; includes device ID, operating system, and browser.", Transform: transform.FromMethod("SignInDeviceDetail")}, + {Name: "location", Type: proto.ColumnType_JSON, Description: " Provides the city, state, and country code where the sign-in originated.", Transform: transform.FromMethod("SignInLocation")}, + {Name: "applied_conditional_access_policies", Type: proto.ColumnType_JSON, Description: "Provides a list of conditional access policies that are triggered by the corresponding sign-in activity.", Transform: transform.FromMethod("SignInAppliedConditionalAccessPolicies")}, + + // Standard columns + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromMethod("GetId")}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, + }, + } +} + +//// LIST FUNCTION + +func listAdSignInReportsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + // List operations + input := &signins.SignInsRequestBuilderGetQueryParameters{} + + // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow + // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. + limit := d.QueryContext.Limit + if limit != nil { + if *limit > 0 && *limit <= 999 { + l := int32(*limit) + input.Top = &l + } + } + + options := &signins.SignInsRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + result, err := client.AuditLogs().SignIns().GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errors.New(fmt.Sprintf("failed to list groups. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateSignInCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + signIn := pageItem.(models.SignInable) + + d.StreamListItem(ctx, &ADSignInReportInfo{signIn}) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.QueryStatus.RowsRemaining(ctx) == 0 { + return false + } + + return true + }) + + return nil, nil +} + +//// Hydrate Functions + +func getAdSignInReportTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + signInID := d.KeyColumnQuals["id"].GetStringValue() + if signInID == "" { + return nil, nil + } + + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + signIn, err := client.AuditLogs().SignInsById(signInID).Get() + if err != nil { + errObj := getErrorObject(err) + if isResourceNotFound(errObj) { + return nil, nil + } + return nil, errors.New(fmt.Sprintf("failed to get signIn log. Code: %s Message: %s", errObj.Code, errObj.Message)) + } + + return &ADSignInReportInfo{signIn}, nil +} diff --git a/azuread/table_azuread_user1.go b/azuread/table_azuread_user1.go index 972e00d..29fdf0b 100644 --- a/azuread/table_azuread_user1.go +++ b/azuread/table_azuread_user1.go @@ -44,35 +44,34 @@ func tableAzureAdUserTest() *plugin.Table { }, Columns: []*plugin.Column{ - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name."}, - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the user. Should be treated as an opaque identifier.", Transform: transform.FromGo()}, - {Name: "user_principal_name", Type: proto.ColumnType_STRING, Description: "Principal email of the active directory user."}, - {Name: "account_enabled", Type: proto.ColumnType_BOOL, Description: "True if the account is enabled; otherwise, false."}, - {Name: "user_type", Type: proto.ColumnType_STRING, Description: "A string value that can be used to classify user types in your directory."}, - {Name: "given_name", Type: proto.ColumnType_STRING, Description: "The given name (first name) of the user."}, - {Name: "surname", Type: proto.ColumnType_STRING, Description: "Family name or last name of the active directory user."}, + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name.", Transform: transform.FromMethod("GetDisplayName")}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the user. Should be treated as an opaque identifier.", Transform: transform.FromMethod("GetId")}, + {Name: "user_principal_name", Type: proto.ColumnType_STRING, Description: "Principal email of the active directory user.", Transform: transform.FromMethod("GetUserPrincipalName")}, + {Name: "account_enabled", Type: proto.ColumnType_BOOL, Description: "True if the account is enabled; otherwise, false.", Transform: transform.FromMethod("GetAccountEnabled")}, + {Name: "user_type", Type: proto.ColumnType_STRING, Description: "A string value that can be used to classify user types in your directory.", Transform: transform.FromMethod("GetUserType")}, + {Name: "given_name", Type: proto.ColumnType_STRING, Description: "The given name (first name) of the user.", Transform: transform.FromMethod("GetGivenName")}, + {Name: "surname", Type: proto.ColumnType_STRING, Description: "Family name or last name of the active directory user.", Transform: transform.FromMethod("GetSurname")}, {Name: "filter", Type: proto.ColumnType_STRING, Transform: transform.FromQual("filter"), Description: "Odata query to search for resources."}, - // // Other fields - {Name: "on_premises_immutable_id", Type: proto.ColumnType_STRING, Description: "Used to associate an on-premises Active Directory user account with their Azure AD user object."}, - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the user was created."}, - {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the user, for example, jeff@contoso.onmicrosoft.com."}, - {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user."}, - {Name: "password_policies", Type: proto.ColumnType_STRING, Description: "Specifies password policies for the user. This value is an enumeration with one possible value being DisableStrongPassword, which allows weaker passwords than the default policy to be specified. DisablePasswordExpiration can also be specified. The two may be specified together; for example: DisablePasswordExpiration, DisableStrongPassword."}, + // Other fields + {Name: "on_premises_immutable_id", Type: proto.ColumnType_STRING, Description: "Used to associate an on-premises Active Directory user account with their Azure AD user object.", Transform: transform.FromMethod("GetOnPremisesImmutableId")}, + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the user was created.", Transform: transform.FromMethod("GetCreatedDateTime")}, + {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the user, for example, jeff@contoso.onmicrosoft.com.", Transform: transform.FromMethod("GetMail")}, + {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user.", Transform: transform.FromMethod("GetMailNickname")}, + {Name: "password_policies", Type: proto.ColumnType_STRING, Description: "Specifies password policies for the user. This value is an enumeration with one possible value being DisableStrongPassword, which allows weaker passwords than the default policy to be specified. DisablePasswordExpiration can also be specified. The two may be specified together; for example: DisablePasswordExpiration, DisableStrongPassword.", Transform: transform.FromMethod("GetPasswordPolicies")}, // {Name: "refresh_tokens_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph)."}, - {Name: "sign_in_sessions_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph)."}, - {Name: "usage_location", Type: proto.ColumnType_STRING, Description: "A two letter country code (ISO standard 3166), required for users that will be assigned licenses due to legal requirement to check for availability of services in countries."}, + {Name: "sign_in_sessions_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph).", Transform: transform.FromMethod("GetSignInSessionsValidFromDateTime")}, + {Name: "usage_location", Type: proto.ColumnType_STRING, Description: "A two letter country code (ISO standard 3166), required for users that will be assigned licenses due to legal requirement to check for availability of services in countries.", Transform: transform.FromMethod("GetUsageLocation")}, - // // Json fields - {Name: "member_of", Type: proto.ColumnType_JSON, Description: "A list the groups and directory roles that the user is a direct member of."}, - // {Name: "additional_properties", Type: proto.ColumnType_JSON, Description: "A list of unmatched properties from the message are deserialized this collection."}, - {Name: "im_addresses", Type: proto.ColumnType_JSON, Description: "The instant message voice over IP (VOIP) session initiation protocol (SIP) addresses for the user."}, - {Name: "other_mails", Type: proto.ColumnType_JSON, Description: "A list of additional email addresses for the user."}, - {Name: "password_profile", Type: proto.ColumnType_JSON, Description: "Specifies the password profile for the user. The profile contains the user’s password. This property is required when a user is created."}, + // Json fields + {Name: "member_of", Type: proto.ColumnType_JSON, Description: "A list the groups and directory roles that the user is a direct member of.", Transform: transform.FromMethod("UserMemberOf")}, + {Name: "im_addresses", Type: proto.ColumnType_JSON, Description: "The instant message voice over IP (VOIP) session initiation protocol (SIP) addresses for the user.", Transform: transform.FromMethod("GetImAddresses")}, + {Name: "other_mails", Type: proto.ColumnType_JSON, Description: "A list of additional email addresses for the user.", Transform: transform.FromMethod("GetOtherMails")}, + {Name: "password_profile", Type: proto.ColumnType_JSON, Description: "Specifies the password profile for the user. The profile contains the user’s password. This property is required when a user is created.", Transform: transform.FromMethod("UserPasswordProfile")}, // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "UserPrincipalName")}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adUserTitle)}, {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } @@ -84,7 +83,7 @@ func listAdUsersTest(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydrate // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - panic(fmt.Errorf("error creating credentials: %w", err)) + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } // List operations @@ -139,33 +138,12 @@ func listAdUsersTest(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydrate err = pageIterator.Iterate(func(pageItem interface{}) bool { user := pageItem.(models.Userable) - result := map[string]interface{}{ - "DisplayName": user.GetDisplayName(), - "ID": user.GetId(), - "UserPrincipalName": user.GetUserPrincipalName(), - "AccountEnabled": user.GetAccountEnabled(), - "UserType": user.GetUserType(), - "GivenName": user.GetGivenName(), - "Surname": user.GetSurname(), - "OnPremisesImmutableId": user.GetOnPremisesImmutableId(), - "CreatedDateTime": user.GetCreatedDateTime(), - "Mail": user.GetMail(), - "MailNickname": user.GetMailNickname(), - "PasswordPolicies": user.GetPasswordPolicies(), - "SignInSessionsValidFromDateTime": user.GetSignInSessionsValidFromDateTime(), - "UsageLocation": user.GetUsageLocation(), - "ImAddresses": user.GetImAddresses(), - "OtherMails": user.GetOtherMails(), - "PasswordProfile": user.GetPasswordProfile(), - } + d.StreamListItem(ctx, &ADUserInfo{user}) - memberIds := []string{} - for _, i := range user.GetMemberOf() { - memberIds = append(memberIds, *i.GetId()) + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.QueryStatus.RowsRemaining(ctx) == 0 { + return false } - result["MemberOf"] = memberIds - - d.StreamListItem(ctx, result) return true }) @@ -180,7 +158,7 @@ func getAdUserTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateDa // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - panic(fmt.Errorf("error creating credentials: %w", err)) + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } userId := d.KeyColumnQuals["id"].GetStringValue() @@ -210,33 +188,7 @@ func getAdUserTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateDa return nil, errors.New(fmt.Sprintf("failed to get user. Code: %s Message: %s", errObj.Code, errObj.Message)) } - result := map[string]interface{}{ - "DisplayName": user.GetDisplayName(), - "ID": user.GetId(), - "UserPrincipalName": user.GetUserPrincipalName(), - "AccountEnabled": user.GetAccountEnabled(), - "UserType": user.GetUserType(), - "GivenName": user.GetGivenName(), - "Surname": user.GetSurname(), - "OnPremisesImmutableId": user.GetOnPremisesImmutableId(), - "CreatedDateTime": user.GetCreatedDateTime(), - "Mail": user.GetMail(), - "MailNickname": user.GetMailNickname(), - "PasswordPolicies": user.GetPasswordPolicies(), - "SignInSessionsValidFromDateTime": user.GetSignInSessionsValidFromDateTime(), - "UsageLocation": user.GetUsageLocation(), - "ImAddresses": user.GetImAddresses(), - "OtherMails": user.GetOtherMails(), - "PasswordProfile": user.GetPasswordProfile(), - } - - memberIds := []string{} - for _, i := range user.GetMemberOf() { - memberIds = append(memberIds, *i.GetId()) - } - result["MemberOf"] = memberIds - - return result, nil + return &ADUserInfo{user}, nil } func buildUserRequestFields(ctx context.Context, queryColumns []string) ([]string, []string) { @@ -280,3 +232,19 @@ func getTenant(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) return tenantID, nil } + +//// TRANSFORM FUNCTIONS + +func adUserTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADUserInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetUserPrincipalName() + } + + return title, nil +} From 3911d9208d78577b17868462500a0b8da216e9cb Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Mon, 25 Jul 2022 17:56:01 +0530 Subject: [PATCH 05/21] Update error handling --- azuread/errors.go | 56 +++++ azuread/helpers.go | 191 ++++++++++++++++++ azuread/plugin.go | 35 ++-- azuread/table_azuread_application1.go | 21 +- ...able_azuread_conditional_access_policy1.go | 190 +++++++++++++++++ azuread/table_azuread_directory_role1.go | 14 +- azuread/table_azuread_domain1.go | 12 +- azuread/table_azuread_group1.go | 20 +- azuread/table_azuread_service_principal1.go | 27 ++- azuread/table_azuread_sign_in_report1.go | 12 +- azuread/table_azuread_user1.go | 13 +- azuread/utils.go | 27 --- go.mod | 50 +++-- go.sum | 133 ++++++++---- 14 files changed, 646 insertions(+), 155 deletions(-) create mode 100644 azuread/errors.go create mode 100644 azuread/table_azuread_conditional_access_policy1.go diff --git a/azuread/errors.go b/azuread/errors.go new file mode 100644 index 0000000..00489e4 --- /dev/null +++ b/azuread/errors.go @@ -0,0 +1,56 @@ +package azuread + +import ( + "context" + "encoding/json" + "strings" + + "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" +) + +type RequestError struct { + Code string + Message string +} + +func (m *RequestError) Error() string { + errStr, _ := json.Marshal(m) + return string(errStr) +} + +func getErrorObject(err error) *RequestError { + if oDataError, ok := err.(*odataerrors.ODataError); ok { + if terr := oDataError.GetError(); terr != nil { + return &RequestError{ + Code: *terr.GetCode(), + Message: *terr.GetMessage(), + } + } + } + + return nil +} + +func isIgnorableErrorPredicate(ignoreErrorCodes []string) plugin.ErrorPredicateWithContext { + return func(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData, err error) bool { + if err != nil { + if terr, ok := err.(*RequestError); ok { + for _, item := range ignoreErrorCodes { + if terr != nil && (terr.Code == item || strings.Contains(terr.Message, item)) { + return true + } + } + } + } + return false + } +} + +// Remove after adding IgnoreConfig +func isResourceNotFound(errObj *RequestError) bool { + if errObj != nil && errObj.Code == "Request_ResourceNotFound" { + return true + } + return false +} diff --git a/azuread/helpers.go b/azuread/helpers.go index ddbb62f..0b9da1f 100644 --- a/azuread/helpers.go +++ b/azuread/helpers.go @@ -6,6 +6,10 @@ type ADApplicationInfo struct { models.Applicationable } +type ADConditionalAccessPolicyInfo struct { + models.ConditionalAccessPolicyable +} + type ADGroupInfo struct { models.Groupable } @@ -229,6 +233,193 @@ func (application *ADApplicationInfo) ApplicationWeb() map[string]interface{} { return webData } +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicyConditionsApplications() map[string]interface{} { + if conditionalAccessPolicy.GetConditions() == nil { + return nil + } + + if conditionalAccessPolicy.GetConditions().GetApplications() == nil { + return nil + } + + return map[string]interface{}{ + "excludeApplications": conditionalAccessPolicy.GetConditions().GetApplications().GetExcludeApplications(), + "includeApplications": conditionalAccessPolicy.GetConditions().GetApplications().GetIncludeApplications(), + "includeAuthenticationContextClassReferences": conditionalAccessPolicy.GetConditions().GetApplications().GetIncludeAuthenticationContextClassReferences(), + "includeUserActions": conditionalAccessPolicy.GetConditions().GetApplications().GetIncludeUserActions(), + } +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicyConditionsClientAppTypes() []string { + if conditionalAccessPolicy.GetConditions() == nil { + return nil + } + return conditionalAccessPolicy.GetConditions().GetClientAppTypes() +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicyConditionsLocations() map[string]interface{} { + if conditionalAccessPolicy.GetConditions() == nil { + return nil + } + + if conditionalAccessPolicy.GetConditions().GetLocations() == nil { + return nil + } + + return map[string]interface{}{ + "excludeLocations": conditionalAccessPolicy.GetConditions().GetLocations().GetExcludeLocations(), + "includeLocations": conditionalAccessPolicy.GetConditions().GetLocations().GetIncludeLocations(), + } +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicyConditionsPlatforms() map[string]interface{} { + if conditionalAccessPolicy.GetConditions() == nil { + return nil + } + + if conditionalAccessPolicy.GetConditions().GetPlatforms() == nil { + return nil + } + + return map[string]interface{}{ + "excludePlatforms": conditionalAccessPolicy.GetConditions().GetPlatforms().GetExcludePlatforms(), + "includePlatforms": conditionalAccessPolicy.GetConditions().GetPlatforms().GetIncludePlatforms(), + } +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicyConditionsSignInRiskLevels() []string { + if conditionalAccessPolicy.GetConditions() == nil { + return nil + } + return conditionalAccessPolicy.GetConditions().GetSignInRiskLevels() +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicyConditionsUsers() map[string]interface{} { + if conditionalAccessPolicy.GetConditions() == nil { + return nil + } + + if conditionalAccessPolicy.GetConditions().GetUsers() == nil { + return nil + } + + return map[string]interface{}{ + "excludeGroups": conditionalAccessPolicy.GetConditions().GetUsers().GetExcludeGroups(), + "excludeRoles": conditionalAccessPolicy.GetConditions().GetUsers().GetExcludeRoles(), + "excludeUsers": conditionalAccessPolicy.GetConditions().GetUsers().GetExcludeUsers(), + "includeGroups": conditionalAccessPolicy.GetConditions().GetUsers().GetIncludeGroups(), + "includeRoles": conditionalAccessPolicy.GetConditions().GetUsers().GetIncludeRoles(), + "includeUsers": conditionalAccessPolicy.GetConditions().GetUsers().GetIncludeUsers(), + } +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicyConditionsUserRiskLevels() []string { + if conditionalAccessPolicy.GetConditions() == nil { + return nil + } + return conditionalAccessPolicy.GetConditions().GetUserRiskLevels() +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicyGrantControlsBuiltInControls() []string { + if conditionalAccessPolicy.GetGrantControls() == nil { + return nil + } + return conditionalAccessPolicy.GetGrantControls().GetBuiltInControls() +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicyGrantControlsCustomAuthenticationFactors() []string { + if conditionalAccessPolicy.GetGrantControls() == nil { + return nil + } + return conditionalAccessPolicy.GetGrantControls().GetCustomAuthenticationFactors() +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicyGrantControlsOperator() *string { + if conditionalAccessPolicy.GetGrantControls() == nil { + return nil + } + return conditionalAccessPolicy.GetGrantControls().GetOperator() +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicyGrantControlsTermsOfUse() []string { + if conditionalAccessPolicy.GetGrantControls() == nil { + return nil + } + return conditionalAccessPolicy.GetGrantControls().GetTermsOfUse() +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicySessionControlsApplicationEnforcedRestrictions() map[string]interface{} { + if conditionalAccessPolicy.GetSessionControls() == nil { + return nil + } + if conditionalAccessPolicy.GetSessionControls().GetApplicationEnforcedRestrictions() == nil { + return nil + } + + data := map[string]interface{}{} + if conditionalAccessPolicy.GetSessionControls().GetApplicationEnforcedRestrictions().GetIsEnabled() != nil { + data["isEnabled"] = conditionalAccessPolicy.GetSessionControls().GetApplicationEnforcedRestrictions().GetIsEnabled() + } + if conditionalAccessPolicy.GetSessionControls().GetApplicationEnforcedRestrictions().GetType() != nil { + data["type"] = conditionalAccessPolicy.GetSessionControls().GetApplicationEnforcedRestrictions().GetType() + } + return data +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicySessionControlsCloudAppSecurity() map[string]interface{} { + if conditionalAccessPolicy.GetSessionControls() == nil { + return nil + } + if conditionalAccessPolicy.GetSessionControls().GetCloudAppSecurity() == nil { + return nil + } + + data := map[string]interface{}{} + if conditionalAccessPolicy.GetSessionControls().GetCloudAppSecurity().GetIsEnabled() != nil { + data["isEnabled"] = conditionalAccessPolicy.GetSessionControls().GetCloudAppSecurity().GetIsEnabled() + } + if conditionalAccessPolicy.GetSessionControls().GetCloudAppSecurity().GetCloudAppSecurityType() != nil { + data["cloudAppSecurityType"] = conditionalAccessPolicy.GetSessionControls().GetCloudAppSecurity().GetCloudAppSecurityType() + } + return data +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicySessionControlsPersistentBrowser() map[string]interface{} { + if conditionalAccessPolicy.GetSessionControls() == nil { + return nil + } + if conditionalAccessPolicy.GetSessionControls().GetPersistentBrowser() == nil { + return nil + } + + data := map[string]interface{}{} + if conditionalAccessPolicy.GetSessionControls().GetPersistentBrowser().GetIsEnabled() != nil { + data["isEnabled"] = conditionalAccessPolicy.GetSessionControls().GetPersistentBrowser().GetIsEnabled() + } + if conditionalAccessPolicy.GetSessionControls().GetPersistentBrowser().GetMode() != nil { + data["mode"] = conditionalAccessPolicy.GetSessionControls().GetPersistentBrowser().GetMode() + } + return data +} + +func (conditionalAccessPolicy *ADConditionalAccessPolicyInfo) ConditionalAccessPolicySessionControlsSignInFrequency() map[string]interface{} { + if conditionalAccessPolicy.GetSessionControls() == nil { + return nil + } + if conditionalAccessPolicy.GetSessionControls().GetSignInFrequency() == nil { + return nil + } + + data := map[string]interface{}{} + if conditionalAccessPolicy.GetSessionControls().GetSignInFrequency().GetIsEnabled() != nil { + data["isEnabled"] = conditionalAccessPolicy.GetSessionControls().GetSignInFrequency().GetIsEnabled() + } + if conditionalAccessPolicy.GetSessionControls().GetSignInFrequency().GetValue() != nil { + data["value"] = conditionalAccessPolicy.GetSessionControls().GetSignInFrequency().GetValue() + } + return data +} + func (group *ADGroupInfo) GroupAssignedLabels() []map[string]*string { if group.GetAssignedLabels() == nil { return nil diff --git a/azuread/plugin.go b/azuread/plugin.go index a145989..a52291d 100644 --- a/azuread/plugin.go +++ b/azuread/plugin.go @@ -22,23 +22,24 @@ func Plugin(ctx context.Context) *plugin.Plugin { Schema: ConfigSchema, }, TableMap: map[string]*plugin.Table{ - "azuread_application": tableAzureAdApplication(), - "azuread_conditional_access_policy": tableAzureAdConditionalAccessPolicy(), - "azuread_directory_role": tableAzureAdDirectoryRole(), - "azuread_domain": tableAzureAdDomain(), - "azuread_group": tableAzureAdGroup(), - "azuread_identity_provider": tableAzureAdIdentityProvider(), - "azuread_service_principal": tableAzureAdServicePrincipal(), - "azuread_sign_in_report": tableAzureAdSignInReport(), - "azuread_user": tableAzureAdUser(), - "azuread_user_test": tableAzureAdUserTest(), - "azuread_group_test": tableAzureAdGroupTest(), - "azuread_domain_test": tableAzureAdDomainTest(), - "azuread_identity_provider_test": tableAzureAdIdentityProviderTest(), - "azuread_directory_role_test": tableAzureAdDirectoryRoleTest(), - "azuread_application_test": tableAzureAdApplicationTest(), - "azuread_service_principal_test": tableAzureAdServicePrincipalTest(), - "azuread_sign_in_report_test": tableAzureAdSignInReportTest(), + "azuread_application": tableAzureAdApplication(), + "azuread_conditional_access_policy": tableAzureAdConditionalAccessPolicy(), + "azuread_directory_role": tableAzureAdDirectoryRole(), + "azuread_domain": tableAzureAdDomain(), + "azuread_group": tableAzureAdGroup(), + "azuread_identity_provider": tableAzureAdIdentityProvider(), + "azuread_service_principal": tableAzureAdServicePrincipal(), + "azuread_sign_in_report": tableAzureAdSignInReport(), + "azuread_user": tableAzureAdUser(), + "azuread_user_test": tableAzureAdUserTest(), + "azuread_group_test": tableAzureAdGroupTest(), + "azuread_domain_test": tableAzureAdDomainTest(), + "azuread_identity_provider_test": tableAzureAdIdentityProviderTest(), + "azuread_directory_role_test": tableAzureAdDirectoryRoleTest(), + "azuread_application_test": tableAzureAdApplicationTest(), + "azuread_service_principal_test": tableAzureAdServicePrincipalTest(), + "azuread_sign_in_report_test": tableAzureAdSignInReportTest(), + "azuread_conditional_access_policy_test": tableAzureAdConditionalAccessPolicyTest(), }, } diff --git a/azuread/table_azuread_application1.go b/azuread/table_azuread_application1.go index 49f9c53..1ac4df6 100644 --- a/azuread/table_azuread_application1.go +++ b/azuread/table_azuread_application1.go @@ -24,12 +24,17 @@ func tableAzureAdApplicationTest() *plugin.Table { Name: "azuread_application_test", Description: "Represents an Azure Active Directory (Azure AD) application", Get: &plugin.GetConfig{ - Hydrate: getAdApplicationTest, + Hydrate: getAdApplicationTest, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ Hydrate: listAdApplicationsTest, - //ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Request_UnsupportedQuery"}), + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery"}), + }, KeyColumns: plugin.KeyColumnSlice{ // Key fields {Name: "app_id", Require: plugin.Optional}, @@ -115,7 +120,7 @@ func listAdApplicationsTest(ctx context.Context, d *plugin.QueryData, _ *plugin. result, err := client.Applications().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list applications. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateApplicationCollectionResponseFromDiscriminatorValue) @@ -154,11 +159,7 @@ func getAdApplicationTest(ctx context.Context, d *plugin.QueryData, h *plugin.Hy application, err := client.ApplicationsById(applicationId).Get() if err != nil { errObj := getErrorObject(err) - if isResourceNotFound(errObj) { - return nil, nil - } - - return nil, errors.New(fmt.Sprintf("failed to get application. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } return &ADApplicationInfo{application}, nil @@ -192,7 +193,7 @@ func getAdApplicationOwners(ctx context.Context, d *plugin.QueryData, h *plugin. owners, err := client.ApplicationsById(*applicationID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list application owners. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) @@ -237,7 +238,7 @@ func buildApplicationQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []str "publisher_domain": "string", } - for qual, _ := range filterQuals { + for qual := range filterQuals { if equalQuals[qual] != nil { filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) } diff --git a/azuread/table_azuread_conditional_access_policy1.go b/azuread/table_azuread_conditional_access_policy1.go new file mode 100644 index 0000000..592f7af --- /dev/null +++ b/azuread/table_azuread_conditional_access_policy1.go @@ -0,0 +1,190 @@ +package azuread + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/iancoleman/strcase" + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" + + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/identity/conditionalaccess/policies" + "github.com/microsoftgraph/msgraph-sdk-go/models" +) + +//// TABLE DEFINITION + +func tableAzureAdConditionalAccessPolicyTest() *plugin.Table { + return &plugin.Table{ + Name: "azuread_conditional_access_policy_test", + Description: "Represents an Azure Active Directory (Azure AD) Conditional Access Policy", + Get: &plugin.GetConfig{ + Hydrate: getAdConditionalAccessPolicyTest, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, + KeyColumns: plugin.SingleColumn("id"), + }, + List: &plugin.ListConfig{ + Hydrate: listAdConditionalAccessPoliciesTest, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery"}), + }, + KeyColumns: []*plugin.KeyColumn{ + {Name: "display_name", Require: plugin.Optional}, + {Name: "state", Require: plugin.Optional}, + }, + }, + + Columns: []*plugin.Column{ + {Name: "id", Type: proto.ColumnType_STRING, Description: "Specifies the identifier of a conditionalAccessPolicy object.", Transform: transform.FromMethod("GetId")}, + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "Specifies a display name for the conditionalAccessPolicy object.", Transform: transform.FromMethod("GetDisplayName")}, + {Name: "state", Type: proto.ColumnType_STRING, Description: "Specifies the state of the conditionalAccessPolicy object. Possible values are: enabled, disabled, enabledForReportingButNotEnforced.", Transform: transform.FromMethod("GetState")}, + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The create date of the conditional access policy.", Transform: transform.FromMethod("GetCreatedDateTime")}, + {Name: "modified_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The modification date of the conditional access policy.", Transform: transform.FromMethod("GetModifiedDateTime")}, + {Name: "operator", Type: proto.ColumnType_STRING, Description: "Defines the relationship of the grant controls. Possible values: AND, OR.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsOperator")}, + + // Json fields + {Name: "applications", Type: proto.ColumnType_JSON, Description: "Applications and user actions included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsApplications")}, + {Name: "application_enforced_restrictions", Type: proto.ColumnType_JSON, Description: "Session control to enforce application restrictions. Only Exchange Online and Sharepoint Online support this session control.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsApplicationEnforcedRestrictions")}, + {Name: "built_in_controls", Type: proto.ColumnType_JSON, Description: "List of values of built-in controls required by the policy. Possible values: block, mfa, compliantDevice, domainJoinedDevice, approvedApplication, compliantApplication, passwordChange, unknownFutureValue.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsBuiltInControls")}, + {Name: "client_app_types", Type: proto.ColumnType_JSON, Description: "Client application types included in the policy. Possible values are: all, browser, mobileAppsAndDesktopClients, exchangeActiveSync, easSupported, other.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsClientAppTypes")}, + {Name: "custom_authentication_factors", Type: proto.ColumnType_JSON, Description: "List of custom controls IDs required by the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsCustomAuthenticationFactors")}, + {Name: "cloud_app_security", Type: proto.ColumnType_JSON, Description: "Session control to apply cloud app security.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsCloudAppSecurity")}, + {Name: "locations", Type: proto.ColumnType_JSON, Description: "Locations included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsLocations")}, + {Name: "persistent_browser", Type: proto.ColumnType_JSON, Description: "Session control to define whether to persist cookies or not. All apps should be selected for this session control to work correctly.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsPersistentBrowser")}, + {Name: "platforms", Type: proto.ColumnType_JSON, Description: "Platforms included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsPlatforms")}, + {Name: "sign_in_frequency", Type: proto.ColumnType_JSON, Description: "Session control to enforce signin frequency.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsSignInFrequency")}, + {Name: "sign_in_risk_levels", Type: proto.ColumnType_JSON, Description: "Sign-in risk levels included in the policy. Possible values are: low, medium, high, hidden, none, unknownFutureValue.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsSignInRiskLevels")}, + {Name: "terms_of_use", Type: proto.ColumnType_JSON, Description: "List of terms of use IDs required by the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsTermsOfUse")}, + {Name: "users", Type: proto.ColumnType_JSON, Description: "Users, groups, and roles included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsUsers")}, + {Name: "user_risk_levels", Type: proto.ColumnType_JSON, Description: "User risk levels included in the policy. Possible values are: low, medium, high, hidden, none, unknownFutureValue.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsUserRiskLevels")}, + + // Standard columns + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adConditionalAccessPolicyTitle)}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, + }, + } +} + +//// LIST FUNCTION + +func listAdConditionalAccessPoliciesTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + // List operations + input := &policies.PoliciesRequestBuilderGetQueryParameters{} + + // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow + // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. + limit := d.QueryContext.Limit + if limit != nil { + if *limit > 0 && *limit <= 999 { + l := int32(*limit) + input.Top = &l + } + } + + equalQuals := d.KeyColumnQuals + filter := buildConditionalAccessPolicyQueryFilter(equalQuals) + + if len(filter) > 0 { + joinStr := strings.Join(filter, " and ") + input.Filter = &joinStr + } + + options := &policies.PoliciesRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + result, err := client.Identity().ConditionalAccess().Policies().GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errObj + } + + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateConditionalAccessPolicyCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + policy := pageItem.(models.ConditionalAccessPolicyable) + + d.StreamListItem(ctx, &ADConditionalAccessPolicyInfo{policy}) + + // Context can be cancelled due to manual cancellation or the limit has been hit + if d.QueryStatus.RowsRemaining(ctx) == 0 { + return false + } + + return true + }) + + return nil, err +} + +//// HYDRATE FUNCTIONS + +func getAdConditionalAccessPolicyTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + + conditionalAccessPolicyId := d.KeyColumnQuals["id"].GetStringValue() + if conditionalAccessPolicyId == "" { + return nil, nil + } + + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + policy, err := client.Identity().ConditionalAccess().PoliciesById(conditionalAccessPolicyId).Get() + if err != nil { + errObj := getErrorObject(err) + return nil, errObj + } + return &ADConditionalAccessPolicyInfo{policy}, nil +} + +func buildConditionalAccessPolicyQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { + filters := []string{} + + filterQuals := map[string]string{ + "display_name": "string", + "state": "string", + } + + for qual, qualType := range filterQuals { + switch qualType { + case "string": + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) + } + } + } + + return filters +} + +//// TRANSFORM FUNCTIONS + +func adConditionalAccessPolicyTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADConditionalAccessPolicyInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetId() + } + + return title, nil +} diff --git a/azuread/table_azuread_directory_role1.go b/azuread/table_azuread_directory_role1.go index df55325..275a584 100644 --- a/azuread/table_azuread_directory_role1.go +++ b/azuread/table_azuread_directory_role1.go @@ -21,7 +21,10 @@ func tableAzureAdDirectoryRoleTest() *plugin.Table { Name: "azuread_directory_role_test", Description: "Represents an Azure Active Directory (Azure AD) directory role", Get: &plugin.GetConfig{ - Hydrate: getAdDirectoryRoleTest, + Hydrate: getAdDirectoryRoleTest, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ @@ -62,7 +65,7 @@ func listAdDirectoryRolesTest(ctx context.Context, d *plugin.QueryData, _ *plugi result, err := client.DirectoryRoles().Get() if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list groups. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } for _, directoryRole := range result.GetValue() { @@ -94,10 +97,7 @@ func getAdDirectoryRoleTest(ctx context.Context, d *plugin.QueryData, h *plugin. directoryRole, err := client.DirectoryRolesById(directoryRoleId).Get() if err != nil { errObj := getErrorObject(err) - if isResourceNotFound(errObj) { - return nil, nil - } - return nil, errors.New(fmt.Sprintf("failed to get directory role. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } return &ADDirectoryRoleInfo{directoryRole}, nil @@ -131,7 +131,7 @@ func getDirectoryRoleMembers(ctx context.Context, d *plugin.QueryData, h *plugin members, err := client.DirectoryRolesById(*directoryRoleID).Members().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list directory role members. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } pageIterator, err := msgraphcore.NewPageIterator(members, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) diff --git a/azuread/table_azuread_domain1.go b/azuread/table_azuread_domain1.go index aa378e2..a97fc67 100644 --- a/azuread/table_azuread_domain1.go +++ b/azuread/table_azuread_domain1.go @@ -22,7 +22,10 @@ func tableAzureAdDomainTest() *plugin.Table { Name: "azuread_domain_test", Description: "Represents an Azure Active Directory (Azure AD) domain", Get: &plugin.GetConfig{ - Hydrate: getAdDomainTest, + Hydrate: getAdDomainTest, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ @@ -83,7 +86,7 @@ func listAdDomainsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydra result, err := client.Domains().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list domains. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateDomainCollectionResponseFromDiscriminatorValue) @@ -121,10 +124,7 @@ func getAdDomainTest(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrate domain, err := client.DomainsById(domainId).Get() if err != nil { errObj := getErrorObject(err) - if isResourceNotFound(errObj) { - return nil, nil - } - return nil, errors.New(fmt.Sprintf("failed to get domain. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } return &ADDomainInfo{domain}, nil diff --git a/azuread/table_azuread_group1.go b/azuread/table_azuread_group1.go index b4bf366..a90acd6 100644 --- a/azuread/table_azuread_group1.go +++ b/azuread/table_azuread_group1.go @@ -27,9 +27,11 @@ func tableAzureAdGroupTest() *plugin.Table { Name: "azuread_group_test", Description: "Represents an Azure AD user account.", Get: &plugin.GetConfig{ - Hydrate: getAdGroupTest, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), - KeyColumns: plugin.SingleColumn("id"), + Hydrate: getAdGroupTest, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound"}), + }, + KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ Hydrate: listAdGroupsTest, @@ -154,7 +156,7 @@ func listAdGroupsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydrat result, err := client.Groups().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list groups. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateGroupCollectionResponseFromDiscriminatorValue) @@ -204,11 +206,7 @@ func getAdGroupTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateD group, err := client.GroupsById(groupId).GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) - if isResourceNotFound(errObj) { - return nil, nil - } - - return nil, errors.New(fmt.Sprintf("failed to get group. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } return &ADGroupInfo{group}, nil @@ -242,7 +240,7 @@ func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.Hydra members, err := client.GroupsById(*groupID).Members().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list group members. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } pageIterator, err := msgraphcore.NewPageIterator(members, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) @@ -284,7 +282,7 @@ func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat owners, err := client.GroupsById(*groupID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list group owners. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) diff --git a/azuread/table_azuread_service_principal1.go b/azuread/table_azuread_service_principal1.go index a3eeb45..d7319a1 100644 --- a/azuread/table_azuread_service_principal1.go +++ b/azuread/table_azuread_service_principal1.go @@ -24,12 +24,17 @@ func tableAzureAdServicePrincipalTest() *plugin.Table { Name: "azuread_service_principal_test", Description: "Represents an Azure Active Directory (Azure AD) service principal", Get: &plugin.GetConfig{ - Hydrate: getAdServicePrincipalTest, + Hydrate: getAdServicePrincipalTest, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ Hydrate: listAdServicePrincipalsTest, - //ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Request_UnsupportedQuery"}), + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery"}), + }, KeyColumns: plugin.KeyColumnSlice{ // Key fields {Name: "display_name", Require: plugin.Optional}, @@ -124,7 +129,7 @@ func listAdServicePrincipalsTest(ctx context.Context, d *plugin.QueryData, _ *pl result, err := client.ServicePrincipals().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list service principals. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateServicePrincipalCollectionResponseFromDiscriminatorValue) @@ -145,7 +150,7 @@ func listAdServicePrincipalsTest(ctx context.Context, d *plugin.QueryData, _ *pl return nil, nil } -//// Hydrate Functions +//// HYDRATE FUNCTIONS func getAdServicePrincipalTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { @@ -163,11 +168,7 @@ func getAdServicePrincipalTest(ctx context.Context, d *plugin.QueryData, h *plug servicePrincipal, err := client.ServicePrincipalsById(servicePrincipalID).Get() if err != nil { errObj := getErrorObject(err) - if isResourceNotFound(errObj) { - return nil, nil - } - - return nil, errors.New(fmt.Sprintf("failed to get service principal. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } return &ADServicePrincipalInfo{servicePrincipal}, nil @@ -183,6 +184,10 @@ func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plug servicePrincipal := h.Item.(*ADServicePrincipalInfo) servicePrincipalID := servicePrincipal.GetId() + if servicePrincipalID == nil { + return nil, nil + } + headers := map[string]string{ "ConsistencyLevel": "eventual", } @@ -201,7 +206,7 @@ func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plug owners, err := client.ServicePrincipalsById(*servicePrincipalID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list service principal owners. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) @@ -215,7 +220,7 @@ func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plug return ownerIds, nil } -//// Transform Function +//// TRANSFORM FUNCTIONS func adServicePrincipalTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { servicePrincipal := d.HydrateItem.(*ADServicePrincipalInfo) diff --git a/azuread/table_azuread_sign_in_report1.go b/azuread/table_azuread_sign_in_report1.go index 194371f..387be6a 100644 --- a/azuread/table_azuread_sign_in_report1.go +++ b/azuread/table_azuread_sign_in_report1.go @@ -21,7 +21,10 @@ func tableAzureAdSignInReportTest() *plugin.Table { Name: "azuread_sign_in_report_test", Description: "Represents an Azure Active Directory (Azure AD) sign in report", Get: &plugin.GetConfig{ - Hydrate: getAdSignInReportTest, + Hydrate: getAdSignInReportTest, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ @@ -91,7 +94,7 @@ func listAdSignInReportsTest(ctx context.Context, d *plugin.QueryData, _ *plugin result, err := client.AuditLogs().SignIns().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list groups. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateSignInCollectionResponseFromDiscriminatorValue) @@ -129,10 +132,7 @@ func getAdSignInReportTest(ctx context.Context, d *plugin.QueryData, h *plugin.H signIn, err := client.AuditLogs().SignInsById(signInID).Get() if err != nil { errObj := getErrorObject(err) - if isResourceNotFound(errObj) { - return nil, nil - } - return nil, errors.New(fmt.Sprintf("failed to get signIn log. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } return &ADSignInReportInfo{signIn}, nil diff --git a/azuread/table_azuread_user1.go b/azuread/table_azuread_user1.go index 29fdf0b..7a2422d 100644 --- a/azuread/table_azuread_user1.go +++ b/azuread/table_azuread_user1.go @@ -25,7 +25,10 @@ func tableAzureAdUserTest() *plugin.Table { Name: "azuread_user_test", Description: "Represents an Azure AD user account.", Get: &plugin.GetConfig{ - Hydrate: getAdUserTest, + Hydrate: getAdUserTest, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ @@ -130,7 +133,7 @@ func listAdUsersTest(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydrate result, err := client.Users().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list users. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateUserCollectionResponseFromDiscriminatorValue) @@ -181,11 +184,7 @@ func getAdUserTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateDa user, err := client.UsersById(userId).GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) - if isResourceNotFound(errObj) { - return nil, nil - } - - return nil, errors.New(fmt.Sprintf("failed to get user. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } return &ADUserInfo{user}, nil diff --git a/azuread/utils.go b/azuread/utils.go index 31ab8c5..2d6221e 100644 --- a/azuread/utils.go +++ b/azuread/utils.go @@ -5,7 +5,6 @@ import ( "strconv" "strings" - "github.com/microsoftgraph/msgraph-sdk-go/models/odataerrors" "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" ) @@ -21,32 +20,6 @@ func isNotFoundError(err error) bool { return strings.Contains(err.Error(), "Request_ResourceNotFound") } -// New -type RequestError struct { - Code string - Message string -} - -func getErrorObject(err error) *RequestError { - if oDataError, ok := err.(*odataerrors.ODataError); ok { - if terr := oDataError.GetError(); terr != nil { - return &RequestError{ - Code: *terr.GetCode(), - Message: *terr.GetMessage(), - } - } - } - - return nil -} - -func isResourceNotFound(errObj *RequestError) bool { - if errObj != nil && errObj.Code == "Request_ResourceNotFound" { - return true - } - return false -} - func isNotFoundErrorPredicate(notFoundErrors []string) plugin.ErrorPredicate { return func(err error) bool { if err != nil { diff --git a/go.mod b/go.mod index 109bf05..73b2403 100644 --- a/go.mod +++ b/go.mod @@ -11,67 +11,83 @@ require ( github.com/microsoft/kiota-authentication-azure-go v0.3.1 github.com/microsoftgraph/msgraph-sdk-go v0.30.0 github.com/microsoftgraph/msgraph-sdk-go-core v0.26.2 - github.com/turbot/go-kit v0.3.0 - github.com/turbot/steampipe-plugin-sdk/v3 v3.1.0 + github.com/turbot/go-kit v0.4.0 + github.com/turbot/steampipe-plugin-sdk/v3 v3.3.2 ) require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/agext/levenshtein v1.2.1 // indirect + github.com/agext/levenshtein v1.2.2 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/btubbs/datetime v0.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.1.3 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cjlapao/common-go v0.0.20 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/fatih/color v1.13.0 // indirect - github.com/gertd/go-pluralize v0.2.0 // indirect + github.com/gertd/go-pluralize v0.2.1 // indirect github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v0.15.0 // indirect - github.com/hashicorp/go-plugin v1.4.3 // indirect + github.com/hashicorp/go-hclog v1.2.0 // indirect + github.com/hashicorp/go-plugin v1.4.4 // indirect github.com/hashicorp/go-retryablehttp v0.7.0 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect - github.com/hashicorp/go-version v1.4.0 // indirect - github.com/hashicorp/hcl/v2 v2.11.1 // indirect - github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect + github.com/hashicorp/go-version v1.5.0 // indirect + github.com/hashicorp/hcl/v2 v2.12.0 // indirect + github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect github.com/microsoft/kiota-abstractions-go v0.8.1 // indirect github.com/microsoft/kiota-http-go v0.5.2 // indirect github.com/microsoft/kiota-serialization-json-go v0.5.5 // indirect github.com/microsoft/kiota-serialization-text-go v0.4.1 // indirect - github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect - github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect - github.com/mitchellh/mapstructure v1.3.3 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/oklog/run v1.0.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/sethvargo/go-retry v0.1.0 // indirect github.com/stevenle/topsort v0.0.0-20130922064739-8130c1d7596b // indirect github.com/stretchr/testify v1.7.2 // indirect github.com/tkrajina/go-reflector v0.5.4 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/zclconf/go-cty v1.10.0 // indirect + go.opentelemetry.io/otel v1.7.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 // indirect + go.opentelemetry.io/otel/metric v0.30.0 // indirect + go.opentelemetry.io/otel/sdk v1.7.0 // indirect + go.opentelemetry.io/otel/sdk/metric v0.30.0 // indirect + go.opentelemetry.io/otel/trace v1.7.0 // indirect + go.opentelemetry.io/proto/otlp v0.16.0 // indirect golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 // indirect golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect - golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect - golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect - google.golang.org/grpc v1.44.0 // indirect + google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac // indirect + google.golang.org/grpc v1.46.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 1ce7609..6314997 100644 --- a/go.sum +++ b/go.sum @@ -41,19 +41,26 @@ github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7 github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= +github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/btubbs/datetime v0.1.1 h1:KuV+F9tyq/hEnezmKZNGk8dzqMVsId6EpFVrQCfA3To= github.com/btubbs/datetime v0.1.1/go.mod h1:n2BZ/2ltnRzNiz27aE3wUb2onNttQdC+WFxAoks5jJM= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -66,8 +73,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -84,20 +93,27 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/gertd/go-pluralize v0.2.0 h1:VzWNnxkUo3wkW2Nmp+3ieHSTQQ0LBHeSVxlKsQPQ+UY= -github.com/gertd/go-pluralize v0.2.0/go.mod h1:4ouO1Ndf/r7sZMorwp4Sbfw80lUni+sd+o3qJR8L9To= +github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA= +github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= @@ -143,8 +159,10 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -162,33 +180,33 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.15.0 h1:qMuK0wxsoW4D0ddCCYwPSTm4KQv1X1ke3WmPWZ0Mvsk= -github.com/hashicorp/go-hclog v0.15.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= -github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= +github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= -github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= +github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc= -github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/hcl/v2 v2.12.0 h1:PsYxySWpMD4KPaoJLnsHwtK5Qptvj/4Q6s0t4sUxZf4= +github.com/hashicorp/hcl/v2 v2.12.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -211,8 +229,9 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/microsoft/kiota-abstractions-go v0.8.1 h1:ACCwRwddJYOx+SRqfgcR8Wo8PZTd4g+JMa8lY8ABy+4= github.com/microsoft/kiota-abstractions-go v0.8.1/go.mod h1:05aCidCKhzer+yfhGeePaMUY3MH+wrAkQztBVEreTtc= github.com/microsoft/kiota-authentication-azure-go v0.3.1 h1:jKZcFPCaNk4WSqS39mmnPvpXlBxc4DhX0zIQVYu2b4U= @@ -227,12 +246,14 @@ github.com/microsoftgraph/msgraph-sdk-go v0.30.0 h1:HsQHy5YBHQABxQOfzWovkZw7ee4z github.com/microsoftgraph/msgraph-sdk-go v0.30.0/go.mod h1:HsVKM/WvckKfF9CM1y5JNCfE9DmFKxz5/vJ3fHcZJkU= github.com/microsoftgraph/msgraph-sdk-go-core v0.26.2 h1:rYNyzWdTM9W7ePbiUX3pO0rpBgGFo6CxoN0Qh3QqDI0= github.com/microsoftgraph/msgraph-sdk-go-core v0.26.2/go.mod h1:kcTY0sEZ/LOJiSj/1OMxcs0T51uodJ/bOeVfWo4lo/s= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -245,29 +266,32 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sethvargo/go-retry v0.1.0 h1:8sPqlWannzcReEcYjHSNw9becsiYudcwTD7CasGjQaI= github.com/sethvargo/go-retry v0.1.0/go.mod h1:JzIOdZqQDNpPkQDmcqgtteAcxFLtYpNF/zJCM1ysDg8= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stevenle/topsort v0.0.0-20130922064739-8130c1d7596b h1:wJSBFlabo96ySlmSX0a02WAPyGxagzTo9c5sk3sHP3E= github.com/stevenle/topsort v0.0.0-20130922064739-8130c1d7596b/go.mod h1:YIyOMT17IKD8FbLO8RfCJZd2qAZiOnIfuYePIeESwWc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/tkrajina/go-reflector v0.5.4 h1:dS9aJEa/eYNQU/fwsb5CSiATOxcNyA/gG/A7a582D5s= github.com/tkrajina/go-reflector v0.5.4/go.mod h1:9PyLgEOzc78ey/JmQQHbW8cQJ1oucLlNQsg8yFvkVk8= -github.com/turbot/go-kit v0.3.0 h1:o4zZIO1ovdmJ2bHWOdXnnt8jJMIDGqYSkZvBREzFeMQ= -github.com/turbot/go-kit v0.3.0/go.mod h1:SBdPRngbEfYubiR81iAVtO43oPkg1+ASr+XxvgbH7/k= -github.com/turbot/steampipe-plugin-sdk/v3 v3.1.0 h1:qMUo/60UwWN7OW1Q+FSrZfwlJ+OMd+uAETRBALLRUSY= -github.com/turbot/steampipe-plugin-sdk/v3 v3.1.0/go.mod h1:F43pC9q0w4AxISFMr1O2OG/BR1xQ0kJWXYJu8FDMuq4= +github.com/turbot/go-kit v0.4.0 h1:EdD7Bf2EGAjvHRGQxRiWpDawzZSk3T+eghqbj74qiSc= +github.com/turbot/go-kit v0.4.0/go.mod h1:SBdPRngbEfYubiR81iAVtO43oPkg1+ASr+XxvgbH7/k= +github.com/turbot/steampipe-plugin-sdk/v3 v3.3.2 h1:bBkefHcsYLJnaPgdEFBMF3Qc11ImSzeodGu4Hl2zfQ8= +github.com/turbot/steampipe-plugin-sdk/v3 v3.3.2/go.mod h1:8r7CDDlrSUd5iUgPlvPa5ttlZ4OEFNMHm8fdwgnn5WM= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= @@ -276,6 +300,7 @@ github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT0 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= @@ -286,7 +311,31 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM= +go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 h1:7Yxsak1q4XrJ5y7XBnNwqWx9amMZvoidCctv62XOQ6Y= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0/go.mod h1:M1hVZHNxcbkAlcvrOMlpQ4YOO3Awf+4N2dxkZL3xm04= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.30.0 h1:Os0ds8fJp2AUa9DNraFWIycgUzevz47i6UvnSh+8LQ0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.30.0/go.mod h1:8Lz1GGcrx1kPGE3zqDrK7ZcPzABEfIQqBjq7roQa5ZA= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.30.0 h1:7E8znQuiqnaFDDl1zJYUpoqHteZI6u2rrcxH3Gwoiis= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.30.0/go.mod h1:RejW0QAFotPIixlFZKZka4/70S5UaFOqDO9DYOgScIs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 h1:cMDtmgJ5FpRvqx9x2Aq+Mm0O6K/zcUkH73SFz20TuBw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0/go.mod h1:ceUgdyfNv4h4gLxHR0WNfDiiVmZFodZhZSbOLhpxqXE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 h1:MFAyzUPrTwLOwCi+cltN0ZVyy4phU41lwH+lyMyQTS4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0/go.mod h1:E+/KKhwOSw8yoPxSSuUHG6vKppkvhN+S1Jc7Nib3k3o= +go.opentelemetry.io/otel/metric v0.30.0 h1:Hs8eQZ8aQgs0U49diZoaS6Uaxw3+bBE3lcMUKBFIk3c= +go.opentelemetry.io/otel/metric v0.30.0/go.mod h1:/ShZ7+TS4dHzDFmfi1kSXMhMVubNoP0oIaBp70J6UXU= +go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0= +go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU= +go.opentelemetry.io/otel/sdk/metric v0.30.0 h1:XTqQ4y3erR2Oj8xSAOL5ovO5011ch2ELg51z4fVkpME= +go.opentelemetry.io/otel/sdk/metric v0.30.0/go.mod h1:8AKFRi5HyvTR0RRty3paN1aMC9HMT+NzcEhw/BLkLX8= +go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o= +go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.16.0 h1:WHzDWdXUvbc5bG2ObdrGfaNpQz7ft7QN9HHmJlbiB1E= +go.opentelemetry.io/proto/otlp v0.16.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -326,7 +375,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -354,7 +403,9 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -363,8 +414,9 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -373,6 +425,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -404,13 +457,16 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -464,6 +520,7 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -493,7 +550,6 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -523,9 +579,10 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac h1:qSNTkEN+L2mvWcLgJOR+8bdHX9rN/IdU3A1Ghpfb1Rg= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -540,8 +597,11 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.44.0 h1:weqSxi/TMs1SqFRMHCtBgXRs8k3X39QIDEZ0pRcttUg= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -554,6 +614,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 4d7e3b70207e029469163ee3d5599bb87a7b4abf Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Tue, 26 Jul 2022 21:22:35 +0530 Subject: [PATCH 06/21] Update error handling and add missing columns --- azuread/helpers.go | 40 ++++++++++++ azuread/table_azuread_application1.go | 2 +- azuread/table_azuread_group1.go | 46 ++----------- azuread/table_azuread_identity_provider1.go | 72 +++++++++------------ azuread/table_azuread_service_principal1.go | 2 +- azuread/table_azuread_sign_in_report1.go | 16 ++++- 6 files changed, 93 insertions(+), 85 deletions(-) diff --git a/azuread/helpers.go b/azuread/helpers.go index 0b9da1f..de2e4c4 100644 --- a/azuread/helpers.go +++ b/azuread/helpers.go @@ -553,6 +553,46 @@ func (servicePrincipal *ADServicePrincipalInfo) ServicePrincipalKeyCredentials() return keyCredentials } +func (servicePrincipal *ADServicePrincipalInfo) ServicePrincipalOauth2PermissionScopes() []map[string]interface{} { + if servicePrincipal.GetOauth2PermissionScopes() == nil { + return nil + } + + oauth2PermissionScopes := []map[string]interface{}{} + for _, p := range servicePrincipal.GetOauth2PermissionScopes() { + data := map[string]interface{}{} + if p.GetAdminConsentDescription() != nil { + data["adminConsentDescription"] = *p.GetAdminConsentDescription() + } + if p.GetAdminConsentDisplayName() != nil { + data["adminConsentDisplayName"] = *p.GetAdminConsentDisplayName() + } + if p.GetId() != nil { + data["id"] = *p.GetId() + } + if p.GetIsEnabled() != nil { + data["isEnabled"] = *p.GetIsEnabled() + } + if p.GetType() != nil { + data["type"] = *p.GetType() + } + if p.GetOrigin() != nil { + data["origin"] = *p.GetOrigin() + } + if p.GetUserConsentDescription() != nil { + data["userConsentDescription"] = p.GetUserConsentDescription() + } + if p.GetUserConsentDisplayName() != nil { + data["userConsentDisplayName"] = p.GetUserConsentDisplayName() + } + if p.GetValue() != nil { + data["value"] = p.GetValue() + } + oauth2PermissionScopes = append(oauth2PermissionScopes, data) + } + return oauth2PermissionScopes +} + func (servicePrincipal *ADServicePrincipalInfo) ServicePrincipalPasswordCredentials() []map[string]interface{} { if servicePrincipal.GetPasswordCredentials() == nil { return nil diff --git a/azuread/table_azuread_application1.go b/azuread/table_azuread_application1.go index 1ac4df6..31b1d00 100644 --- a/azuread/table_azuread_application1.go +++ b/azuread/table_azuread_application1.go @@ -52,7 +52,7 @@ func tableAzureAdApplicationTest() *plugin.Table { {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The date and time the application was registered. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time.", Transform: transform.FromMethod("GetCreatedDateTime")}, {Name: "description", Type: proto.ColumnType_STRING, Description: "Free text field to provide a description of the application object to end users.", Transform: transform.FromMethod("GetDescription")}, // {Name: "is_authorization_service_enabled", Type: proto.ColumnType_BOOL, Description: "Is authorization service enabled."}, - {Name: "oauth2_require_post_response", Type: proto.ColumnType_BOOL, Description: "Specifies whether, as part of OAuth 2.0 token requests, Azure AD allows POST requests, as opposed to GET requests. The default is false, which specifies that only GET requests are allowed.", Transform: transform.FromMethod("GetOauth2RequirePostResponse")}, + {Name: "oauth2_require_post_response", Type: proto.ColumnType_BOOL, Description: "Specifies whether, as part of OAuth 2.0 token requests, Azure AD allows POST requests, as opposed to GET requests. The default is false, which specifies that only GET requests are allowed.", Transform: transform.FromMethod("GetOauth2RequirePostResponse"), Default: false}, {Name: "publisher_domain", Type: proto.ColumnType_STRING, Description: "The verified publisher domain for the application.", Transform: transform.FromMethod("GetPublisherDomain")}, {Name: "sign_in_audience", Type: proto.ColumnType_STRING, Description: "Specifies the Microsoft accounts that are supported for the current application.", Transform: transform.FromMethod("GetSignInAudience")}, diff --git a/azuread/table_azuread_group1.go b/azuread/table_azuread_group1.go index a90acd6..025fd63 100644 --- a/azuread/table_azuread_group1.go +++ b/azuread/table_azuread_group1.go @@ -14,7 +14,6 @@ import ( "github.com/microsoftgraph/msgraph-sdk-go/groups/item/owners" "github.com/microsoftgraph/msgraph-sdk-go/models" - "github.com/turbot/go-kit/helpers" "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" @@ -29,12 +28,15 @@ func tableAzureAdGroupTest() *plugin.Table { Get: &plugin.GetConfig{ Hydrate: getAdGroupTest, IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound"}), + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), }, KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ Hydrate: listAdGroupsTest, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery", "Invalid filter clause"}), + }, KeyColumns: plugin.KeyColumnSlice{ // Key fields {Name: "display_name", Require: plugin.Optional}, @@ -69,7 +71,7 @@ func tableAzureAdGroupTest() *plugin.Table { // } // } // } - // {Name: "is_subscribed_by_mail", Type: proto.ColumnType_BOOL, Description: "Indicates whether the signed-in user is subscribed to receive email conversations. Default value is true."}, + // {Name: "is_subscribed_by_mail", Type: proto.ColumnType_BOOL, Description: "Indicates whether the signed-in user is subscribed to receive email conversations. Default value is true.", Transform: transform.FromMethod("GetIsSubscribedByMail")}, {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the group, for example, \"serviceadmins@contoso.onmicrosoft.com\".", Transform: transform.FromMethod("GetMail")}, {Name: "mail_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is mail-enabled.", Transform: transform.FromMethod("GetMailEnabled")}, @@ -128,12 +130,6 @@ func listAdGroupsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydrat equalQuals := d.KeyColumnQuals quals := d.Quals - // Check for query context and requests only for queried columns - givenColumns := d.QueryContext.Columns - selectColumns := buildGroupRequestFields(ctx, givenColumns) - - input.Select = selectColumns - var queryFilter string filter := buildGroupQueryFilter(equalQuals) filter = append(filter, buildGroupBoolNEFilter(quals)...) @@ -192,12 +188,7 @@ func getAdGroupTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateD return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - // Check for query context and requests only for queried columns - givenColumns := d.QueryContext.Columns - selectColumns := buildGroupRequestFields(ctx, givenColumns) - input := &item.GroupItemRequestBuilderGetQueryParameters{} - input.Select = selectColumns options := &item.GroupItemRequestBuilderGetRequestConfiguration{ QueryParameters: input, @@ -331,33 +322,6 @@ func adGroupTitle(_ context.Context, d *transform.TransformData) (interface{}, e return title, nil } -func buildGroupRequestFields(ctx context.Context, queryColumns []string) []string { - var selectColumns []string - - if !helpers.StringSliceContains(queryColumns, "id") { - queryColumns = append(queryColumns, "id") - } - - if !helpers.StringSliceContains(queryColumns, "assignedLabels") && helpers.StringSliceContains(queryColumns, "tags") { - queryColumns = append(queryColumns, "assignedLabels") - } - - for _, columnName := range queryColumns { - if columnName == "title" || columnName == "tags" || columnName == "filter" || columnName == "tenant_id" { - continue - } - - // Uses separate hydrate functions - if columnName == "member_ids" || columnName == "owner_ids" { - continue - } - - selectColumns = append(selectColumns, strcase.ToLowerCamel(columnName)) - } - - return selectColumns -} - func buildGroupQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { filters := []string{} diff --git a/azuread/table_azuread_identity_provider1.go b/azuread/table_azuread_identity_provider1.go index 80eeb25..a4cf8e6 100644 --- a/azuread/table_azuread_identity_provider1.go +++ b/azuread/table_azuread_identity_provider1.go @@ -22,25 +22,27 @@ func tableAzureAdIdentityProviderTest() *plugin.Table { Name: "azuread_identity_provider_test", Description: "Represents an Azure Active Directory (Azure AD) identity provider", Get: &plugin.GetConfig{ - Hydrate: getAdIdentityProviderTest, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), - KeyColumns: plugin.SingleColumn("id"), + Hydrate: getAdIdentityProviderTest, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, + KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ Hydrate: listAdIdentityProvidersTest, }, Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "The ID of the identity provider.", Transform: transform.FromGo()}, - {Name: "name", Type: proto.ColumnType_STRING, Description: "The display name of the identity provider."}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The ID of the identity provider.", Transform: transform.FromMethod("GetId")}, + {Name: "name", Type: proto.ColumnType_STRING, Description: "The display name of the identity provider.", Transform: transform.FromMethod("GetDisplayName")}, // Other fields - {Name: "type", Type: proto.ColumnType_STRING, Description: "The identity provider type is a required field. For B2B scenario: Google, Facebook. For B2C scenario: Microsoft, Google, Amazon, LinkedIn, Facebook, GitHub, Twitter, Weibo, QQ, WeChat, OpenIDConnect."}, - // {Name: "client_id", Type: proto.ColumnType_STRING, Description: "The client ID for the application. This is the client ID obtained when registering the application with the identity provider."}, - // {Name: "client_secret", Type: proto.ColumnType_STRING, Description: "The client secret for the application. This is the client secret obtained when registering the application with the identity provider. This is write-only. A read operation will return ****."}, + {Name: "type", Type: proto.ColumnType_STRING, Description: "The identity provider type is a required field. For B2B scenario: Google, Facebook. For B2C scenario: Microsoft, Google, Amazon, LinkedIn, Facebook, GitHub, Twitter, Weibo, QQ, WeChat, OpenIDConnect.", Transform: transform.FromMethod("GetIdentityProviderType")}, + // {Name: "client_id", Type: proto.ColumnType_STRING, Description: "The client ID for the application. This is the client ID obtained when registering the application with the identity provider.", Transform: transform.FromMethod("GetClientId")}, + // {Name: "client_secret", Type: proto.ColumnType_STRING, Description: "The client secret for the application. This is the client secret obtained when registering the application with the identity provider. This is write-only. A read operation will return ****.", Transform: transform.FromMethod("GetClientSecret")}, // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "ID")}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adIdentityProviderTitle)}, {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } @@ -71,23 +73,15 @@ func listAdIdentityProvidersTest(ctx context.Context, d *plugin.QueryData, _ *pl result, err := client.Identity().IdentityProviders().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) - return nil, errors.New(fmt.Sprintf("failed to list identity providers. Code: %s Message: %s", errObj.Code, errObj.Message)) + return nil, errObj } - pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateIdentityProviderCollectionResponseFromDiscriminatorValue) + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateBuiltInIdentityProviderFromDiscriminatorValue) err = pageIterator.Iterate(func(pageItem interface{}) bool { - identityProvider := pageItem.(models.IdentityProviderBaseable) - - result := map[string]interface{}{ - "ID": identityProvider.GetId(), - "Name": identityProvider.GetDisplayName(), - "Type": identityProvider.GetType(), - // "ClientId": identityProvider.GetClientId(), - // "ClientSecret": identityProvider.GetClientSecret(), - } + identityProvider := pageItem.(*models.BuiltInIdentityProvider) - d.StreamListItem(ctx, result) + d.StreamListItem(ctx, identityProvider) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { @@ -100,21 +94,15 @@ func listAdIdentityProvidersTest(ctx context.Context, d *plugin.QueryData, _ *pl return nil, nil } -//// Hydrate Functions +//// HYDRATE FUNCTIONS +// https://docs.microsoft.com/en-us/graph/api/identityproviderbase-get?view=graph-rest-1.0&tabs=go -// Need to validate. -// Getting following error -// Code: AADB2C90063 Message: There is a problem with the service. func getAdIdentityProviderTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { identityProviderId := d.KeyColumnQuals["id"].GetStringValue() if identityProviderId == "" { return nil, nil } - if identityProviderId == "" { - return nil, nil - } - // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { @@ -124,20 +112,24 @@ func getAdIdentityProviderTest(ctx context.Context, d *plugin.QueryData, h *plug identityProvider, err := client.Identity().IdentityProvidersById(identityProviderId).Get() if err != nil { errObj := getErrorObject(err) - if isResourceNotFound(errObj) { - return nil, nil - } + return nil, errObj + } + + return identityProvider, nil +} - return nil, errors.New(fmt.Sprintf("failed to get identity provider. Code: %s Message: %s", errObj.Code, errObj.Message)) +//// TRANSFORM FUNCTIONS + +func adIdentityProviderTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(models.IdentityProviderBaseable) + if data == nil { + return nil, nil } - result := map[string]interface{}{ - "ID": identityProvider.GetId(), - "Name": identityProvider.GetDisplayName(), - "Type": identityProvider.GetType(), - // "ClientId": identityProvider.GetClientId(), - // "ClientSecret": identityProvider.GetClientSecret(), + title := data.GetDisplayName() + if title == nil { + title = data.GetId() } - return result, nil + return title, nil } diff --git a/azuread/table_azuread_service_principal1.go b/azuread/table_azuread_service_principal1.go index d7319a1..5eecf41 100644 --- a/azuread/table_azuread_service_principal1.go +++ b/azuread/table_azuread_service_principal1.go @@ -69,7 +69,7 @@ func tableAzureAdServicePrincipalTest() *plugin.Table { {Name: "notification_email_addresses", Type: proto.ColumnType_JSON, Description: "Specifies the list of email addresses where Azure AD sends a notification when the active certificate is near the expiration date. This is only for the certificates used to sign the SAML token issued for Azure AD Gallery applications.", Transform: transform.FromMethod("GetNotificationEmailAddresses")}, {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getServicePrincipalOwners, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, {Name: "password_credentials", Type: proto.ColumnType_JSON, Description: "Represents a password credential associated with a service principal.", Transform: transform.FromMethod("ServicePrincipalPasswordCredentials")}, - // {Name: "published_permission_scopes", Type: proto.ColumnType_JSON, Description: "The published permission scopes."}, + {Name: "published_permission_scopes", Type: proto.ColumnType_JSON, Description: "The published permission scopes.", Transform: transform.FromMethod("ServicePrincipalOauth2PermissionScopes")}, {Name: "reply_urls", Type: proto.ColumnType_JSON, Description: "The URLs that user tokens are sent to for sign in with the associated application, or the redirect URIs that OAuth 2.0 authorization codes and access tokens are sent to for the associated application.", Transform: transform.FromMethod("GetReplyUrls")}, {Name: "service_principal_names", Type: proto.ColumnType_JSON, Description: "Contains the list of identifiersUris, copied over from the associated application. Additional values can be added to hybrid applications. These values can be used to identify the permissions exposed by this app within Azure AD.", Transform: transform.FromMethod("GetServicePrincipalNames")}, {Name: "tags_src", Type: proto.ColumnType_JSON, Description: "Custom strings that can be used to categorize and identify the service principal.", Transform: transform.FromMethod("GetTags")}, diff --git a/azuread/table_azuread_sign_in_report1.go b/azuread/table_azuread_sign_in_report1.go index 387be6a..3407260 100644 --- a/azuread/table_azuread_sign_in_report1.go +++ b/azuread/table_azuread_sign_in_report1.go @@ -52,7 +52,7 @@ func tableAzureAdSignInReportTest() *plugin.Table { {Name: "resource_id", Type: proto.ColumnType_STRING, Description: "ID of the resource that the user signed into.", Transform: transform.FromMethod("GetResourceId")}, // Json fields - {Name: "risk_event_types", Type: proto.ColumnType_JSON, Description: "Risk event types associated with the sign-in. The possible values are: unlikelyTravel, anonymizedIPAddress, maliciousIPAddress, unfamiliarFeatures, malwareInfectedIPAddress, suspiciousIPAddress, leakedCredentials, investigationsThreatIntelligence, generic, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskEventTypes")}, + {Name: "risk_event_types", Type: proto.ColumnType_JSON, Description: "Risk event types associated with the sign-in. The possible values are: unlikelyTravel, anonymizedIPAddress, maliciousIPAddress, unfamiliarFeatures, malwareInfectedIPAddress, suspiciousIPAddress, leakedCredentials, investigationsThreatIntelligence, generic, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskEventTypes").Transform(formatSignInReportRiskEventTypes)}, {Name: "status", Type: proto.ColumnType_JSON, Description: "Sign-in status. Includes the error code and description of the error (in case of a sign-in failure).", Transform: transform.FromMethod("SignInStatus")}, {Name: "device_detail", Type: proto.ColumnType_JSON, Description: "Device information from where the sign-in occurred; includes device ID, operating system, and browser.", Transform: transform.FromMethod("SignInDeviceDetail")}, {Name: "location", Type: proto.ColumnType_JSON, Description: " Provides the city, state, and country code where the sign-in originated.", Transform: transform.FromMethod("SignInLocation")}, @@ -115,7 +115,7 @@ func listAdSignInReportsTest(ctx context.Context, d *plugin.QueryData, _ *plugin return nil, nil } -//// Hydrate Functions +//// HYDRATE FUNCTIONS func getAdSignInReportTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { signInID := d.KeyColumnQuals["id"].GetStringValue() @@ -137,3 +137,15 @@ func getAdSignInReportTest(ctx context.Context, d *plugin.QueryData, h *plugin.H return &ADSignInReportInfo{signIn}, nil } + +//// TRANSFORM FUNCTIONS + +func formatSignInReportRiskEventTypes(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADSignInReportInfo) + riskEventTypes := data.GetRiskEventTypes() + if riskEventTypes == nil || len(riskEventTypes) == 0 { + return nil, nil + } + + return riskEventTypes, nil +} From ae04dcc3e5bc14e9c8c19b75f0aaa7f750028ccd Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Wed, 27 Jul 2022 18:10:48 +0530 Subject: [PATCH 07/21] Add missing column from user --- azuread/table_azuread_sign_in_report1.go | 2 +- azuread/table_azuread_user1.go | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/azuread/table_azuread_sign_in_report1.go b/azuread/table_azuread_sign_in_report1.go index 3407260..2a04761 100644 --- a/azuread/table_azuread_sign_in_report1.go +++ b/azuread/table_azuread_sign_in_report1.go @@ -51,7 +51,7 @@ func tableAzureAdSignInReportTest() *plugin.Table { {Name: "resource_display_name", Type: proto.ColumnType_STRING, Description: "Name of the resource the user signed into.", Transform: transform.FromMethod("GetResourceDisplayName")}, {Name: "resource_id", Type: proto.ColumnType_STRING, Description: "ID of the resource that the user signed into.", Transform: transform.FromMethod("GetResourceId")}, - // Json fields + // JSON fields {Name: "risk_event_types", Type: proto.ColumnType_JSON, Description: "Risk event types associated with the sign-in. The possible values are: unlikelyTravel, anonymizedIPAddress, maliciousIPAddress, unfamiliarFeatures, malwareInfectedIPAddress, suspiciousIPAddress, leakedCredentials, investigationsThreatIntelligence, generic, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskEventTypes").Transform(formatSignInReportRiskEventTypes)}, {Name: "status", Type: proto.ColumnType_JSON, Description: "Sign-in status. Includes the error code and description of the error (in case of a sign-in failure).", Transform: transform.FromMethod("SignInStatus")}, {Name: "device_detail", Type: proto.ColumnType_JSON, Description: "Device information from where the sign-in occurred; includes device ID, operating system, and browser.", Transform: transform.FromMethod("SignInDeviceDetail")}, diff --git a/azuread/table_azuread_user1.go b/azuread/table_azuread_user1.go index 7a2422d..26da272 100644 --- a/azuread/table_azuread_user1.go +++ b/azuread/table_azuread_user1.go @@ -63,7 +63,7 @@ func tableAzureAdUserTest() *plugin.Table { {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the user, for example, jeff@contoso.onmicrosoft.com.", Transform: transform.FromMethod("GetMail")}, {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user.", Transform: transform.FromMethod("GetMailNickname")}, {Name: "password_policies", Type: proto.ColumnType_STRING, Description: "Specifies password policies for the user. This value is an enumeration with one possible value being DisableStrongPassword, which allows weaker passwords than the default policy to be specified. DisablePasswordExpiration can also be specified. The two may be specified together; for example: DisablePasswordExpiration, DisableStrongPassword.", Transform: transform.FromMethod("GetPasswordPolicies")}, - // {Name: "refresh_tokens_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph)."}, + {Name: "refresh_tokens_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph).", Transform: transform.FromMethod("GetSignInSessionsValidFromDateTime")}, {Name: "sign_in_sessions_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph).", Transform: transform.FromMethod("GetSignInSessionsValidFromDateTime")}, {Name: "usage_location", Type: proto.ColumnType_STRING, Description: "A two letter country code (ISO standard 3166), required for users that will be assigned licenses due to legal requirement to check for availability of services in countries.", Transform: transform.FromMethod("GetUsageLocation")}, @@ -203,6 +203,11 @@ func buildUserRequestFields(ctx context.Context, queryColumns []string) ([]strin continue } + if columnName == "refresh_tokens_valid_from_date_time" { + selectColumns = append(selectColumns, "signInSessionsValidFromDateTime") + continue + } + selectColumns = append(selectColumns, strcase.ToLowerCamel(columnName)) } From 3941c63fb72262bfee480513261387b08ba9f8ab Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Wed, 27 Jul 2022 20:59:47 +0530 Subject: [PATCH 08/21] Migrated tables to new format and removed test tables --- azuread/plugin.go | 36 +- azuread/table_azuread_application.go | 255 +++++++----- azuread/table_azuread_application1.go | 248 ------------ ...table_azuread_conditional_access_policy.go | 187 +++++---- ...able_azuread_conditional_access_policy1.go | 190 --------- azuread/table_azuread_directory_role.go | 148 ++++--- azuread/table_azuread_directory_role1.go | 162 -------- azuread/table_azuread_domain.go | 118 +++--- azuread/table_azuread_domain1.go | 131 ------ azuread/table_azuread_group.go | 375 ++++++++++++------ azuread/table_azuread_group1.go | 374 ----------------- azuread/table_azuread_service_principal.go | 294 +++++++++----- azuread/table_azuread_service_principal1.go | 290 -------------- azuread/table_azuread_sign_in_report.go | 165 ++++---- azuread/table_azuread_sign_in_report1.go | 151 ------- azuread/table_azuread_user.go | 229 +++++++---- azuread/table_azuread_user1.go | 254 ------------ azuread/utils.go | 12 + 18 files changed, 1160 insertions(+), 2459 deletions(-) delete mode 100644 azuread/table_azuread_application1.go delete mode 100644 azuread/table_azuread_conditional_access_policy1.go delete mode 100644 azuread/table_azuread_directory_role1.go delete mode 100644 azuread/table_azuread_domain1.go delete mode 100644 azuread/table_azuread_group1.go delete mode 100644 azuread/table_azuread_service_principal1.go delete mode 100644 azuread/table_azuread_sign_in_report1.go delete mode 100644 azuread/table_azuread_user1.go diff --git a/azuread/plugin.go b/azuread/plugin.go index a52291d..781bfea 100644 --- a/azuread/plugin.go +++ b/azuread/plugin.go @@ -22,24 +22,24 @@ func Plugin(ctx context.Context) *plugin.Plugin { Schema: ConfigSchema, }, TableMap: map[string]*plugin.Table{ - "azuread_application": tableAzureAdApplication(), - "azuread_conditional_access_policy": tableAzureAdConditionalAccessPolicy(), - "azuread_directory_role": tableAzureAdDirectoryRole(), - "azuread_domain": tableAzureAdDomain(), - "azuread_group": tableAzureAdGroup(), - "azuread_identity_provider": tableAzureAdIdentityProvider(), - "azuread_service_principal": tableAzureAdServicePrincipal(), - "azuread_sign_in_report": tableAzureAdSignInReport(), - "azuread_user": tableAzureAdUser(), - "azuread_user_test": tableAzureAdUserTest(), - "azuread_group_test": tableAzureAdGroupTest(), - "azuread_domain_test": tableAzureAdDomainTest(), - "azuread_identity_provider_test": tableAzureAdIdentityProviderTest(), - "azuread_directory_role_test": tableAzureAdDirectoryRoleTest(), - "azuread_application_test": tableAzureAdApplicationTest(), - "azuread_service_principal_test": tableAzureAdServicePrincipalTest(), - "azuread_sign_in_report_test": tableAzureAdSignInReportTest(), - "azuread_conditional_access_policy_test": tableAzureAdConditionalAccessPolicyTest(), + "azuread_application": tableAzureAdApplication(), + "azuread_conditional_access_policy": tableAzureAdConditionalAccessPolicy(), + "azuread_directory_role": tableAzureAdDirectoryRole(), + "azuread_domain": tableAzureAdDomain(), + "azuread_group": tableAzureAdGroup(), + "azuread_identity_provider": tableAzureAdIdentityProvider(), + "azuread_service_principal": tableAzureAdServicePrincipal(), + "azuread_sign_in_report": tableAzureAdSignInReport(), + "azuread_user": tableAzureAdUser(), + "azuread_identity_provider_test": tableAzureAdIdentityProviderTest(), + // "azuread_application_test": tableAzureAdApplicationTest(), + // "azuread_conditional_access_policy_test": tableAzureAdConditionalAccessPolicyTest(), + // "azuread_directory_role_test": tableAzureAdDirectoryRoleTest(), + // "azuread_domain_test": tableAzureAdDomainTest(), + // "azuread_group_test": tableAzureAdGroupTest(), + // "azuread_service_principal_test": tableAzureAdServicePrincipalTest(), + // "azuread_sign_in_report_test": tableAzureAdSignInReportTest(), + // "azuread_user_test": tableAzureAdUserTest(), }, } diff --git a/azuread/table_azuread_application.go b/azuread/table_azuread_application.go index 601857f..8d136bf 100644 --- a/azuread/table_azuread_application.go +++ b/azuread/table_azuread_application.go @@ -2,14 +2,19 @@ package azuread import ( "context" + "errors" + "fmt" "strings" - "github.com/manicminer/hamilton/msgraph" - "github.com/manicminer/hamilton/odata" - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" + "github.com/iancoleman/strcase" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/applications" + "github.com/microsoftgraph/msgraph-sdk-go/applications/item/owners" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" ) //// TABLE DEFINITION @@ -19,13 +24,17 @@ func tableAzureAdApplication() *plugin.Table { Name: "azuread_application", Description: "Represents an Azure Active Directory (Azure AD) application", Get: &plugin.GetConfig{ - Hydrate: getAdApplication, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), + Hydrate: getAdApplication, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ Hydrate: listAdApplications, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Request_UnsupportedQuery"}), + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery"}), + }, KeyColumns: plugin.KeyColumnSlice{ // Key fields {Name: "app_id", Require: plugin.Optional}, @@ -35,33 +44,34 @@ func tableAzureAdApplication() *plugin.Table { }, Columns: []*plugin.Column{ - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the application."}, - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the application.", Transform: transform.FromGo()}, - {Name: "app_id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the application that is assigned to an application by Azure AD."}, - - // // Other fields - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The date and time the application was registered. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time."}, - {Name: "is_authorization_service_enabled", Type: proto.ColumnType_BOOL, Description: "Is authorization service enabled."}, - {Name: "oauth2_require_post_response", Type: proto.ColumnType_BOOL, Description: "Specifies whether, as part of OAuth 2.0 token requests, Azure AD allows POST requests, as opposed to GET requests. The default is false, which specifies that only GET requests are allowed."}, - {Name: "publisher_domain", Type: proto.ColumnType_STRING, Description: "The verified publisher domain for the application."}, - {Name: "sign_in_audience", Type: proto.ColumnType_STRING, Description: "Specifies the Microsoft accounts that are supported for the current application."}, - - // // Json fields - {Name: "api", Type: proto.ColumnType_JSON, Description: "Specifies settings for an application that implements a web API."}, - {Name: "identifier_uris", Type: proto.ColumnType_JSON, Description: "The URIs that identify the application within its Azure AD tenant, or within a verified custom domain if the application is multi-tenant. "}, - {Name: "info", Type: proto.ColumnType_JSON, Description: "Basic profile information of the application such as app's marketing, support, terms of service and privacy statement URLs. The terms of service and privacy statement are surfaced to users through the user consent experience."}, - {Name: "key_credentials", Type: proto.ColumnType_JSON, Description: "The collection of key credentials associated with the application."}, - {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getApplicationOwners, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, - {Name: "parental_control_settings", Type: proto.ColumnType_JSON, Description: "Specifies parental control settings for an application."}, - {Name: "password_credentials", Type: proto.ColumnType_JSON, Description: "The collection of password credentials associated with the application."}, - {Name: "spa", Type: proto.ColumnType_JSON, Description: "Specifies settings for a single-page application, including sign out URLs and redirect URIs for authorization codes and access tokens."}, - {Name: "tags_src", Type: proto.ColumnType_JSON, Description: "Custom strings that can be used to categorize and identify the application.", Transform: transform.FromField("Tags")}, - {Name: "web", Type: proto.ColumnType_JSON, Description: "Specifies settings for a web application."}, - - // // Standard columns - {Name: "tags", Type: proto.ColumnType_JSON, Description: ColumnDescriptionTags, Transform: transform.From(applicationTags)}, - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "ID")}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenantId).WithCache(), Transform: transform.FromValue()}, + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the application.", Transform: transform.FromMethod("GetDisplayName")}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the application.", Transform: transform.FromMethod("GetId")}, + {Name: "app_id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the application that is assigned to an application by Azure AD.", Transform: transform.FromMethod("GetAppId")}, + + // Other fields + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The date and time the application was registered. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time.", Transform: transform.FromMethod("GetCreatedDateTime")}, + {Name: "description", Type: proto.ColumnType_STRING, Description: "Free text field to provide a description of the application object to end users.", Transform: transform.FromMethod("GetDescription")}, + // {Name: "is_authorization_service_enabled", Type: proto.ColumnType_BOOL, Description: "Is authorization service enabled."}, + {Name: "oauth2_require_post_response", Type: proto.ColumnType_BOOL, Description: "Specifies whether, as part of OAuth 2.0 token requests, Azure AD allows POST requests, as opposed to GET requests. The default is false, which specifies that only GET requests are allowed.", Transform: transform.FromMethod("GetOauth2RequirePostResponse"), Default: false}, + {Name: "publisher_domain", Type: proto.ColumnType_STRING, Description: "The verified publisher domain for the application.", Transform: transform.FromMethod("GetPublisherDomain")}, + {Name: "sign_in_audience", Type: proto.ColumnType_STRING, Description: "Specifies the Microsoft accounts that are supported for the current application.", Transform: transform.FromMethod("GetSignInAudience")}, + + // JSON fields + {Name: "api", Type: proto.ColumnType_JSON, Description: "Specifies settings for an application that implements a web API.", Transform: transform.FromMethod("ApplicationAPI")}, + {Name: "identifier_uris", Type: proto.ColumnType_JSON, Description: "The URIs that identify the application within its Azure AD tenant, or within a verified custom domain if the application is multi-tenant.", Transform: transform.FromMethod("GetIdentifierUris")}, + {Name: "info", Type: proto.ColumnType_JSON, Description: "Basic profile information of the application such as app's marketing, support, terms of service and privacy statement URLs. The terms of service and privacy statement are surfaced to users through the user consent experience.", Transform: transform.FromMethod("ApplicationInfo")}, + {Name: "key_credentials", Type: proto.ColumnType_JSON, Description: "The collection of key credentials associated with the application.", Transform: transform.FromMethod("ApplicationKeyCredentials")}, + {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getAdApplicationOwners, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, + {Name: "parental_control_settings", Type: proto.ColumnType_JSON, Description: "Specifies parental control settings for an application.", Transform: transform.FromMethod("ApplicationParentalControlSettings")}, + {Name: "password_credentials", Type: proto.ColumnType_JSON, Description: "The collection of password credentials associated with the application.", Transform: transform.FromMethod("ApplicationPasswordCredentials")}, + {Name: "spa", Type: proto.ColumnType_JSON, Description: "Specifies settings for a single-page application, including sign out URLs and redirect URIs for authorization codes and access tokens.", Transform: transform.FromMethod("ApplicationSpa")}, + {Name: "tags_src", Type: proto.ColumnType_JSON, Description: "Custom strings that can be used to categorize and identify the application.", Transform: transform.FromMethod("GetTags")}, + {Name: "web", Type: proto.ColumnType_JSON, Description: "Specifies settings for a web application.", Transform: transform.FromMethod("ApplicationWeb")}, + + // Standard columns + {Name: "tags", Type: proto.ColumnType_JSON, Description: ColumnDescriptionTags, Transform: transform.From(adApplicationTags)}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adApplicationTitle)}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } } @@ -69,111 +79,170 @@ func tableAzureAdApplication() *plugin.Table { //// LIST FUNCTION func listAdApplications(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - session, err := GetNewSession(ctx, d) + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewApplicationsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - - input := odata.Query{} + // List operations + input := &applications.ApplicationsRequestBuilderGetQueryParameters{} // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. limit := d.QueryContext.Limit if limit != nil { if *limit > 0 && *limit <= 999 { - input.Top = int(*limit) + l := int32(*limit) + input.Top = &l } } - qualsColumnMap := []QualsColumn{ - {"app_id", "string", "appId"}, - {"display_name", "string", "displayName"}, - {"publisher_domain", "string", "publisherDomain"}, + var queryFilter string + equalQuals := d.KeyColumnQuals + filter := buildApplicationQueryFilter(equalQuals) + + if equalQuals["filter"] != nil { + queryFilter = equalQuals["filter"].GetStringValue() + } + + if queryFilter != "" { + input.Filter = &queryFilter + } else if len(filter) > 0 { + joinStr := strings.Join(filter, " and ") + input.Filter = &joinStr } - filter := buildCommaonQueryFilter(qualsColumnMap, d.Quals) - if len(filter) > 0 { - input.Filter = strings.Join(filter, " and ") + options := &applications.ApplicationsRequestBuilderGetRequestConfiguration{ + QueryParameters: input, } - applications, _, err := client.List(ctx, input) + result, err := client.Applications().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { - if isNotFoundError(err) { - return nil, nil - } - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - for _, application := range *applications { - d.StreamListItem(ctx, application) + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateApplicationCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + application := pageItem.(models.Applicationable) + + d.StreamListItem(ctx, &ADApplicationInfo{application}) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { - return nil, nil + return false } - } + + return true + }) + return nil, err } -//// Hydrate Functions +//// HYDRATE FUNCTIONS func getAdApplication(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var applicationId string - if h.Item != nil { - applicationId = *h.Item.(msgraph.Application).ID - } else { - applicationId = d.KeyColumnQuals["id"].GetStringValue() - } - + applicationId := d.KeyColumnQuals["id"].GetStringValue() if applicationId == "" { return nil, nil } - session, err := GetNewSession(ctx, d) + + // Create client + client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewApplicationsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - client.BaseClient.DisableRetries = true - - application, _, err := client.Get(ctx, applicationId, odata.Query{}) + application, err := client.ApplicationsById(applicationId).Get() if err != nil { - if isNotFoundError(err){ - return nil, nil - } - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - return *application, nil + + return &ADApplicationInfo{application}, nil } -func getApplicationOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - application := h.Item.(msgraph.Application) - session, err := GetNewSession(ctx, d) +func getAdApplicationOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - if isNotFoundError(err) { - return nil, nil - } - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewApplicationsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer + application := h.Item.(*ADApplicationInfo) + applicationID := application.GetId() - owners, _, err := client.ListOwners(ctx, *application.ID) + headers := map[string]string{ + "ConsistencyLevel": "eventual", + } + + includeCount := true + requestParameters := &owners.OwnersRequestBuilderGetQueryParameters{ + Count: &includeCount, + } + + config := &owners.OwnersRequestBuilderGetRequestConfiguration{ + Headers: headers, + QueryParameters: requestParameters, + } + + ownerIds := []*string{} + owners, err := client.ApplicationsById(*applicationID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - return owners, nil + + pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(func(pageItem interface{}) bool { + owner := pageItem.(models.DirectoryObjectable) + ownerIds = append(ownerIds, owner.GetId()) + + return true + }) + + return ownerIds, nil +} + +//// TRANSFORM FUNCTIONS + +func adApplicationTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { + application := d.HydrateItem.(*ADApplicationInfo) + tags := application.GetTags() + return TagsToMap(tags) } -//// Transform Function +func adApplicationTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADApplicationInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetId() + } + + return title, nil +} + +func buildApplicationQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { + filters := []string{} + + filterQuals := map[string]string{ + "display_name": "string", + "app_id": "string", + "publisher_domain": "string", + } + + for qual := range filterQuals { + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) + } + } -func applicationTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { - application := d.HydrateItem.(msgraph.Application) - return TagsToMap(*application.Tags) + return filters } diff --git a/azuread/table_azuread_application1.go b/azuread/table_azuread_application1.go deleted file mode 100644 index 31b1d00..0000000 --- a/azuread/table_azuread_application1.go +++ /dev/null @@ -1,248 +0,0 @@ -package azuread - -import ( - "context" - "errors" - "fmt" - "strings" - - "github.com/iancoleman/strcase" - msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" - "github.com/microsoftgraph/msgraph-sdk-go/applications" - "github.com/microsoftgraph/msgraph-sdk-go/applications/item/owners" - "github.com/microsoftgraph/msgraph-sdk-go/models" - - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" -) - -//// TABLE DEFINITION - -func tableAzureAdApplicationTest() *plugin.Table { - return &plugin.Table{ - Name: "azuread_application_test", - Description: "Represents an Azure Active Directory (Azure AD) application", - Get: &plugin.GetConfig{ - Hydrate: getAdApplicationTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), - }, - KeyColumns: plugin.SingleColumn("id"), - }, - List: &plugin.ListConfig{ - Hydrate: listAdApplicationsTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery"}), - }, - KeyColumns: plugin.KeyColumnSlice{ - // Key fields - {Name: "app_id", Require: plugin.Optional}, - {Name: "display_name", Require: plugin.Optional}, - {Name: "publisher_domain", Require: plugin.Optional}, - }, - }, - - Columns: []*plugin.Column{ - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the application.", Transform: transform.FromMethod("GetDisplayName")}, - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the application.", Transform: transform.FromMethod("GetId")}, - {Name: "app_id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the application that is assigned to an application by Azure AD.", Transform: transform.FromMethod("GetAppId")}, - - // Other fields - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The date and time the application was registered. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time.", Transform: transform.FromMethod("GetCreatedDateTime")}, - {Name: "description", Type: proto.ColumnType_STRING, Description: "Free text field to provide a description of the application object to end users.", Transform: transform.FromMethod("GetDescription")}, - // {Name: "is_authorization_service_enabled", Type: proto.ColumnType_BOOL, Description: "Is authorization service enabled."}, - {Name: "oauth2_require_post_response", Type: proto.ColumnType_BOOL, Description: "Specifies whether, as part of OAuth 2.0 token requests, Azure AD allows POST requests, as opposed to GET requests. The default is false, which specifies that only GET requests are allowed.", Transform: transform.FromMethod("GetOauth2RequirePostResponse"), Default: false}, - {Name: "publisher_domain", Type: proto.ColumnType_STRING, Description: "The verified publisher domain for the application.", Transform: transform.FromMethod("GetPublisherDomain")}, - {Name: "sign_in_audience", Type: proto.ColumnType_STRING, Description: "Specifies the Microsoft accounts that are supported for the current application.", Transform: transform.FromMethod("GetSignInAudience")}, - - // Json fields - {Name: "api", Type: proto.ColumnType_JSON, Description: "Specifies settings for an application that implements a web API.", Transform: transform.FromMethod("ApplicationAPI")}, - {Name: "identifier_uris", Type: proto.ColumnType_JSON, Description: "The URIs that identify the application within its Azure AD tenant, or within a verified custom domain if the application is multi-tenant.", Transform: transform.FromMethod("GetIdentifierUris")}, - {Name: "info", Type: proto.ColumnType_JSON, Description: "Basic profile information of the application such as app's marketing, support, terms of service and privacy statement URLs. The terms of service and privacy statement are surfaced to users through the user consent experience.", Transform: transform.FromMethod("ApplicationInfo")}, - {Name: "key_credentials", Type: proto.ColumnType_JSON, Description: "The collection of key credentials associated with the application.", Transform: transform.FromMethod("ApplicationKeyCredentials")}, - {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getAdApplicationOwners, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, - {Name: "parental_control_settings", Type: proto.ColumnType_JSON, Description: "Specifies parental control settings for an application.", Transform: transform.FromMethod("ApplicationParentalControlSettings")}, - {Name: "password_credentials", Type: proto.ColumnType_JSON, Description: "The collection of password credentials associated with the application.", Transform: transform.FromMethod("ApplicationPasswordCredentials")}, - {Name: "spa", Type: proto.ColumnType_JSON, Description: "Specifies settings for a single-page application, including sign out URLs and redirect URIs for authorization codes and access tokens.", Transform: transform.FromMethod("ApplicationSpa")}, - {Name: "tags_src", Type: proto.ColumnType_JSON, Description: "Custom strings that can be used to categorize and identify the application.", Transform: transform.FromMethod("GetTags")}, - {Name: "web", Type: proto.ColumnType_JSON, Description: "Specifies settings for a web application.", Transform: transform.FromMethod("ApplicationWeb")}, - - // Standard columns - {Name: "tags", Type: proto.ColumnType_JSON, Description: ColumnDescriptionTags, Transform: transform.From(adApplicationTags)}, - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adApplicationTitle)}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, - }, - } -} - -//// LIST FUNCTION - -func listAdApplicationsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - // List operations - input := &applications.ApplicationsRequestBuilderGetQueryParameters{} - - // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow - // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. - limit := d.QueryContext.Limit - if limit != nil { - if *limit > 0 && *limit <= 999 { - l := int32(*limit) - input.Top = &l - } - } - - var queryFilter string - equalQuals := d.KeyColumnQuals - filter := buildApplicationQueryFilter(equalQuals) - - if equalQuals["filter"] != nil { - queryFilter = equalQuals["filter"].GetStringValue() - } - - if queryFilter != "" { - input.Filter = &queryFilter - } else if len(filter) > 0 { - joinStr := strings.Join(filter, " and ") - input.Filter = &joinStr - } - - options := &applications.ApplicationsRequestBuilderGetRequestConfiguration{ - QueryParameters: input, - } - - result, err := client.Applications().GetWithRequestConfigurationAndResponseHandler(options, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateApplicationCollectionResponseFromDiscriminatorValue) - - err = pageIterator.Iterate(func(pageItem interface{}) bool { - application := pageItem.(models.Applicationable) - - d.StreamListItem(ctx, &ADApplicationInfo{application}) - - // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true - }) - - return nil, err -} - -//// HYDRATE FUNCTIONS - -func getAdApplicationTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - - applicationId := d.KeyColumnQuals["id"].GetStringValue() - if applicationId == "" { - return nil, nil - } - - // Create client - client, _, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - application, err := client.ApplicationsById(applicationId).Get() - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - return &ADApplicationInfo{application}, nil -} - -func getAdApplicationOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - application := h.Item.(*ADApplicationInfo) - applicationID := application.GetId() - - headers := map[string]string{ - "ConsistencyLevel": "eventual", - } - - includeCount := true - requestParameters := &owners.OwnersRequestBuilderGetQueryParameters{ - Count: &includeCount, - } - - config := &owners.OwnersRequestBuilderGetRequestConfiguration{ - Headers: headers, - QueryParameters: requestParameters, - } - - ownerIds := []*string{} - owners, err := client.ApplicationsById(*applicationID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) - err = pageIterator.Iterate(func(pageItem interface{}) bool { - owner := pageItem.(models.DirectoryObjectable) - ownerIds = append(ownerIds, owner.GetId()) - - return true - }) - - return ownerIds, nil -} - -//// TRANSFORM FUNCTIONS - -func adApplicationTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { - application := d.HydrateItem.(*ADApplicationInfo) - tags := application.GetTags() - return TagsToMap(tags) -} - -func adApplicationTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { - data := d.HydrateItem.(*ADApplicationInfo) - if data == nil { - return nil, nil - } - - title := data.GetDisplayName() - if title == nil { - title = data.GetId() - } - - return title, nil -} - -func buildApplicationQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { - filters := []string{} - - filterQuals := map[string]string{ - "display_name": "string", - "app_id": "string", - "publisher_domain": "string", - } - - for qual := range filterQuals { - if equalQuals[qual] != nil { - filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) - } - } - - return filters -} diff --git a/azuread/table_azuread_conditional_access_policy.go b/azuread/table_azuread_conditional_access_policy.go index e21637e..5f8920a 100644 --- a/azuread/table_azuread_conditional_access_policy.go +++ b/azuread/table_azuread_conditional_access_policy.go @@ -2,14 +2,19 @@ package azuread import ( "context" + "errors" + "fmt" "strings" - "github.com/manicminer/hamilton/msgraph" - "github.com/manicminer/hamilton/odata" + "github.com/iancoleman/strcase" "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/identity/conditionalaccess/policies" + "github.com/microsoftgraph/msgraph-sdk-go/models" ) //// TABLE DEFINITION @@ -19,13 +24,17 @@ func tableAzureAdConditionalAccessPolicy() *plugin.Table { Name: "azuread_conditional_access_policy", Description: "Represents an Azure Active Directory (Azure AD) Conditional Access Policy", Get: &plugin.GetConfig{ - Hydrate: getAdConditionalAccessPolicy, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), - KeyColumns: plugin.SingleColumn("id"), + Hydrate: getAdConditionalAccessPolicy, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, + KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ - Hydrate: listAdConditionalAccessPolicies, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Request_UnsupportedQuery"}), + Hydrate: listAdConditionalAccessPolicies, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery"}), + }, KeyColumns: []*plugin.KeyColumn{ {Name: "display_name", Require: plugin.Optional}, {Name: "state", Require: plugin.Optional}, @@ -33,32 +42,32 @@ func tableAzureAdConditionalAccessPolicy() *plugin.Table { }, Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "Specifies the identifier of a conditionalAccessPolicy object.", Transform: transform.FromGo()}, - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "Specifies a display name for the conditionalAccessPolicy object."}, - {Name: "state", Type: proto.ColumnType_STRING, Description: "Specifies the state of the conditionalAccessPolicy object. Possible values are: enabled, disabled, enabledForReportingButNotEnforced."}, - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The create date of the conditional access policy."}, - {Name: "modified_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The modification date of the conditional access policy."}, - {Name: "operator", Type: proto.ColumnType_STRING, Description: "Defines the relationship of the grant controls. Possible values: AND, OR."}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "Specifies the identifier of a conditionalAccessPolicy object.", Transform: transform.FromMethod("GetId")}, + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "Specifies a display name for the conditionalAccessPolicy object.", Transform: transform.FromMethod("GetDisplayName")}, + {Name: "state", Type: proto.ColumnType_STRING, Description: "Specifies the state of the conditionalAccessPolicy object. Possible values are: enabled, disabled, enabledForReportingButNotEnforced.", Transform: transform.FromMethod("GetState")}, + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The create date of the conditional access policy.", Transform: transform.FromMethod("GetCreatedDateTime")}, + {Name: "modified_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The modification date of the conditional access policy.", Transform: transform.FromMethod("GetModifiedDateTime")}, + {Name: "operator", Type: proto.ColumnType_STRING, Description: "Defines the relationship of the grant controls. Possible values: AND, OR.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsOperator")}, // Json fields - {Name: "applications", Type: proto.ColumnType_JSON, Description: "Applications and user actions included in and excluded from the policy.", Transform: transform.FromField("Conditions.Applications")}, - {Name: "application_enforced_restrictions", Type: proto.ColumnType_JSON, Description: "Session control to enforce application restrictions. Only Exchange Online and Sharepoint Online support this session control.", Transform: transform.FromField("SessionControls.ApplicationEnforcedRestrictions")}, - {Name: "built_in_controls", Type: proto.ColumnType_JSON, Description: "List of values of built-in controls required by the policy. Possible values: block, mfa, compliantDevice, domainJoinedDevice, approvedApplication, compliantApplication, passwordChange, unknownFutureValue.", Transform: transform.FromField("GrantControls.BuiltInControls")}, - {Name: "client_app_types", Type: proto.ColumnType_JSON, Description: "Client application types included in the policy. Possible values are: all, browser, mobileAppsAndDesktopClients, exchangeActiveSync, easSupported, other.", Transform: transform.FromField("Conditions.ClientAppTypes")}, - {Name: "custom_authentication_factors", Type: proto.ColumnType_JSON, Description: "List of custom controls IDs required by the policy.", Transform: transform.FromField("GrantControls.CustomAuthenticationFactors")}, - {Name: "cloud_app_security", Type: proto.ColumnType_JSON, Description: "Session control to apply cloud app security.", Transform: transform.FromField("SessionControls.CloudAppSecurity")}, - {Name: "locations", Type: proto.ColumnType_JSON, Description: "Locations included in and excluded from the policy.", Transform: transform.FromField("Conditions.Locations")}, - {Name: "persistent_browser", Type: proto.ColumnType_JSON, Description: "Session control to define whether to persist cookies or not. All apps should be selected for this session control to work correctly.", Transform: transform.FromField("SessionControls.PersistentBrowser")}, - {Name: "platforms", Type: proto.ColumnType_JSON, Description: "Platforms included in and excluded from the policy.", Transform: transform.FromField("Conditions.Platforms")}, - {Name: "sign_in_frequency", Type: proto.ColumnType_JSON, Description: "Session control to enforce signin frequency.", Transform: transform.FromField("SessionControls.SignInFrequency")}, - {Name: "sign_in_risk_levels", Type: proto.ColumnType_JSON, Description: "Sign-in risk levels included in the policy. Possible values are: low, medium, high, hidden, none, unknownFutureValue.", Transform: transform.FromField("Conditions.SignInRiskLevels")}, - {Name: "terms_of_use", Type: proto.ColumnType_JSON, Description: "List of terms of use IDs required by the policy.", Transform: transform.FromField("GrantControls.TermsOfUse")}, - {Name: "users", Type: proto.ColumnType_JSON, Description: "Users, groups, and roles included in and excluded from the policy.", Transform: transform.FromField("Conditions.Users")}, - {Name: "user_risk_levels", Type: proto.ColumnType_JSON, Description: "User risk levels included in the policy. Possible values are: low, medium, high, hidden, none, unknownFutureValue.", Transform: transform.FromField("Conditions.UserRiskLevels")}, + {Name: "applications", Type: proto.ColumnType_JSON, Description: "Applications and user actions included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsApplications")}, + {Name: "application_enforced_restrictions", Type: proto.ColumnType_JSON, Description: "Session control to enforce application restrictions. Only Exchange Online and Sharepoint Online support this session control.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsApplicationEnforcedRestrictions")}, + {Name: "built_in_controls", Type: proto.ColumnType_JSON, Description: "List of values of built-in controls required by the policy. Possible values: block, mfa, compliantDevice, domainJoinedDevice, approvedApplication, compliantApplication, passwordChange, unknownFutureValue.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsBuiltInControls")}, + {Name: "client_app_types", Type: proto.ColumnType_JSON, Description: "Client application types included in the policy. Possible values are: all, browser, mobileAppsAndDesktopClients, exchangeActiveSync, easSupported, other.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsClientAppTypes")}, + {Name: "custom_authentication_factors", Type: proto.ColumnType_JSON, Description: "List of custom controls IDs required by the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsCustomAuthenticationFactors")}, + {Name: "cloud_app_security", Type: proto.ColumnType_JSON, Description: "Session control to apply cloud app security.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsCloudAppSecurity")}, + {Name: "locations", Type: proto.ColumnType_JSON, Description: "Locations included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsLocations")}, + {Name: "persistent_browser", Type: proto.ColumnType_JSON, Description: "Session control to define whether to persist cookies or not. All apps should be selected for this session control to work correctly.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsPersistentBrowser")}, + {Name: "platforms", Type: proto.ColumnType_JSON, Description: "Platforms included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsPlatforms")}, + {Name: "sign_in_frequency", Type: proto.ColumnType_JSON, Description: "Session control to enforce signin frequency.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsSignInFrequency")}, + {Name: "sign_in_risk_levels", Type: proto.ColumnType_JSON, Description: "Sign-in risk levels included in the policy. Possible values are: low, medium, high, hidden, none, unknownFutureValue.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsSignInRiskLevels")}, + {Name: "terms_of_use", Type: proto.ColumnType_JSON, Description: "List of terms of use IDs required by the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsTermsOfUse")}, + {Name: "users", Type: proto.ColumnType_JSON, Description: "Users, groups, and roles included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsUsers")}, + {Name: "user_risk_levels", Type: proto.ColumnType_JSON, Description: "User risk levels included in the policy. Possible values are: low, medium, high, hidden, none, unknownFutureValue.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsUserRiskLevels")}, // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "ID")}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenantId).WithCache(), Transform: transform.FromValue()}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adConditionalAccessPolicyTitle)}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } } @@ -66,88 +75,116 @@ func tableAzureAdConditionalAccessPolicy() *plugin.Table { //// LIST FUNCTION func listAdConditionalAccessPolicies(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - session, err := GetNewSession(ctx, d) + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewConditionalAccessPolicyClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - - input := odata.Query{} + // List operations + input := &policies.PoliciesRequestBuilderGetQueryParameters{} // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. limit := d.QueryContext.Limit if limit != nil { if *limit > 0 && *limit <= 999 { - input.Top = int(*limit) + l := int32(*limit) + input.Top = &l } } - - qualsColumnMap := []QualsColumn{ - {"display_name", "string", "displayName"}, - {"state", "string", "state"}, - } - filter := buildCommaonQueryFilter(qualsColumnMap, d.Quals) + equalQuals := d.KeyColumnQuals + filter := buildConditionalAccessPolicyQueryFilter(equalQuals) + if len(filter) > 0 { - input.Filter = strings.Join(filter, " and ") + joinStr := strings.Join(filter, " and ") + input.Filter = &joinStr + } + + options := &policies.PoliciesRequestBuilderGetRequestConfiguration{ + QueryParameters: input, } - conditionalAccessPolicies, _, err := client.List(ctx, input) + result, err := client.Identity().ConditionalAccess().Policies().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { - if isNotFoundError(err) { - plugin.Logger(ctx).Error("listAdConditionalAccessPolicies", "Resource not found", err) - return nil, nil - } - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - for _, conditionalAccesPolicy := range *conditionalAccessPolicies { - d.StreamListItem(ctx, conditionalAccesPolicy) + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateConditionalAccessPolicyCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + policy := pageItem.(models.ConditionalAccessPolicyable) + + d.StreamListItem(ctx, &ADConditionalAccessPolicyInfo{policy}) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { - return nil, nil + return false } - } + + return true + }) + return nil, err } -//// Hydrate Functions +//// HYDRATE FUNCTIONS func getAdConditionalAccessPolicy(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var conditionalAccessPolicyId string - if h.Item != nil { - conditionalAccessPolicyId = *h.Item.(msgraph.ConditionalAccessPolicy).ID - } else { - conditionalAccessPolicyId = d.KeyColumnQuals["id"].GetStringValue() - } + conditionalAccessPolicyId := d.KeyColumnQuals["id"].GetStringValue() if conditionalAccessPolicyId == "" { return nil, nil } - session, err := GetNewSession(ctx, d) + // Create client + client, _, err := GetGraphClient(ctx, d) if err != nil { - if isNotFoundError(err) { - return nil, nil - } - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewConditionalAccessPolicyClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - client.BaseClient.DisableRetries = true - - conditionalAccessPolicy, _, err := client.Get(ctx, conditionalAccessPolicyId, odata.Query{}) + policy, err := client.Identity().ConditionalAccess().PoliciesById(conditionalAccessPolicyId).Get() if err != nil { - if isNotFoundError(err) { - plugin.Logger(ctx).Error("getAdConditionalAccessPolicy", "Resource not found", err) - return nil, nil + errObj := getErrorObject(err) + return nil, errObj + } + return &ADConditionalAccessPolicyInfo{policy}, nil +} + +func buildConditionalAccessPolicyQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { + filters := []string{} + + filterQuals := map[string]string{ + "display_name": "string", + "state": "string", + } + + for qual, qualType := range filterQuals { + switch qualType { + case "string": + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) + } } - return nil, err } - return conditionalAccessPolicy, nil + + return filters +} + +//// TRANSFORM FUNCTIONS + +func adConditionalAccessPolicyTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADConditionalAccessPolicyInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetId() + } + + return title, nil } diff --git a/azuread/table_azuread_conditional_access_policy1.go b/azuread/table_azuread_conditional_access_policy1.go deleted file mode 100644 index 592f7af..0000000 --- a/azuread/table_azuread_conditional_access_policy1.go +++ /dev/null @@ -1,190 +0,0 @@ -package azuread - -import ( - "context" - "errors" - "fmt" - "strings" - - "github.com/iancoleman/strcase" - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" - - "github.com/turbot/steampipe-plugin-sdk/v3/plugin" - - msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" - "github.com/microsoftgraph/msgraph-sdk-go/identity/conditionalaccess/policies" - "github.com/microsoftgraph/msgraph-sdk-go/models" -) - -//// TABLE DEFINITION - -func tableAzureAdConditionalAccessPolicyTest() *plugin.Table { - return &plugin.Table{ - Name: "azuread_conditional_access_policy_test", - Description: "Represents an Azure Active Directory (Azure AD) Conditional Access Policy", - Get: &plugin.GetConfig{ - Hydrate: getAdConditionalAccessPolicyTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), - }, - KeyColumns: plugin.SingleColumn("id"), - }, - List: &plugin.ListConfig{ - Hydrate: listAdConditionalAccessPoliciesTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery"}), - }, - KeyColumns: []*plugin.KeyColumn{ - {Name: "display_name", Require: plugin.Optional}, - {Name: "state", Require: plugin.Optional}, - }, - }, - - Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "Specifies the identifier of a conditionalAccessPolicy object.", Transform: transform.FromMethod("GetId")}, - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "Specifies a display name for the conditionalAccessPolicy object.", Transform: transform.FromMethod("GetDisplayName")}, - {Name: "state", Type: proto.ColumnType_STRING, Description: "Specifies the state of the conditionalAccessPolicy object. Possible values are: enabled, disabled, enabledForReportingButNotEnforced.", Transform: transform.FromMethod("GetState")}, - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The create date of the conditional access policy.", Transform: transform.FromMethod("GetCreatedDateTime")}, - {Name: "modified_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The modification date of the conditional access policy.", Transform: transform.FromMethod("GetModifiedDateTime")}, - {Name: "operator", Type: proto.ColumnType_STRING, Description: "Defines the relationship of the grant controls. Possible values: AND, OR.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsOperator")}, - - // Json fields - {Name: "applications", Type: proto.ColumnType_JSON, Description: "Applications and user actions included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsApplications")}, - {Name: "application_enforced_restrictions", Type: proto.ColumnType_JSON, Description: "Session control to enforce application restrictions. Only Exchange Online and Sharepoint Online support this session control.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsApplicationEnforcedRestrictions")}, - {Name: "built_in_controls", Type: proto.ColumnType_JSON, Description: "List of values of built-in controls required by the policy. Possible values: block, mfa, compliantDevice, domainJoinedDevice, approvedApplication, compliantApplication, passwordChange, unknownFutureValue.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsBuiltInControls")}, - {Name: "client_app_types", Type: proto.ColumnType_JSON, Description: "Client application types included in the policy. Possible values are: all, browser, mobileAppsAndDesktopClients, exchangeActiveSync, easSupported, other.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsClientAppTypes")}, - {Name: "custom_authentication_factors", Type: proto.ColumnType_JSON, Description: "List of custom controls IDs required by the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsCustomAuthenticationFactors")}, - {Name: "cloud_app_security", Type: proto.ColumnType_JSON, Description: "Session control to apply cloud app security.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsCloudAppSecurity")}, - {Name: "locations", Type: proto.ColumnType_JSON, Description: "Locations included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsLocations")}, - {Name: "persistent_browser", Type: proto.ColumnType_JSON, Description: "Session control to define whether to persist cookies or not. All apps should be selected for this session control to work correctly.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsPersistentBrowser")}, - {Name: "platforms", Type: proto.ColumnType_JSON, Description: "Platforms included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsPlatforms")}, - {Name: "sign_in_frequency", Type: proto.ColumnType_JSON, Description: "Session control to enforce signin frequency.", Transform: transform.FromMethod("ConditionalAccessPolicySessionControlsSignInFrequency")}, - {Name: "sign_in_risk_levels", Type: proto.ColumnType_JSON, Description: "Sign-in risk levels included in the policy. Possible values are: low, medium, high, hidden, none, unknownFutureValue.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsSignInRiskLevels")}, - {Name: "terms_of_use", Type: proto.ColumnType_JSON, Description: "List of terms of use IDs required by the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyGrantControlsTermsOfUse")}, - {Name: "users", Type: proto.ColumnType_JSON, Description: "Users, groups, and roles included in and excluded from the policy.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsUsers")}, - {Name: "user_risk_levels", Type: proto.ColumnType_JSON, Description: "User risk levels included in the policy. Possible values are: low, medium, high, hidden, none, unknownFutureValue.", Transform: transform.FromMethod("ConditionalAccessPolicyConditionsUserRiskLevels")}, - - // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adConditionalAccessPolicyTitle)}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, - }, - } -} - -//// LIST FUNCTION - -func listAdConditionalAccessPoliciesTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - // List operations - input := &policies.PoliciesRequestBuilderGetQueryParameters{} - - // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow - // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. - limit := d.QueryContext.Limit - if limit != nil { - if *limit > 0 && *limit <= 999 { - l := int32(*limit) - input.Top = &l - } - } - - equalQuals := d.KeyColumnQuals - filter := buildConditionalAccessPolicyQueryFilter(equalQuals) - - if len(filter) > 0 { - joinStr := strings.Join(filter, " and ") - input.Filter = &joinStr - } - - options := &policies.PoliciesRequestBuilderGetRequestConfiguration{ - QueryParameters: input, - } - - result, err := client.Identity().ConditionalAccess().Policies().GetWithRequestConfigurationAndResponseHandler(options, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateConditionalAccessPolicyCollectionResponseFromDiscriminatorValue) - - err = pageIterator.Iterate(func(pageItem interface{}) bool { - policy := pageItem.(models.ConditionalAccessPolicyable) - - d.StreamListItem(ctx, &ADConditionalAccessPolicyInfo{policy}) - - // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true - }) - - return nil, err -} - -//// HYDRATE FUNCTIONS - -func getAdConditionalAccessPolicyTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - - conditionalAccessPolicyId := d.KeyColumnQuals["id"].GetStringValue() - if conditionalAccessPolicyId == "" { - return nil, nil - } - - // Create client - client, _, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - policy, err := client.Identity().ConditionalAccess().PoliciesById(conditionalAccessPolicyId).Get() - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - return &ADConditionalAccessPolicyInfo{policy}, nil -} - -func buildConditionalAccessPolicyQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { - filters := []string{} - - filterQuals := map[string]string{ - "display_name": "string", - "state": "string", - } - - for qual, qualType := range filterQuals { - switch qualType { - case "string": - if equalQuals[qual] != nil { - filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) - } - } - } - - return filters -} - -//// TRANSFORM FUNCTIONS - -func adConditionalAccessPolicyTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { - data := d.HydrateItem.(*ADConditionalAccessPolicyInfo) - if data == nil { - return nil, nil - } - - title := data.GetDisplayName() - if title == nil { - title = data.GetId() - } - - return title, nil -} diff --git a/azuread/table_azuread_directory_role.go b/azuread/table_azuread_directory_role.go index d20d301..9f847fd 100644 --- a/azuread/table_azuread_directory_role.go +++ b/azuread/table_azuread_directory_role.go @@ -2,8 +2,12 @@ package azuread import ( "context" + "errors" + "fmt" - "github.com/manicminer/hamilton/msgraph" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/directoryroles/item/members" + "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" @@ -17,52 +21,55 @@ func tableAzureAdDirectoryRole() *plugin.Table { Name: "azuread_directory_role", Description: "Represents an Azure Active Directory (Azure AD) directory role", Get: &plugin.GetConfig{ - Hydrate: getAdDirectoryRole, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), - KeyColumns: plugin.SingleColumn("id"), + Hydrate: getAdDirectoryRole, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, + KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ Hydrate: listAdDirectoryRoles, }, Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the directory role.", Transform: transform.FromGo()}, - {Name: "description", Type: proto.ColumnType_STRING, Description: "The description for the directory role."}, - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the directory role."}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the directory role.", Transform: transform.FromMethod("GetId")}, + {Name: "description", Type: proto.ColumnType_STRING, Description: "The description for the directory role.", Transform: transform.FromMethod("GetDescription")}, + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the directory role.", Transform: transform.FromMethod("GetDisplayName")}, // Other fields - {Name: "role_template_id", Type: proto.ColumnType_STRING, Description: "The id of the directoryRoleTemplate that this role is based on. The property must be specified when activating a directory role in a tenant with a POST operation. After the directory role has been activated, the property is read only."}, + {Name: "role_template_id", Type: proto.ColumnType_STRING, Description: "The id of the directoryRoleTemplate that this role is based on. The property must be specified when activating a directory role in a tenant with a POST operation. After the directory role has been activated, the property is read only.", Transform: transform.FromMethod("GetRoleTemplateId")}, - // // Json fields - {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getAdDirectoryRoleMembers, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, - // // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "ID")}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenantId).WithCache(), Transform: transform.FromValue()}, + // Json fields + {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getDirectoryRoleMembers, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, + + // Standard columns + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adDirectoryRoleTitle)}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } } +type ADDirectoryRoleInfo struct { + models.DirectoryRoleable +} + //// LIST FUNCTION func listAdDirectoryRoles(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - session, err := GetNewSession(ctx, d) + // Create client + client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewDirectoryRolesClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - - directoryRoles, _, err := client.List(ctx) + result, err := client.DirectoryRoles().Get() if err != nil { - if isNotFoundError(err) { - return nil, nil - } - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - for _, directoryRoles := range *directoryRoles { - d.StreamListItem(ctx, directoryRoles) + for _, directoryRole := range result.GetValue() { + d.StreamListItem(ctx, &ADDirectoryRoleInfo{directoryRole}) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { @@ -70,61 +77,86 @@ func listAdDirectoryRoles(ctx context.Context, d *plugin.QueryData, _ *plugin.Hy } } - return nil, err + return nil, nil } -//// Hydrate Functions +//// HYDRATE FUNCTIONS func getAdDirectoryRole(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var directoryRoleId string - if h.Item != nil { - directoryRoleId = *h.Item.(msgraph.DirectoryRole).ID - } else { - directoryRoleId = d.KeyColumnQuals["id"].GetStringValue() - } - + directoryRoleId := d.KeyColumnQuals["id"].GetStringValue() if directoryRoleId == "" { return nil, nil } - session, err := GetNewSession(ctx, d) + // Create client + client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewDirectoryRolesClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - - directoryRole, _, err := client.Get(ctx, directoryRoleId) + directoryRole, err := client.DirectoryRolesById(directoryRoleId).Get() if err != nil { - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - return *directoryRole, nil + + return &ADDirectoryRoleInfo{directoryRole}, nil } -func getAdDirectoryRoleMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var directoryRoleId string - if h.Item != nil { - directoryRoleId = *h.Item.(msgraph.DirectoryRole).ID - } else { - directoryRoleId = d.KeyColumnQuals["id"].GetStringValue() +func getDirectoryRoleMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - if directoryRoleId == "" { - return nil, nil + directoryRole := h.Item.(*ADDirectoryRoleInfo) + directoryRoleID := directoryRole.GetId() + + headers := map[string]string{ + "ConsistencyLevel": "eventual", } - session, err := GetNewSession(ctx, d) - if err != nil { - return nil, err + includeCount := true + requestParameters := &members.MembersRequestBuilderGetQueryParameters{ + Count: &includeCount, } - client := msgraph.NewDirectoryRolesClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer + config := &members.MembersRequestBuilderGetRequestConfiguration{ + Headers: headers, + QueryParameters: requestParameters, + } - members, _, err := client.ListMembers(ctx, directoryRoleId) + memberIds := []*string{} + members, err := client.DirectoryRolesById(*directoryRoleID).Members().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - return members, nil + + pageIterator, err := msgraphcore.NewPageIterator(members, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(func(pageItem interface{}) bool { + member := pageItem.(models.DirectoryObjectable) + memberIds = append(memberIds, member.GetId()) + + return true + }) + + return memberIds, nil +} + +//// TRANSFORM FUNCTIONS + +func adDirectoryRoleTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADDirectoryRoleInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetId() + } + + return title, nil } diff --git a/azuread/table_azuread_directory_role1.go b/azuread/table_azuread_directory_role1.go deleted file mode 100644 index 275a584..0000000 --- a/azuread/table_azuread_directory_role1.go +++ /dev/null @@ -1,162 +0,0 @@ -package azuread - -import ( - "context" - "errors" - "fmt" - - msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" - "github.com/microsoftgraph/msgraph-sdk-go/directoryroles/item/members" - "github.com/microsoftgraph/msgraph-sdk-go/models" - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" - - "github.com/turbot/steampipe-plugin-sdk/v3/plugin" -) - -//// TABLE DEFINITION - -func tableAzureAdDirectoryRoleTest() *plugin.Table { - return &plugin.Table{ - Name: "azuread_directory_role_test", - Description: "Represents an Azure Active Directory (Azure AD) directory role", - Get: &plugin.GetConfig{ - Hydrate: getAdDirectoryRoleTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), - }, - KeyColumns: plugin.SingleColumn("id"), - }, - List: &plugin.ListConfig{ - Hydrate: listAdDirectoryRolesTest, - }, - - Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the directory role.", Transform: transform.FromMethod("GetId")}, - {Name: "description", Type: proto.ColumnType_STRING, Description: "The description for the directory role.", Transform: transform.FromMethod("GetDescription")}, - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the directory role.", Transform: transform.FromMethod("GetDisplayName")}, - - // Other fields - {Name: "role_template_id", Type: proto.ColumnType_STRING, Description: "The id of the directoryRoleTemplate that this role is based on. The property must be specified when activating a directory role in a tenant with a POST operation. After the directory role has been activated, the property is read only.", Transform: transform.FromMethod("GetRoleTemplateId")}, - - // Json fields - {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getDirectoryRoleMembers, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, - - // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adDirectoryRoleTitle)}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, - }, - } -} - -type ADDirectoryRoleInfo struct { - models.DirectoryRoleable -} - -//// LIST FUNCTION - -func listAdDirectoryRolesTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - // Create client - client, _, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - result, err := client.DirectoryRoles().Get() - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - for _, directoryRole := range result.GetValue() { - d.StreamListItem(ctx, &ADDirectoryRoleInfo{directoryRole}) - - // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return nil, nil - } - } - - return nil, nil -} - -//// HYDRATE FUNCTIONS - -func getAdDirectoryRoleTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - directoryRoleId := d.KeyColumnQuals["id"].GetStringValue() - if directoryRoleId == "" { - return nil, nil - } - - // Create client - client, _, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - directoryRole, err := client.DirectoryRolesById(directoryRoleId).Get() - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - return &ADDirectoryRoleInfo{directoryRole}, nil -} - -func getDirectoryRoleMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - directoryRole := h.Item.(*ADDirectoryRoleInfo) - directoryRoleID := directoryRole.GetId() - - headers := map[string]string{ - "ConsistencyLevel": "eventual", - } - - includeCount := true - requestParameters := &members.MembersRequestBuilderGetQueryParameters{ - Count: &includeCount, - } - - config := &members.MembersRequestBuilderGetRequestConfiguration{ - Headers: headers, - QueryParameters: requestParameters, - } - - memberIds := []*string{} - members, err := client.DirectoryRolesById(*directoryRoleID).Members().GetWithRequestConfigurationAndResponseHandler(config, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(members, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) - err = pageIterator.Iterate(func(pageItem interface{}) bool { - member := pageItem.(models.DirectoryObjectable) - memberIds = append(memberIds, member.GetId()) - - return true - }) - - return memberIds, nil -} - -//// TRANSFORM FUNCTIONS - -func adDirectoryRoleTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { - data := d.HydrateItem.(*ADDirectoryRoleInfo) - if data == nil { - return nil, nil - } - - title := data.GetDisplayName() - if title == nil { - title = data.GetId() - } - - return title, nil -} diff --git a/azuread/table_azuread_domain.go b/azuread/table_azuread_domain.go index cda28ae..f4d5f0e 100644 --- a/azuread/table_azuread_domain.go +++ b/azuread/table_azuread_domain.go @@ -2,12 +2,16 @@ package azuread import ( "context" + "errors" + "fmt" - "github.com/manicminer/hamilton/msgraph" - "github.com/manicminer/hamilton/odata" "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/domains" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" ) @@ -18,102 +22,110 @@ func tableAzureAdDomain() *plugin.Table { Name: "azuread_domain", Description: "Represents an Azure Active Directory (Azure AD) domain", Get: &plugin.GetConfig{ - Hydrate: getAdDomain, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), - KeyColumns: plugin.SingleColumn("id"), + Hydrate: getAdDomain, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, + KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ Hydrate: listAdDomains, }, Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "The fully qualified name of the domain.", Transform: transform.FromGo()}, - {Name: "authentication_type", Type: proto.ColumnType_STRING, Description: "Indicates the configured authentication type for the domain. The value is either Managed or Federated. Managed indicates a cloud managed domain where Azure AD performs user authentication. Federated indicates authentication is federated with an identity provider such as the tenant's on-premises Active Directory via Active Directory Federation Services."}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The fully qualified name of the domain.", Transform: transform.FromMethod("GetId")}, + {Name: "authentication_type", Type: proto.ColumnType_STRING, Description: "Indicates the configured authentication type for the domain. The value is either Managed or Federated. Managed indicates a cloud managed domain where Azure AD performs user authentication. Federated indicates authentication is federated with an identity provider such as the tenant's on-premises Active Directory via Active Directory Federation Services.", Transform: transform.FromMethod("GetAuthenticationType")}, // Other fields - {Name: "is_default", Type: proto.ColumnType_BOOL, Description: "true if this is the default domain that is used for user creation. There is only one default domain per company."}, - {Name: "is_admin_managed", Type: proto.ColumnType_BOOL, Description: "The value of the property is false if the DNS record management of the domain has been delegated to Microsoft 365. Otherwise, the value is true."}, - {Name: "is_initial", Type: proto.ColumnType_BOOL, Description: "true if this is the initial domain created by Microsoft Online Services (companyname.onmicrosoft.com). There is only one initial domain per company."}, - {Name: "is_root", Type: proto.ColumnType_BOOL, Description: "true if the domain is a verified root domain. Otherwise, false if the domain is a subdomain or unverified."}, - {Name: "is_verified", Type: proto.ColumnType_BOOL, Description: "true if the domain has completed domain ownership verification."}, - - // // Json fields - {Name: "supported_services", Type: proto.ColumnType_JSON, Description: "The capabilities assigned to the domain. Can include 0, 1 or more of following values: Email, Sharepoint, EmailInternalRelayOnly, OfficeCommunicationsOnline, SharePointDefaultDomain, FullRedelegation, SharePointPublic, OrgIdAuthentication, Yammer, Intune. The values which you can add/remove using Graph API include: Email, OfficeCommunicationsOnline, Yammer."}, - - // // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "ID")}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenantId).WithCache(), Transform: transform.FromValue()}, + {Name: "is_default", Type: proto.ColumnType_BOOL, Description: "true if this is the default domain that is used for user creation. There is only one default domain per company.", Transform: transform.FromMethod("GetIsDefault")}, + {Name: "is_admin_managed", Type: proto.ColumnType_BOOL, Description: "The value of the property is false if the DNS record management of the domain has been delegated to Microsoft 365. Otherwise, the value is true.", Transform: transform.FromMethod("GetIsAdminManaged")}, + {Name: "is_initial", Type: proto.ColumnType_BOOL, Description: "true if this is the initial domain created by Microsoft Online Services (companyname.onmicrosoft.com). There is only one initial domain per company.", Transform: transform.FromMethod("GetIsInitial")}, + {Name: "is_root", Type: proto.ColumnType_BOOL, Description: "true if the domain is a verified root domain. Otherwise, false if the domain is a subdomain or unverified.", Transform: transform.FromMethod("GetIsRoot")}, + {Name: "is_verified", Type: proto.ColumnType_BOOL, Description: "true if the domain has completed domain ownership verification.", Transform: transform.FromMethod("GetIsVerified")}, + + // Json fields + {Name: "supported_services", Type: proto.ColumnType_JSON, Description: "The capabilities assigned to the domain. Can include 0, 1 or more of following values: Email, Sharepoint, EmailInternalRelayOnly, OfficeCommunicationsOnline, SharePointDefaultDomain, FullRedelegation, SharePointPublic, OrgIdAuthentication, Yammer, Intune. The values which you can add/remove using Graph API include: Email, OfficeCommunicationsOnline, Yammer.", Transform: transform.FromMethod("GetSupportedServices")}, + + // Standard columns + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromMethod("GetId")}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } } +type ADDomainInfo struct { + models.Domainable +} + //// LIST FUNCTION func listAdDomains(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - session, err := GetNewSession(ctx, d) + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewDomainsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - - input := odata.Query{} + // List operations + input := &domains.DomainsRequestBuilderGetQueryParameters{} // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. limit := d.QueryContext.Limit if limit != nil { if *limit > 0 && *limit <= 999 { - input.Top = int(*limit) + l := int32(*limit) + input.Top = &l } } - domains, _, err := client.List(ctx, input) + options := &domains.DomainsRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + result, err := client.Domains().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { - if isNotFoundError(err) { - return nil, nil - } - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - for _, domain := range *domains { - d.StreamListItem(ctx, domain) + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateDomainCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + domain := pageItem.(models.Domainable) + + d.StreamListItem(ctx, &ADDomainInfo{domain}) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { - return nil, nil + return false } - } - return nil, err + return true + }) + + return nil, nil } -//// Hydrate Functions +//// HYDRATE FUNCTIONS func getAdDomain(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var domainId string - if h.Item != nil { - domainId = *h.Item.(msgraph.ServicePrincipal).ID - } else { - domainId = d.KeyColumnQuals["id"].GetStringValue() - } - + domainId := d.KeyColumnQuals["id"].GetStringValue() if domainId == "" { return nil, nil } - session, err := GetNewSession(ctx, d) + + // Create client + client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewDomainsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - client.BaseClient.DisableRetries = true - - domain, _, err := client.Get(ctx, domainId, odata.Query{}) + domain, err := client.DomainsById(domainId).Get() if err != nil { - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - return *domain, nil + + return &ADDomainInfo{domain}, nil } diff --git a/azuread/table_azuread_domain1.go b/azuread/table_azuread_domain1.go deleted file mode 100644 index a97fc67..0000000 --- a/azuread/table_azuread_domain1.go +++ /dev/null @@ -1,131 +0,0 @@ -package azuread - -import ( - "context" - "errors" - "fmt" - - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" - - msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" - "github.com/microsoftgraph/msgraph-sdk-go/domains" - "github.com/microsoftgraph/msgraph-sdk-go/models" - - "github.com/turbot/steampipe-plugin-sdk/v3/plugin" -) - -//// TABLE DEFINITION - -func tableAzureAdDomainTest() *plugin.Table { - return &plugin.Table{ - Name: "azuread_domain_test", - Description: "Represents an Azure Active Directory (Azure AD) domain", - Get: &plugin.GetConfig{ - Hydrate: getAdDomainTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), - }, - KeyColumns: plugin.SingleColumn("id"), - }, - List: &plugin.ListConfig{ - Hydrate: listAdDomainsTest, - }, - - Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "The fully qualified name of the domain.", Transform: transform.FromMethod("GetId")}, - {Name: "authentication_type", Type: proto.ColumnType_STRING, Description: "Indicates the configured authentication type for the domain. The value is either Managed or Federated. Managed indicates a cloud managed domain where Azure AD performs user authentication. Federated indicates authentication is federated with an identity provider such as the tenant's on-premises Active Directory via Active Directory Federation Services.", Transform: transform.FromMethod("GetAuthenticationType")}, - - // Other fields - {Name: "is_default", Type: proto.ColumnType_BOOL, Description: "true if this is the default domain that is used for user creation. There is only one default domain per company.", Transform: transform.FromMethod("GetIsDefault")}, - {Name: "is_admin_managed", Type: proto.ColumnType_BOOL, Description: "The value of the property is false if the DNS record management of the domain has been delegated to Microsoft 365. Otherwise, the value is true.", Transform: transform.FromMethod("GetIsAdminManaged")}, - {Name: "is_initial", Type: proto.ColumnType_BOOL, Description: "true if this is the initial domain created by Microsoft Online Services (companyname.onmicrosoft.com). There is only one initial domain per company.", Transform: transform.FromMethod("GetIsInitial")}, - {Name: "is_root", Type: proto.ColumnType_BOOL, Description: "true if the domain is a verified root domain. Otherwise, false if the domain is a subdomain or unverified.", Transform: transform.FromMethod("GetIsRoot")}, - {Name: "is_verified", Type: proto.ColumnType_BOOL, Description: "true if the domain has completed domain ownership verification.", Transform: transform.FromMethod("GetIsVerified")}, - - // Json fields - {Name: "supported_services", Type: proto.ColumnType_JSON, Description: "The capabilities assigned to the domain. Can include 0, 1 or more of following values: Email, Sharepoint, EmailInternalRelayOnly, OfficeCommunicationsOnline, SharePointDefaultDomain, FullRedelegation, SharePointPublic, OrgIdAuthentication, Yammer, Intune. The values which you can add/remove using Graph API include: Email, OfficeCommunicationsOnline, Yammer.", Transform: transform.FromMethod("GetSupportedServices")}, - - // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromMethod("GetId")}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, - }, - } -} - -type ADDomainInfo struct { - models.Domainable -} - -//// LIST FUNCTION - -func listAdDomainsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - // List operations - input := &domains.DomainsRequestBuilderGetQueryParameters{} - - // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow - // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. - limit := d.QueryContext.Limit - if limit != nil { - if *limit > 0 && *limit <= 999 { - l := int32(*limit) - input.Top = &l - } - } - - options := &domains.DomainsRequestBuilderGetRequestConfiguration{ - QueryParameters: input, - } - - result, err := client.Domains().GetWithRequestConfigurationAndResponseHandler(options, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateDomainCollectionResponseFromDiscriminatorValue) - - err = pageIterator.Iterate(func(pageItem interface{}) bool { - domain := pageItem.(models.Domainable) - - d.StreamListItem(ctx, &ADDomainInfo{domain}) - - // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true - }) - - return nil, nil -} - -//// HYDRATE FUNCTIONS - -func getAdDomainTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - domainId := d.KeyColumnQuals["id"].GetStringValue() - if domainId == "" { - return nil, nil - } - - // Create client - client, _, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - domain, err := client.DomainsById(domainId).Get() - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - return &ADDomainInfo{domain}, nil -} diff --git a/azuread/table_azuread_group.go b/azuread/table_azuread_group.go index 054b172..c0f9c5e 100644 --- a/azuread/table_azuread_group.go +++ b/azuread/table_azuread_group.go @@ -2,14 +2,21 @@ package azuread import ( "context" + "errors" + "fmt" "strings" - "github.com/manicminer/hamilton/msgraph" - "github.com/manicminer/hamilton/odata" - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" + "github.com/iancoleman/strcase" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/groups" + "github.com/microsoftgraph/msgraph-sdk-go/groups/item" + "github.com/microsoftgraph/msgraph-sdk-go/groups/item/members" + "github.com/microsoftgraph/msgraph-sdk-go/groups/item/owners" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" ) //// TABLE DEFINITION @@ -17,69 +24,84 @@ import ( func tableAzureAdGroup() *plugin.Table { return &plugin.Table{ Name: "azuread_group", - Description: "Represents an Azure Active Directory (Azure AD) group, which can be a Microsoft 365 group, or a security group.", + Description: "Represents an Azure AD user account.", Get: &plugin.GetConfig{ - Hydrate: getAdGroup, + Hydrate: getAdGroup, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ Hydrate: listAdGroups, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery", "Invalid filter clause"}), + }, KeyColumns: plugin.KeyColumnSlice{ // Key fields - {Name: "id", Require: plugin.Optional}, {Name: "display_name", Require: plugin.Optional}, {Name: "filter", Require: plugin.Optional}, - - // Other fields for filtering OData - {Name: "mail", Require: plugin.Optional, Operators: []string{"<>", "="}}, // $filter (eq, ne, NOT, ge, le, in, startsWith). - {Name: "mail_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, // $filter (eq, ne, NOT). - {Name: "on_premises_sync_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, // $filter (eq, ne, NOT, in). - {Name: "security_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, // $filter (eq, ne, NOT, in). - + {Name: "mail", Require: plugin.Optional}, + {Name: "mail_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, + {Name: "on_premises_sync_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, + {Name: "security_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, }, }, - Columns: []*plugin.Column{ - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name."}, - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the group.", Transform: transform.FromGo()}, - {Name: "description", Type: proto.ColumnType_STRING, Description: "An optional description for the group."}, + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name.", Transform: transform.FromMethod("GetDisplayName")}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the group.", Transform: transform.FromMethod("GetId")}, + {Name: "description", Type: proto.ColumnType_STRING, Description: "An optional description for the group.", Transform: transform.FromMethod("GetDescription")}, {Name: "filter", Type: proto.ColumnType_STRING, Transform: transform.FromQual("filter"), Description: "Odata query to search for groups."}, // Other fields - {Name: "classification", Type: proto.ColumnType_STRING, Description: "Describes a classification for the group (such as low, medium or high business impact)."}, - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the group was created."}, - {Name: "expiration_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group is set to expire."}, - {Name: "is_assignable_to_role", Type: proto.ColumnType_BOOL, Description: "Indicates whether this group can be assigned to an Azure Active Directory role or not."}, - {Name: "is_subscribed_by_mail", Type: proto.ColumnType_BOOL, Description: "Indicates whether the signed-in user is subscribed to receive email conversations. Default value is true."}, - {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the group, for example, \"serviceadmins@contoso.onmicrosoft.com\"."}, - {Name: "mail_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is mail-enabled."}, - {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user."}, - {Name: "membership_rule", Type: proto.ColumnType_STRING, Description: "The mail alias for the group, unique in the organization."}, - {Name: "membership_rule_processing_state", Type: proto.ColumnType_STRING, Description: "Indicates whether the dynamic membership processing is on or paused. Possible values are On or Paused."}, - {Name: "on_premises_domain_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises Domanin name synchronized from the on-premises directory."}, - {Name: "on_premises_last_sync_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Indicates the last time at which the group was synced with the on-premises directory."}, - {Name: "on_premises_net_bios_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises NetBiosName synchronized from the on-premises directory."}, - {Name: "on_premises_sam_account_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises SAM account name synchronized from the on-premises directory."}, - {Name: "on_premises_security_identifier", Type: proto.ColumnType_STRING, Description: "Contains the on-premises security identifier (SID) for the group that was synchronized from on-premises to the cloud."}, - {Name: "on_premises_sync_enabled", Type: proto.ColumnType_BOOL, Description: "True if this group is synced from an on-premises directory; false if this group was originally synced from an on-premises directory but is no longer synced; null if this object has never been synced from an on-premises directory (default)."}, - {Name: "renewed_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group was last renewed. This cannot be modified directly and is only updated via the renew service action."}, - {Name: "security_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is a security group."}, - {Name: "security_identifier", Type: proto.ColumnType_STRING, Description: "Security identifier of the group, used in Windows scenarios."}, - {Name: "visibility", Type: proto.ColumnType_STRING, Description: "Specifies the group join policy and group content visibility for groups. Possible values are: Private, Public, or Hiddenmembership."}, - - // Json fields - {Name: "assigned_labels", Type: proto.ColumnType_JSON, Description: "The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group."}, - {Name: "group_types", Type: proto.ColumnType_JSON, Description: "Specifies the group type and its membership. If the collection contains Unified, the group is a Microsoft 365 group; otherwise, it's either a security group or distribution group. For details, see [groups overview](https://docs.microsoft.com/en-us/graph/api/resources/groups-overview?view=graph-rest-1.0)."}, - {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getGroupMembers, Transform: transform.FromValue(), Description: "Id of Users and groups that are members of this group."}, - {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getGroupOwners, Transform: transform.FromValue(), Description: "Id od the owners of the group. The owners are a set of non-admin users who are allowed to modify this object."}, - {Name: "proxy_addresses", Type: proto.ColumnType_JSON, Description: "Email addresses for the group that direct to the same group mailbox. For example: [\"SMTP: bob@contoso.com\", \"smtp: bob@sales.contoso.com\"]. The any operator is required to filter expressions on multi-valued properties."}, - {Name: "resource_behavior_options", Type: proto.ColumnType_JSON, Description: "Specifies the group behaviors that can be set for a Microsoft 365 group during creation. Possible values are AllowOnlyMembersToPost, HideGroupInOutlook, SubscribeNewGroupMembers, WelcomeEmailDisabled."}, - {Name: "resource_provisioning_options", Type: proto.ColumnType_JSON, Description: "Specifies the group resources that are provisioned as part of Microsoft 365 group creation, that are not normally part of default group creation. Possible value is Team."}, + {Name: "classification", Type: proto.ColumnType_STRING, Description: "Describes a classification for the group (such as low, medium or high business impact).", Transform: transform.FromMethod("GetClassification")}, + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the group was created.", Transform: transform.FromMethod("GetCreatedDateTime")}, + {Name: "expiration_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group is set to expire.", Transform: transform.FromMethod("GetExpirationDateTime")}, + {Name: "is_assignable_to_role", Type: proto.ColumnType_BOOL, Description: "Indicates whether this group can be assigned to an Azure Active Directory role or not.", Transform: transform.FromMethod("GetIsAssignableToRole")}, + + // Getting below error while requesting value for isSubscribedByMail + // { + // "error": { + // "code": "ErrorInvalidGroup", + // "message": "The requested group '[id@tenantId]' is invalid.", + // "innerError": { + // "date": "2022-07-13T11:06:23", + // "request-id": "63a83d86-a007-4c68-be75-21cea61d830e", + // "client-request-id": "d69d6667-e818-a322-c694-1fec40b438a8" + // } + // } + // } + // {Name: "is_subscribed_by_mail", Type: proto.ColumnType_BOOL, Description: "Indicates whether the signed-in user is subscribed to receive email conversations. Default value is true.", Transform: transform.FromMethod("GetIsSubscribedByMail")}, + + {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the group, for example, \"serviceadmins@contoso.onmicrosoft.com\".", Transform: transform.FromMethod("GetMail")}, + {Name: "mail_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is mail-enabled.", Transform: transform.FromMethod("GetMailEnabled")}, + {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user.", Transform: transform.FromMethod("GetMailNickname")}, + {Name: "membership_rule", Type: proto.ColumnType_STRING, Description: "The mail alias for the group, unique in the organization.", Transform: transform.FromMethod("GetMembershipRule")}, + {Name: "membership_rule_processing_state", Type: proto.ColumnType_STRING, Description: "Indicates whether the dynamic membership processing is on or paused. Possible values are On or Paused.", Transform: transform.FromMethod("GetMembershipRuleProcessingState")}, + {Name: "on_premises_domain_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises Domanin name synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesDomainName")}, + {Name: "on_premises_last_sync_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Indicates the last time at which the group was synced with the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesLastSyncDateTime")}, + {Name: "on_premises_net_bios_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises NetBiosName synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesNetBiosName")}, + {Name: "on_premises_sam_account_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises SAM account name synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesSamAccountName")}, + {Name: "on_premises_security_identifier", Type: proto.ColumnType_STRING, Description: "Contains the on-premises security identifier (SID) for the group that was synchronized from on-premises to the cloud.", Transform: transform.FromMethod("GetOnPremisesSecurityIdentifier")}, + {Name: "on_premises_sync_enabled", Type: proto.ColumnType_BOOL, Description: "True if this group is synced from an on-premises directory; false if this group was originally synced from an on-premises directory but is no longer synced; null if this object has never been synced from an on-premises directory (default).", Transform: transform.FromMethod("GetOnPremisesSyncEnabled")}, + {Name: "renewed_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group was last renewed. This cannot be modified directly and is only updated via the renew service action.", Transform: transform.FromMethod("GetRenewedDateTime")}, + {Name: "security_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is a security group.", Transform: transform.FromMethod("GetSecurityEnabled")}, + {Name: "security_identifier", Type: proto.ColumnType_STRING, Description: "Security identifier of the group, used in Windows scenarios.", Transform: transform.FromMethod("GetSecurityIdentifier")}, + {Name: "visibility", Type: proto.ColumnType_STRING, Description: "Specifies the group join policy and group content visibility for groups. Possible values are: Private, Public, or Hiddenmembership.", Transform: transform.FromMethod("GetVisibility")}, + + // JSON fields + {Name: "assigned_labels", Type: proto.ColumnType_JSON, Description: "The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group.", Transform: transform.FromMethod("GroupAssignedLabels")}, + {Name: "group_types", Type: proto.ColumnType_JSON, Description: "Specifies the group type and its membership. If the collection contains Unified, the group is a Microsoft 365 group; otherwise, it's either a security group or distribution group. For details, see [groups overview](https://docs.microsoft.com/en-us/graph/api/resources/groups-overview?view=graph-rest-1.0).", Transform: transform.FromMethod("GetGroupTypes")}, + {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupMembers, Transform: transform.FromValue(), Description: "Id of Users and groups that are members of this group."}, + {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupOwners, Transform: transform.FromValue(), Description: "Id od the owners of the group. The owners are a set of non-admin users who are allowed to modify this object."}, + {Name: "proxy_addresses", Type: proto.ColumnType_JSON, Description: "Email addresses for the group that direct to the same group mailbox. For example: [\"SMTP: bob@contoso.com\", \"smtp: bob@sales.contoso.com\"]. The any operator is required to filter expressions on multi-valued properties.", Transform: transform.FromMethod("GetProxyAddresses")}, + // {Name: "resource_behavior_options", Type: proto.ColumnType_JSON, Description: "Specifies the group behaviors that can be set for a Microsoft 365 group during creation. Possible values are AllowOnlyMembersToPost, HideGroupInOutlook, SubscribeNewGroupMembers, WelcomeEmailDisabled."}, + // {Name: "resource_provisioning_options", Type: proto.ColumnType_JSON, Description: "Specifies the group resources that are provisioned as part of Microsoft 365 group creation, that are not normally part of default group creation. Possible value is Team."}, // Standard columns - {Name: "tags", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTags, Transform: transform.From(groupTags)}, - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "ID")}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenantId).WithCache(), Transform: transform.FromValue()}, + {Name: "tags", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTags, Transform: transform.From(adGroupTags)}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adGroupTitle)}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } } @@ -87,22 +109,21 @@ func tableAzureAdGroup() *plugin.Table { //// LIST FUNCTION func listAdGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - session, err := GetNewSession(ctx, d) + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewGroupsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - - input := odata.Query{} + // List operations + input := &groups.GroupsRequestBuilderGetQueryParameters{} // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow - // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. limit := d.QueryContext.Limit if limit != nil { if *limit > 0 && *limit <= 999 { - input.Top = int(*limit) + l := int32(*limit) + input.Top = &l } } @@ -110,114 +131,244 @@ func listAdGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDat quals := d.Quals var queryFilter string - filter := buildQueryFilter(equalQuals) - filter = append(filter, buildBoolNEFilter(quals)...) + filter := buildGroupQueryFilter(equalQuals) + filter = append(filter, buildGroupBoolNEFilter(quals)...) if equalQuals["filter"] != nil { queryFilter = equalQuals["filter"].GetStringValue() } if queryFilter != "" { - input.Filter = queryFilter + input.Filter = &queryFilter } else if len(filter) > 0 { - input.Filter = strings.Join(filter, " and ") + joinStr := strings.Join(filter, " and ") + input.Filter = &joinStr + } + + options := &groups.GroupsRequestBuilderGetRequestConfiguration{ + QueryParameters: input, } - groups, _, err := client.List(ctx, input) + result, err := client.Groups().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { - if isNotFoundError(err) { - return nil, nil - } - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - for _, group := range *groups { - d.StreamListItem(ctx, group) + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateGroupCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + group := pageItem.(models.Groupable) + + d.StreamListItem(ctx, &ADGroupInfo{group}) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { - return nil, nil + return false } - } - return nil, err + return true + }) + + return nil, nil } -//// Hydrate Functions +//// HYDRATE FUNCTIONS func getAdGroup(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var groupId string - if h.Item != nil { - groupId = *h.Item.(msgraph.Group).ID - } else { - groupId = d.KeyColumnQuals["id"].GetStringValue() - } + groupId := d.KeyColumnQuals["id"].GetStringValue() if groupId == "" { return nil, nil } - session, err := GetNewSession(ctx, d) + // Create client + client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewGroupsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - client.BaseClient.DisableRetries = true + input := &item.GroupItemRequestBuilderGetQueryParameters{} + + options := &item.GroupItemRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } - group, _, err := client.Get(ctx, groupId, odata.Query{}) + group, err := client.GroupsById(groupId).GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - return *group, nil + + return &ADGroupInfo{group}, nil } -func getGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - group := h.Item.(msgraph.Group) - session, err := GetNewSession(ctx, d) +func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + group := h.Item.(*ADGroupInfo) + groupID := group.GetId() + + headers := map[string]string{ + "ConsistencyLevel": "eventual", + } + + includeCount := true + requestParameters := &members.MembersRequestBuilderGetQueryParameters{ + Count: &includeCount, } - client := msgraph.NewGroupsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer + config := &members.MembersRequestBuilderGetRequestConfiguration{ + Headers: headers, + QueryParameters: requestParameters, + } - members, _, err := client.ListMembers(ctx, *group.ID) + memberIds := []*string{} + members, err := client.GroupsById(*groupID).Members().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - return members, nil + + pageIterator, err := msgraphcore.NewPageIterator(members, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(func(pageItem interface{}) bool { + member := pageItem.(models.DirectoryObjectable) + memberIds = append(memberIds, member.GetId()) + + return true + }) + + return memberIds, nil } -func getGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - group := h.Item.(msgraph.Group) - session, err := GetNewSession(ctx, d) +func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + } + + group := h.Item.(*ADGroupInfo) + groupID := group.GetId() + + headers := map[string]string{ + "ConsistencyLevel": "eventual", } - client := msgraph.NewGroupsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer + includeCount := true + requestParameters := &owners.OwnersRequestBuilderGetQueryParameters{ + Count: &includeCount, + } + + config := &owners.OwnersRequestBuilderGetRequestConfiguration{ + Headers: headers, + QueryParameters: requestParameters, + } - owners, _, err := client.ListOwners(ctx, *group.ID) + ownerIds := []*string{} + owners, err := client.GroupsById(*groupID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - return owners, nil + + pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(func(pageItem interface{}) bool { + member := pageItem.(models.DirectoryObjectable) + ownerIds = append(ownerIds, member.GetId()) + + return true + }) + + return ownerIds, nil } -//// Transform Function +//// TRANSFORM FUNCTIONS -func groupTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { - group := d.HydrateItem.(msgraph.Group) +func adGroupTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { + group := d.HydrateItem.(*ADGroupInfo) + if group == nil { + return nil, nil + } - if group.AssignedLabels == nil { + assignedLabels := group.GroupAssignedLabels() + if assignedLabels == nil || len(assignedLabels) == 0 { return nil, nil } - var tags = map[string]string{} - for _, i := range *group.AssignedLabels { - tags[*i.LabelId] = *i.DisplayName + + var tags = map[*string]*string{} + for _, i := range assignedLabels { + tags[i["labelId"]] = i["displayName"] } + return tags, nil } + +func adGroupTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADGroupInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetId() + } + + return title, nil +} + +func buildGroupQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { + filters := []string{} + + filterQuals := map[string]string{ + "display_name": "string", + "mail": "string", + "mail_enabled": "bool", + "on_premises_sync_enabled": "bool", + "security_enabled": "bool", + } + + for qual, qualType := range filterQuals { + switch qualType { + case "string": + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) + } + case "bool": + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), equalQuals[qual].GetBoolValue())) + } + } + } + + return filters +} + +func buildGroupBoolNEFilter(quals plugin.KeyColumnQualMap) []string { + filters := []string{} + + filterQuals := []string{ + "mail_enabled", + "on_premises_sync_enabled", + "security_enabled", + } + + for _, qual := range filterQuals { + if quals[qual] != nil { + for _, q := range quals[qual].Quals { + value := q.Value.GetBoolValue() + if q.Operator == "<>" { + filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), !value)) + break + } + } + } + } + + return filters +} diff --git a/azuread/table_azuread_group1.go b/azuread/table_azuread_group1.go deleted file mode 100644 index 025fd63..0000000 --- a/azuread/table_azuread_group1.go +++ /dev/null @@ -1,374 +0,0 @@ -package azuread - -import ( - "context" - "errors" - "fmt" - "strings" - - "github.com/iancoleman/strcase" - msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" - "github.com/microsoftgraph/msgraph-sdk-go/groups" - "github.com/microsoftgraph/msgraph-sdk-go/groups/item" - "github.com/microsoftgraph/msgraph-sdk-go/groups/item/members" - "github.com/microsoftgraph/msgraph-sdk-go/groups/item/owners" - "github.com/microsoftgraph/msgraph-sdk-go/models" - - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" -) - -//// TABLE DEFINITION - -func tableAzureAdGroupTest() *plugin.Table { - return &plugin.Table{ - Name: "azuread_group_test", - Description: "Represents an Azure AD user account.", - Get: &plugin.GetConfig{ - Hydrate: getAdGroupTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), - }, - KeyColumns: plugin.SingleColumn("id"), - }, - List: &plugin.ListConfig{ - Hydrate: listAdGroupsTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery", "Invalid filter clause"}), - }, - KeyColumns: plugin.KeyColumnSlice{ - // Key fields - {Name: "display_name", Require: plugin.Optional}, - {Name: "filter", Require: plugin.Optional}, - {Name: "mail", Require: plugin.Optional}, - {Name: "mail_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, - {Name: "on_premises_sync_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, - {Name: "security_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, - }, - }, - Columns: []*plugin.Column{ - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name.", Transform: transform.FromMethod("GetDisplayName")}, - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the group.", Transform: transform.FromMethod("GetId")}, - {Name: "description", Type: proto.ColumnType_STRING, Description: "An optional description for the group.", Transform: transform.FromMethod("GetDescription")}, - {Name: "filter", Type: proto.ColumnType_STRING, Transform: transform.FromQual("filter"), Description: "Odata query to search for groups."}, - - // Other fields - {Name: "classification", Type: proto.ColumnType_STRING, Description: "Describes a classification for the group (such as low, medium or high business impact).", Transform: transform.FromMethod("GetClassification")}, - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the group was created.", Transform: transform.FromMethod("GetCreatedDateTime")}, - {Name: "expiration_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group is set to expire.", Transform: transform.FromMethod("GetExpirationDateTime")}, - {Name: "is_assignable_to_role", Type: proto.ColumnType_BOOL, Description: "Indicates whether this group can be assigned to an Azure Active Directory role or not.", Transform: transform.FromMethod("GetIsAssignableToRole")}, - - // Getting below error while requesting value for isSubscribedByMail - // { - // "error": { - // "code": "ErrorInvalidGroup", - // "message": "The requested group '[id@tenantId]' is invalid.", - // "innerError": { - // "date": "2022-07-13T11:06:23", - // "request-id": "63a83d86-a007-4c68-be75-21cea61d830e", - // "client-request-id": "d69d6667-e818-a322-c694-1fec40b438a8" - // } - // } - // } - // {Name: "is_subscribed_by_mail", Type: proto.ColumnType_BOOL, Description: "Indicates whether the signed-in user is subscribed to receive email conversations. Default value is true.", Transform: transform.FromMethod("GetIsSubscribedByMail")}, - - {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the group, for example, \"serviceadmins@contoso.onmicrosoft.com\".", Transform: transform.FromMethod("GetMail")}, - {Name: "mail_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is mail-enabled.", Transform: transform.FromMethod("GetMailEnabled")}, - {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user.", Transform: transform.FromMethod("GetMailNickname")}, - {Name: "membership_rule", Type: proto.ColumnType_STRING, Description: "The mail alias for the group, unique in the organization.", Transform: transform.FromMethod("GetMembershipRule")}, - {Name: "membership_rule_processing_state", Type: proto.ColumnType_STRING, Description: "Indicates whether the dynamic membership processing is on or paused. Possible values are On or Paused.", Transform: transform.FromMethod("GetMembershipRuleProcessingState")}, - {Name: "on_premises_domain_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises Domanin name synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesDomainName")}, - {Name: "on_premises_last_sync_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Indicates the last time at which the group was synced with the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesLastSyncDateTime")}, - {Name: "on_premises_net_bios_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises NetBiosName synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesNetBiosName")}, - {Name: "on_premises_sam_account_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises SAM account name synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesSamAccountName")}, - {Name: "on_premises_security_identifier", Type: proto.ColumnType_STRING, Description: "Contains the on-premises security identifier (SID) for the group that was synchronized from on-premises to the cloud.", Transform: transform.FromMethod("GetOnPremisesSecurityIdentifier")}, - {Name: "on_premises_sync_enabled", Type: proto.ColumnType_BOOL, Description: "True if this group is synced from an on-premises directory; false if this group was originally synced from an on-premises directory but is no longer synced; null if this object has never been synced from an on-premises directory (default).", Transform: transform.FromMethod("GetOnPremisesSyncEnabled")}, - {Name: "renewed_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group was last renewed. This cannot be modified directly and is only updated via the renew service action.", Transform: transform.FromMethod("GetRenewedDateTime")}, - {Name: "security_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is a security group.", Transform: transform.FromMethod("GetSecurityEnabled")}, - {Name: "security_identifier", Type: proto.ColumnType_STRING, Description: "Security identifier of the group, used in Windows scenarios.", Transform: transform.FromMethod("GetSecurityIdentifier")}, - {Name: "visibility", Type: proto.ColumnType_STRING, Description: "Specifies the group join policy and group content visibility for groups. Possible values are: Private, Public, or Hiddenmembership.", Transform: transform.FromMethod("GetVisibility")}, - - // Json fields - {Name: "assigned_labels", Type: proto.ColumnType_JSON, Description: "The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group.", Transform: transform.FromMethod("GroupAssignedLabels")}, - {Name: "group_types", Type: proto.ColumnType_JSON, Description: "Specifies the group type and its membership. If the collection contains Unified, the group is a Microsoft 365 group; otherwise, it's either a security group or distribution group. For details, see [groups overview](https://docs.microsoft.com/en-us/graph/api/resources/groups-overview?view=graph-rest-1.0).", Transform: transform.FromMethod("GetGroupTypes")}, - {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupMembers, Transform: transform.FromValue(), Description: "Id of Users and groups that are members of this group."}, - {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupOwners, Transform: transform.FromValue(), Description: "Id od the owners of the group. The owners are a set of non-admin users who are allowed to modify this object."}, - {Name: "proxy_addresses", Type: proto.ColumnType_JSON, Description: "Email addresses for the group that direct to the same group mailbox. For example: [\"SMTP: bob@contoso.com\", \"smtp: bob@sales.contoso.com\"]. The any operator is required to filter expressions on multi-valued properties.", Transform: transform.FromMethod("GetProxyAddresses")}, - // {Name: "resource_behavior_options", Type: proto.ColumnType_JSON, Description: "Specifies the group behaviors that can be set for a Microsoft 365 group during creation. Possible values are AllowOnlyMembersToPost, HideGroupInOutlook, SubscribeNewGroupMembers, WelcomeEmailDisabled."}, - // {Name: "resource_provisioning_options", Type: proto.ColumnType_JSON, Description: "Specifies the group resources that are provisioned as part of Microsoft 365 group creation, that are not normally part of default group creation. Possible value is Team."}, - - // Standard columns - {Name: "tags", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTags, Transform: transform.From(adGroupTags)}, - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adGroupTitle)}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, - }, - } -} - -//// LIST FUNCTION - -func listAdGroupsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - // List operations - input := &groups.GroupsRequestBuilderGetQueryParameters{} - - // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow - limit := d.QueryContext.Limit - if limit != nil { - if *limit > 0 && *limit <= 999 { - l := int32(*limit) - input.Top = &l - } - } - - equalQuals := d.KeyColumnQuals - quals := d.Quals - - var queryFilter string - filter := buildGroupQueryFilter(equalQuals) - filter = append(filter, buildGroupBoolNEFilter(quals)...) - - if equalQuals["filter"] != nil { - queryFilter = equalQuals["filter"].GetStringValue() - } - - if queryFilter != "" { - input.Filter = &queryFilter - } else if len(filter) > 0 { - joinStr := strings.Join(filter, " and ") - input.Filter = &joinStr - } - - options := &groups.GroupsRequestBuilderGetRequestConfiguration{ - QueryParameters: input, - } - - result, err := client.Groups().GetWithRequestConfigurationAndResponseHandler(options, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateGroupCollectionResponseFromDiscriminatorValue) - - err = pageIterator.Iterate(func(pageItem interface{}) bool { - group := pageItem.(models.Groupable) - - d.StreamListItem(ctx, &ADGroupInfo{group}) - - // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true - }) - - return nil, nil -} - -//// HYDRATE FUNCTIONS - -func getAdGroupTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - - groupId := d.KeyColumnQuals["id"].GetStringValue() - if groupId == "" { - return nil, nil - } - - // Create client - client, _, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - input := &item.GroupItemRequestBuilderGetQueryParameters{} - - options := &item.GroupItemRequestBuilderGetRequestConfiguration{ - QueryParameters: input, - } - - group, err := client.GroupsById(groupId).GetWithRequestConfigurationAndResponseHandler(options, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - return &ADGroupInfo{group}, nil -} - -func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - group := h.Item.(*ADGroupInfo) - groupID := group.GetId() - - headers := map[string]string{ - "ConsistencyLevel": "eventual", - } - - includeCount := true - requestParameters := &members.MembersRequestBuilderGetQueryParameters{ - Count: &includeCount, - } - - config := &members.MembersRequestBuilderGetRequestConfiguration{ - Headers: headers, - QueryParameters: requestParameters, - } - - memberIds := []*string{} - members, err := client.GroupsById(*groupID).Members().GetWithRequestConfigurationAndResponseHandler(config, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(members, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) - err = pageIterator.Iterate(func(pageItem interface{}) bool { - member := pageItem.(models.DirectoryObjectable) - memberIds = append(memberIds, member.GetId()) - - return true - }) - - return memberIds, nil -} - -func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - group := h.Item.(*ADGroupInfo) - groupID := group.GetId() - - headers := map[string]string{ - "ConsistencyLevel": "eventual", - } - - includeCount := true - requestParameters := &owners.OwnersRequestBuilderGetQueryParameters{ - Count: &includeCount, - } - - config := &owners.OwnersRequestBuilderGetRequestConfiguration{ - Headers: headers, - QueryParameters: requestParameters, - } - - ownerIds := []*string{} - owners, err := client.GroupsById(*groupID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) - err = pageIterator.Iterate(func(pageItem interface{}) bool { - member := pageItem.(models.DirectoryObjectable) - ownerIds = append(ownerIds, member.GetId()) - - return true - }) - - return ownerIds, nil -} - -//// TRANSFORM FUNCTIONS - -func adGroupTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { - group := d.HydrateItem.(*ADGroupInfo) - if group == nil { - return nil, nil - } - - assignedLabels := group.GroupAssignedLabels() - if assignedLabels == nil || len(assignedLabels) == 0 { - return nil, nil - } - - var tags = map[*string]*string{} - for _, i := range assignedLabels { - tags[i["labelId"]] = i["displayName"] - } - - return tags, nil -} - -func adGroupTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { - data := d.HydrateItem.(*ADGroupInfo) - if data == nil { - return nil, nil - } - - title := data.GetDisplayName() - if title == nil { - title = data.GetId() - } - - return title, nil -} - -func buildGroupQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { - filters := []string{} - - filterQuals := map[string]string{ - "display_name": "string", - "mail": "string", - "mail_enabled": "bool", - "on_premises_sync_enabled": "bool", - "security_enabled": "bool", - } - - for qual, qualType := range filterQuals { - switch qualType { - case "string": - if equalQuals[qual] != nil { - filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) - } - case "bool": - if equalQuals[qual] != nil { - filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), equalQuals[qual].GetBoolValue())) - } - } - } - - return filters -} - -func buildGroupBoolNEFilter(quals plugin.KeyColumnQualMap) []string { - filters := []string{} - - filterQuals := []string{ - "mail_enabled", - "on_premises_sync_enabled", - "security_enabled", - } - - for _, qual := range filterQuals { - if quals[qual] != nil { - for _, q := range quals[qual].Quals { - value := q.Value.GetBoolValue() - if q.Operator == "<>" { - filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), !value)) - break - } - } - } - } - - return filters -} diff --git a/azuread/table_azuread_service_principal.go b/azuread/table_azuread_service_principal.go index e204c8b..41ffcb6 100644 --- a/azuread/table_azuread_service_principal.go +++ b/azuread/table_azuread_service_principal.go @@ -2,14 +2,19 @@ package azuread import ( "context" + "errors" + "fmt" "strings" - "github.com/manicminer/hamilton/msgraph" - "github.com/manicminer/hamilton/odata" + "github.com/iancoleman/strcase" "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/serviceprincipals" + "github.com/microsoftgraph/msgraph-sdk-go/serviceprincipals/item/owners" ) //// TABLE DEFINITION @@ -19,52 +24,60 @@ func tableAzureAdServicePrincipal() *plugin.Table { Name: "azuread_service_principal", Description: "Represents an Azure Active Directory (Azure AD) service principal", Get: &plugin.GetConfig{ - Hydrate: getAdServicePrincipal, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), - KeyColumns: plugin.SingleColumn("id"), + Hydrate: getAdServicePrincipal, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, + KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ - Hydrate: listAdServicePrincipals, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Request_UnsupportedQuery"}), + Hydrate: listAdServicePrincipals, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery"}), + }, KeyColumns: plugin.KeyColumnSlice{ // Key fields {Name: "display_name", Require: plugin.Optional}, - {Name: "account_enabled", Require: plugin.Optional}, + {Name: "account_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, {Name: "service_principal_type", Require: plugin.Optional}, }, }, Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the service principal.", Transform: transform.FromGo()}, - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the service principal."}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the service principal.", Transform: transform.FromMethod("GetId")}, + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the service principal.", Transform: transform.FromMethod("GetDisplayName")}, + {Name: "app_id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the associated application (its appId property).", Transform: transform.FromMethod("GetAppId")}, // Other fields - {Name: "account_enabled", Type: proto.ColumnType_BOOL, Description: "true if the service principal account is enabled; otherwise, false."}, - {Name: "app_display_name", Type: proto.ColumnType_STRING, Description: "The display name exposed by the associated application."}, - {Name: "app_owner_organization_id", Type: proto.ColumnType_STRING, Description: "Contains the tenant id where the application is registered. This is applicable only to service principals backed by applications."}, - {Name: "app_role_assignment_required", Type: proto.ColumnType_BOOL, Description: "Specifies whether users or other service principals need to be granted an app role assignment for this service principal before users can sign in or apps can get tokens. The default value is false."}, - {Name: "service_principal_type", Type: proto.ColumnType_STRING, Description: "Identifies whether the service principal represents an application, a managed identity, or a legacy application. This is set by Azure AD internally."}, - {Name: "sign_in_audience", Type: proto.ColumnType_STRING, Description: "Specifies the Microsoft accounts that are supported for the current application. Supported values are: AzureADMyOrg, AzureADMultipleOrgs, AzureADandPersonalMicrosoftAccount, PersonalMicrosoftAccount."}, - - // // Json fields - {Name: "add_ins", Type: proto.ColumnType_JSON, Description: "Defines custom behavior that a consuming service can use to call an app in specific contexts."}, - {Name: "alternative_names", Type: proto.ColumnType_JSON, Description: "Used to retrieve service principals by subscription, identify resource group and full resource ids for managed identities."}, - {Name: "app_roles", Type: proto.ColumnType_JSON, Description: "The roles exposed by the application which this service principal represents."}, - {Name: "info", Type: proto.ColumnType_JSON, Description: "Basic profile information of the acquired application such as app's marketing, support, terms of service and privacy statement URLs."}, - {Name: "keyCredentials", Type: proto.ColumnType_JSON, Description: "The collection of key credentials associated with the service principal."}, - {Name: "notification_email_addresses", Type: proto.ColumnType_JSON, Description: "Specifies the list of email addresses where Azure AD sends a notification when the active certificate is near the expiration date. This is only for the certificates used to sign the SAML token issued for Azure AD Gallery applications."}, - {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getAdServicePrincipalOwners, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, - {Name: "password_credentials", Type: proto.ColumnType_JSON, Description: "Represents a password credential associated with a service principal."}, - {Name: "published_permission_scopes", Type: proto.ColumnType_JSON, Description: "The published permission scopes."}, - {Name: "reply_urls", Type: proto.ColumnType_JSON, Description: "The URLs that user tokens are sent to for sign in with the associated application, or the redirect URIs that OAuth 2.0 authorization codes and access tokens are sent to for the associated application."}, - {Name: "service_principal_names", Type: proto.ColumnType_JSON, Description: "Contains the list of identifiersUris, copied over from the associated application. Additional values can be added to hybrid applications. These values can be used to identify the permissions exposed by this app within Azure AD."}, - {Name: "tags_src", Type: proto.ColumnType_JSON, Description: "Custom strings that can be used to categorize and identify the service principal.", Transform: transform.FromField("Tags")}, - {Name: "verified_publisher", Type: proto.ColumnType_JSON, Description: "Specifies the verified publisher of the application which this service principal represents."}, - - // // Standard columns - {Name: "tags", Type: proto.ColumnType_JSON, Description: ColumnDescriptionTags, Transform: transform.From(servicePrincipalTags)}, - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "ID")}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenantId).WithCache(), Transform: transform.FromValue()}, + {Name: "account_enabled", Type: proto.ColumnType_BOOL, Description: "true if the service principal account is enabled; otherwise, false.", Transform: transform.FromMethod("GetAccountEnabled")}, + {Name: "app_display_name", Type: proto.ColumnType_STRING, Description: "The display name exposed by the associated application.", Transform: transform.FromMethod("GetAppDisplayName")}, + {Name: "app_owner_organization_id", Type: proto.ColumnType_STRING, Description: "Contains the tenant id where the application is registered. This is applicable only to service principals backed by applications.", Transform: transform.FromMethod("GetAppOwnerOrganizationId")}, + {Name: "app_role_assignment_required", Type: proto.ColumnType_BOOL, Description: "Specifies whether users or other service principals need to be granted an app role assignment for this service principal before users can sign in or apps can get tokens. The default value is false.", Transform: transform.FromMethod("GetAppRoleAssignmentRequired")}, + {Name: "service_principal_type", Type: proto.ColumnType_STRING, Description: "Identifies whether the service principal represents an application, a managed identity, or a legacy application. This is set by Azure AD internally.", Transform: transform.FromMethod("GetServicePrincipalType")}, + {Name: "sign_in_audience", Type: proto.ColumnType_STRING, Description: "Specifies the Microsoft accounts that are supported for the current application. Supported values are: AzureADMyOrg, AzureADMultipleOrgs, AzureADandPersonalMicrosoftAccount, PersonalMicrosoftAccount.", Transform: transform.FromMethod("GetSignInAudience")}, + {Name: "app_description", Type: proto.ColumnType_STRING, Description: "The description exposed by the associated application.", Transform: transform.FromMethod("GetAppDescription")}, + {Name: "description", Type: proto.ColumnType_STRING, Description: "Free text field to provide an internal end-user facing description of the service principal.", Transform: transform.FromMethod("GetDescription")}, + {Name: "login_url", Type: proto.ColumnType_STRING, Description: "Specifies the URL where the service provider redirects the user to Azure AD to authenticate. Azure AD uses the URL to launch the application from Microsoft 365 or the Azure AD My Apps. When blank, Azure AD performs IdP-initiated sign-on for applications configured with SAML-based single sign-on.", Transform: transform.FromMethod("GetLoginUrl")}, + {Name: "logout_url", Type: proto.ColumnType_STRING, Description: "Specifies the URL that will be used by Microsoft's authorization service to logout an user using OpenId Connect front-channel, back-channel or SAML logout protocols.", Transform: transform.FromMethod("GetLogoutUrl")}, + + // JSON fields + {Name: "add_ins", Type: proto.ColumnType_JSON, Description: "Defines custom behavior that a consuming service can use to call an app in specific contexts.", Transform: transform.FromMethod("ServicePrincipalAddIns")}, + {Name: "alternative_names", Type: proto.ColumnType_JSON, Description: "Used to retrieve service principals by subscription, identify resource group and full resource ids for managed identities.", Transform: transform.FromMethod("GetAlternativeNames")}, + {Name: "app_roles", Type: proto.ColumnType_JSON, Description: "The roles exposed by the application which this service principal represents.", Transform: transform.FromMethod("ServicePrincipalAppRoles")}, + {Name: "info", Type: proto.ColumnType_JSON, Description: "Basic profile information of the acquired application such as app's marketing, support, terms of service and privacy statement URLs.", Transform: transform.FromMethod("ServicePrincipalInfo")}, + {Name: "key_credentials", Type: proto.ColumnType_JSON, Description: "The collection of key credentials associated with the service principal.", Transform: transform.FromMethod("ServicePrincipalKeyCredentials")}, + {Name: "notification_email_addresses", Type: proto.ColumnType_JSON, Description: "Specifies the list of email addresses where Azure AD sends a notification when the active certificate is near the expiration date. This is only for the certificates used to sign the SAML token issued for Azure AD Gallery applications.", Transform: transform.FromMethod("GetNotificationEmailAddresses")}, + {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getServicePrincipalOwners, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, + {Name: "password_credentials", Type: proto.ColumnType_JSON, Description: "Represents a password credential associated with a service principal.", Transform: transform.FromMethod("ServicePrincipalPasswordCredentials")}, + {Name: "oauth2_permission_scopes", Type: proto.ColumnType_JSON, Description: "The published permission scopes.", Transform: transform.FromMethod("ServicePrincipalOauth2PermissionScopes")}, + {Name: "reply_urls", Type: proto.ColumnType_JSON, Description: "The URLs that user tokens are sent to for sign in with the associated application, or the redirect URIs that OAuth 2.0 authorization codes and access tokens are sent to for the associated application.", Transform: transform.FromMethod("GetReplyUrls")}, + {Name: "service_principal_names", Type: proto.ColumnType_JSON, Description: "Contains the list of identifiersUris, copied over from the associated application. Additional values can be added to hybrid applications. These values can be used to identify the permissions exposed by this app within Azure AD.", Transform: transform.FromMethod("GetServicePrincipalNames")}, + {Name: "tags_src", Type: proto.ColumnType_JSON, Description: "Custom strings that can be used to categorize and identify the service principal.", Transform: transform.FromMethod("GetTags")}, + + // Standard columns + {Name: "tags", Type: proto.ColumnType_JSON, Description: ColumnDescriptionTags, Transform: transform.From(adServicePrincipalTags)}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adServicePrincipalTitle)}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } } @@ -72,106 +85,205 @@ func tableAzureAdServicePrincipal() *plugin.Table { //// LIST FUNCTION func listAdServicePrincipals(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - session, err := GetNewSession(ctx, d) + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewServicePrincipalsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - - input := odata.Query{} + // List operations + input := &serviceprincipals.ServicePrincipalsRequestBuilderGetQueryParameters{} // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. limit := d.QueryContext.Limit if limit != nil { if *limit > 0 && *limit <= 999 { - input.Top = int(*limit) + l := int32(*limit) + input.Top = &l } } - qualsColumnMap := []QualsColumn{ - {"display_name", "string", "displayName"}, - {"account_enabled", "bool", "accountEnabled"}, - {"service_principal_type", "string", "servicePrincipalType"}, + var queryFilter string + equalQuals := d.KeyColumnQuals + quals := d.Quals + filter := buildServicePrincipalQueryFilter(equalQuals) + filter = append(filter, buildServicePrincipalBoolNEFilter(quals)...) + + if equalQuals["filter"] != nil { + queryFilter = equalQuals["filter"].GetStringValue() + } + + if queryFilter != "" { + input.Filter = &queryFilter + } else if len(filter) > 0 { + joinStr := strings.Join(filter, " and ") + input.Filter = &joinStr } - filter := buildCommaonQueryFilter(qualsColumnMap, d.Quals) - if len(filter) > 0 { - input.Filter = strings.Join(filter, " and ") + options := &serviceprincipals.ServicePrincipalsRequestBuilderGetRequestConfiguration{ + QueryParameters: input, } - servicePrincipals, _, err := client.List(ctx, input) + result, err := client.ServicePrincipals().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { - if isNotFoundError(err) { - return nil, nil - } - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - for _, servicePrincipal := range *servicePrincipals { - d.StreamListItem(ctx, servicePrincipal) + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateServicePrincipalCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + servicePrincipal := pageItem.(models.ServicePrincipalable) + + d.StreamListItem(ctx, &ADServicePrincipalInfo{servicePrincipal}) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { - return nil, nil + return false } - } - return nil, err + return true + }) + + return nil, nil } -//// Hydrate Functions +//// HYDRATE FUNCTIONS func getAdServicePrincipal(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var servicePrincipalId string - if h.Item != nil { - servicePrincipalId = *h.Item.(msgraph.ServicePrincipal).ID - } else { - servicePrincipalId = d.KeyColumnQuals["id"].GetStringValue() - } - if servicePrincipalId == "" { + servicePrincipalID := d.KeyColumnQuals["id"].GetStringValue() + if servicePrincipalID == "" { return nil, nil } - session, err := GetNewSession(ctx, d) + // Create client + client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewServicePrincipalsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - client.BaseClient.DisableRetries = true - - servicePrincipal, _, err := client.Get(ctx, servicePrincipalId, odata.Query{}) + servicePrincipal, err := client.ServicePrincipalsById(servicePrincipalID).Get() if err != nil { - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - return *servicePrincipal, nil + + return &ADServicePrincipalInfo{servicePrincipal}, nil } -func getAdServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - servicePrincipalId := *h.Item.(msgraph.ServicePrincipal).ID - session, err := GetNewSession(ctx, d) +func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewServicePrincipalsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer + servicePrincipal := h.Item.(*ADServicePrincipalInfo) + servicePrincipalID := servicePrincipal.GetId() + + if servicePrincipalID == nil { + return nil, nil + } + + headers := map[string]string{ + "ConsistencyLevel": "eventual", + } + + includeCount := true + requestParameters := &owners.OwnersRequestBuilderGetQueryParameters{ + Count: &includeCount, + } + + config := &owners.OwnersRequestBuilderGetRequestConfiguration{ + Headers: headers, + QueryParameters: requestParameters, + } - owners, _, err := client.ListOwners(ctx, servicePrincipalId) + ownerIds := []*string{} + owners, err := client.ServicePrincipalsById(*servicePrincipalID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - return owners, nil + + pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + err = pageIterator.Iterate(func(pageItem interface{}) bool { + owner := pageItem.(models.DirectoryObjectable) + ownerIds = append(ownerIds, owner.GetId()) + + return true + }) + + return ownerIds, nil +} + +//// TRANSFORM FUNCTIONS + +func adServicePrincipalTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { + servicePrincipal := d.HydrateItem.(*ADServicePrincipalInfo) + tags := servicePrincipal.GetTags() + return TagsToMap(tags) +} + +func adServicePrincipalTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADServicePrincipalInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetId() + } + + return title, nil } -//// Transform Function +func buildServicePrincipalQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { + filters := []string{} + + filterQuals := map[string]string{ + "display_name": "string", + "account_enabled": "bool", + "service_principal_type": "string", + } + + for qual, qualType := range filterQuals { + switch qualType { + case "string": + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) + } + case "bool": + if equalQuals[qual] != nil { + filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), equalQuals[qual].GetBoolValue())) + } + } + } + + return filters +} + +func buildServicePrincipalBoolNEFilter(quals plugin.KeyColumnQualMap) []string { + filters := []string{} + + filterQuals := []string{ + "account_enabled", + } + + for _, qual := range filterQuals { + if quals[qual] != nil { + for _, q := range quals[qual].Quals { + value := q.Value.GetBoolValue() + if q.Operator == "<>" { + filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), !value)) + break + } + } + } + } -func servicePrincipalTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { - servicePrincipal := d.HydrateItem.(msgraph.ServicePrincipal) - return TagsToMap(*servicePrincipal.Tags) + return filters } diff --git a/azuread/table_azuread_service_principal1.go b/azuread/table_azuread_service_principal1.go deleted file mode 100644 index 5eecf41..0000000 --- a/azuread/table_azuread_service_principal1.go +++ /dev/null @@ -1,290 +0,0 @@ -package azuread - -import ( - "context" - "errors" - "fmt" - "strings" - - "github.com/iancoleman/strcase" - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" - - msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" - "github.com/microsoftgraph/msgraph-sdk-go/models" - "github.com/microsoftgraph/msgraph-sdk-go/serviceprincipals" - "github.com/microsoftgraph/msgraph-sdk-go/serviceprincipals/item/owners" -) - -//// TABLE DEFINITION - -func tableAzureAdServicePrincipalTest() *plugin.Table { - return &plugin.Table{ - Name: "azuread_service_principal_test", - Description: "Represents an Azure Active Directory (Azure AD) service principal", - Get: &plugin.GetConfig{ - Hydrate: getAdServicePrincipalTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), - }, - KeyColumns: plugin.SingleColumn("id"), - }, - List: &plugin.ListConfig{ - Hydrate: listAdServicePrincipalsTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery"}), - }, - KeyColumns: plugin.KeyColumnSlice{ - // Key fields - {Name: "display_name", Require: plugin.Optional}, - {Name: "account_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, - {Name: "service_principal_type", Require: plugin.Optional}, - }, - }, - - Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the service principal.", Transform: transform.FromMethod("GetId")}, - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The display name for the service principal.", Transform: transform.FromMethod("GetDisplayName")}, - {Name: "app_id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the associated application (its appId property).", Transform: transform.FromMethod("GetAppId")}, - - // Other fields - {Name: "account_enabled", Type: proto.ColumnType_BOOL, Description: "true if the service principal account is enabled; otherwise, false.", Transform: transform.FromMethod("GetAccountEnabled")}, - {Name: "app_display_name", Type: proto.ColumnType_STRING, Description: "The display name exposed by the associated application.", Transform: transform.FromMethod("GetAppDisplayName")}, - {Name: "app_owner_organization_id", Type: proto.ColumnType_STRING, Description: "Contains the tenant id where the application is registered. This is applicable only to service principals backed by applications.", Transform: transform.FromMethod("GetAppOwnerOrganizationId")}, - {Name: "app_role_assignment_required", Type: proto.ColumnType_BOOL, Description: "Specifies whether users or other service principals need to be granted an app role assignment for this service principal before users can sign in or apps can get tokens. The default value is false.", Transform: transform.FromMethod("GetAppRoleAssignmentRequired")}, - {Name: "service_principal_type", Type: proto.ColumnType_STRING, Description: "Identifies whether the service principal represents an application, a managed identity, or a legacy application. This is set by Azure AD internally.", Transform: transform.FromMethod("GetServicePrincipalType")}, - {Name: "sign_in_audience", Type: proto.ColumnType_STRING, Description: "Specifies the Microsoft accounts that are supported for the current application. Supported values are: AzureADMyOrg, AzureADMultipleOrgs, AzureADandPersonalMicrosoftAccount, PersonalMicrosoftAccount.", Transform: transform.FromMethod("GetSignInAudience")}, - {Name: "app_description", Type: proto.ColumnType_STRING, Description: "The description exposed by the associated application.", Transform: transform.FromMethod("GetAppDescription")}, - {Name: "description", Type: proto.ColumnType_STRING, Description: "Free text field to provide an internal end-user facing description of the service principal.", Transform: transform.FromMethod("GetDescription")}, - {Name: "login_url", Type: proto.ColumnType_STRING, Description: "Specifies the URL where the service provider redirects the user to Azure AD to authenticate. Azure AD uses the URL to launch the application from Microsoft 365 or the Azure AD My Apps. When blank, Azure AD performs IdP-initiated sign-on for applications configured with SAML-based single sign-on.", Transform: transform.FromMethod("GetLoginUrl")}, - {Name: "logout_url", Type: proto.ColumnType_STRING, Description: "Specifies the URL that will be used by Microsoft's authorization service to logout an user using OpenId Connect front-channel, back-channel or SAML logout protocols.", Transform: transform.FromMethod("GetLogoutUrl")}, - - // Json fields - {Name: "add_ins", Type: proto.ColumnType_JSON, Description: "Defines custom behavior that a consuming service can use to call an app in specific contexts.", Transform: transform.FromMethod("ServicePrincipalAddIns")}, - {Name: "alternative_names", Type: proto.ColumnType_JSON, Description: "Used to retrieve service principals by subscription, identify resource group and full resource ids for managed identities.", Transform: transform.FromMethod("GetAlternativeNames")}, - {Name: "app_roles", Type: proto.ColumnType_JSON, Description: "The roles exposed by the application which this service principal represents.", Transform: transform.FromMethod("ServicePrincipalAppRoles")}, - {Name: "info", Type: proto.ColumnType_JSON, Description: "Basic profile information of the acquired application such as app's marketing, support, terms of service and privacy statement URLs.", Transform: transform.FromMethod("ServicePrincipalInfo")}, - {Name: "key_credentials", Type: proto.ColumnType_JSON, Description: "The collection of key credentials associated with the service principal.", Transform: transform.FromMethod("ServicePrincipalKeyCredentials")}, - {Name: "notification_email_addresses", Type: proto.ColumnType_JSON, Description: "Specifies the list of email addresses where Azure AD sends a notification when the active certificate is near the expiration date. This is only for the certificates used to sign the SAML token issued for Azure AD Gallery applications.", Transform: transform.FromMethod("GetNotificationEmailAddresses")}, - {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getServicePrincipalOwners, Transform: transform.FromValue(), Description: "Id of the owners of the application. The owners are a set of non-admin users who are allowed to modify this object."}, - {Name: "password_credentials", Type: proto.ColumnType_JSON, Description: "Represents a password credential associated with a service principal.", Transform: transform.FromMethod("ServicePrincipalPasswordCredentials")}, - {Name: "published_permission_scopes", Type: proto.ColumnType_JSON, Description: "The published permission scopes.", Transform: transform.FromMethod("ServicePrincipalOauth2PermissionScopes")}, - {Name: "reply_urls", Type: proto.ColumnType_JSON, Description: "The URLs that user tokens are sent to for sign in with the associated application, or the redirect URIs that OAuth 2.0 authorization codes and access tokens are sent to for the associated application.", Transform: transform.FromMethod("GetReplyUrls")}, - {Name: "service_principal_names", Type: proto.ColumnType_JSON, Description: "Contains the list of identifiersUris, copied over from the associated application. Additional values can be added to hybrid applications. These values can be used to identify the permissions exposed by this app within Azure AD.", Transform: transform.FromMethod("GetServicePrincipalNames")}, - {Name: "tags_src", Type: proto.ColumnType_JSON, Description: "Custom strings that can be used to categorize and identify the service principal.", Transform: transform.FromMethod("GetTags")}, - // {Name: "verified_publisher", Type: proto.ColumnType_JSON, Description: "Specifies the verified publisher of the application which this service principal represents."}, - - // Standard columns - {Name: "tags", Type: proto.ColumnType_JSON, Description: ColumnDescriptionTags, Transform: transform.From(adServicePrincipalTags)}, - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adServicePrincipalTitle)}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, - }, - } -} - -//// LIST FUNCTION - -func listAdServicePrincipalsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - // List operations - input := &serviceprincipals.ServicePrincipalsRequestBuilderGetQueryParameters{} - - // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow - // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. - limit := d.QueryContext.Limit - if limit != nil { - if *limit > 0 && *limit <= 999 { - l := int32(*limit) - input.Top = &l - } - } - - var queryFilter string - equalQuals := d.KeyColumnQuals - quals := d.Quals - filter := buildServicePrincipalQueryFilter(equalQuals) - filter = append(filter, buildServicePrincipalBoolNEFilter(quals)...) - - if equalQuals["filter"] != nil { - queryFilter = equalQuals["filter"].GetStringValue() - } - - if queryFilter != "" { - input.Filter = &queryFilter - } else if len(filter) > 0 { - joinStr := strings.Join(filter, " and ") - input.Filter = &joinStr - } - - options := &serviceprincipals.ServicePrincipalsRequestBuilderGetRequestConfiguration{ - QueryParameters: input, - } - - result, err := client.ServicePrincipals().GetWithRequestConfigurationAndResponseHandler(options, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateServicePrincipalCollectionResponseFromDiscriminatorValue) - - err = pageIterator.Iterate(func(pageItem interface{}) bool { - servicePrincipal := pageItem.(models.ServicePrincipalable) - - d.StreamListItem(ctx, &ADServicePrincipalInfo{servicePrincipal}) - - // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true - }) - - return nil, nil -} - -//// HYDRATE FUNCTIONS - -func getAdServicePrincipalTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - - servicePrincipalID := d.KeyColumnQuals["id"].GetStringValue() - if servicePrincipalID == "" { - return nil, nil - } - - // Create client - client, _, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - servicePrincipal, err := client.ServicePrincipalsById(servicePrincipalID).Get() - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - return &ADServicePrincipalInfo{servicePrincipal}, nil -} - -func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - servicePrincipal := h.Item.(*ADServicePrincipalInfo) - servicePrincipalID := servicePrincipal.GetId() - - if servicePrincipalID == nil { - return nil, nil - } - - headers := map[string]string{ - "ConsistencyLevel": "eventual", - } - - includeCount := true - requestParameters := &owners.OwnersRequestBuilderGetQueryParameters{ - Count: &includeCount, - } - - config := &owners.OwnersRequestBuilderGetRequestConfiguration{ - Headers: headers, - QueryParameters: requestParameters, - } - - ownerIds := []*string{} - owners, err := client.ServicePrincipalsById(*servicePrincipalID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) - err = pageIterator.Iterate(func(pageItem interface{}) bool { - owner := pageItem.(models.DirectoryObjectable) - ownerIds = append(ownerIds, owner.GetId()) - - return true - }) - - return ownerIds, nil -} - -//// TRANSFORM FUNCTIONS - -func adServicePrincipalTags(ctx context.Context, d *transform.TransformData) (interface{}, error) { - servicePrincipal := d.HydrateItem.(*ADServicePrincipalInfo) - tags := servicePrincipal.GetTags() - return TagsToMap(tags) -} - -func adServicePrincipalTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { - data := d.HydrateItem.(*ADServicePrincipalInfo) - if data == nil { - return nil, nil - } - - title := data.GetDisplayName() - if title == nil { - title = data.GetId() - } - - return title, nil -} - -func buildServicePrincipalQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { - filters := []string{} - - filterQuals := map[string]string{ - "display_name": "string", - "account_enabled": "bool", - "service_principal_type": "string", - } - - for qual, qualType := range filterQuals { - switch qualType { - case "string": - if equalQuals[qual] != nil { - filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) - } - case "bool": - if equalQuals[qual] != nil { - filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), equalQuals[qual].GetBoolValue())) - } - } - } - - return filters -} - -func buildServicePrincipalBoolNEFilter(quals plugin.KeyColumnQualMap) []string { - filters := []string{} - - filterQuals := []string{ - "account_enabled", - } - - for _, qual := range filterQuals { - if quals[qual] != nil { - for _, q := range quals[qual].Quals { - value := q.Value.GetBoolValue() - if q.Operator == "<>" { - filters = append(filters, fmt.Sprintf("%s eq %t", strcase.ToCamel(qual), !value)) - break - } - } - } - } - - return filters -} diff --git a/azuread/table_azuread_sign_in_report.go b/azuread/table_azuread_sign_in_report.go index b47314d..da9960b 100644 --- a/azuread/table_azuread_sign_in_report.go +++ b/azuread/table_azuread_sign_in_report.go @@ -2,13 +2,16 @@ package azuread import ( "context" + "errors" + "fmt" - "github.com/manicminer/hamilton/msgraph" - "github.com/manicminer/hamilton/odata" - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/auditlogs/signins" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" ) //// TABLE DEFINITION @@ -18,119 +21,131 @@ func tableAzureAdSignInReport() *plugin.Table { Name: "azuread_sign_in_report", Description: "Represents an Azure Active Directory (Azure AD) sign in report", Get: &plugin.GetConfig{ - Hydrate: getAdSigninReport, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), - KeyColumns: plugin.SingleColumn("id"), + Hydrate: getAdSignInReport, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, + KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ - Hydrate: listAdSigninReports, + Hydrate: listAdSignInReports, }, Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "Unique ID representing the sign-in activity."}, - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Date and time (UTC) the sign-in was initiated."}, - {Name: "user_display_name", Type: proto.ColumnType_STRING, Description: "Display name of the user that initiated the sign-in."}, - {Name: "user_principal_name", Type: proto.ColumnType_STRING, Description: "User principal name of the user that initiated the sign-in."}, - {Name: "user_id", Type: proto.ColumnType_STRING, Description: "ID of the user that initiated the sign-in."}, - {Name: "app_id", Type: proto.ColumnType_STRING, Description: "Unique GUID representing the app ID in the Azure Active Directory."}, - {Name: "app_display_name", Type: proto.ColumnType_STRING, Description: "App name displayed in the Azure Portal."}, - {Name: "ip_address", Type: proto.ColumnType_STRING, Description: "IP address of the client used to sign in.", Transform: transform.FromField("IPAddress")}, - {Name: "client_app_used", Type: proto.ColumnType_STRING, Description: "Identifies the legacy client used for sign-in activity."}, - {Name: "correlation_id", Type: proto.ColumnType_STRING, Description: "The request ID sent from the client when the sign-in is initiated; used to troubleshoot sign-in activity."}, - {Name: "conditional_access_status", Type: proto.ColumnType_STRING, Description: "Reports status of an activated conditional access policy. Possible values are: success, failure, notApplied, and unknownFutureValue."}, - {Name: "is_interactive", Type: proto.ColumnType_BOOL, Description: "Indicates if a sign-in is interactive or not."}, - {Name: "risk_detail", Type: proto.ColumnType_STRING, Description: "Provides the 'reason' behind a specific state of a risky user, sign-in or a risk event. The possible values are: none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, unknownFutureValue."}, - {Name: "risk_level_aggregated", Type: proto.ColumnType_STRING, Description: "Aggregated risk level. The possible values are: none, low, medium, high, hidden, and unknownFutureValue."}, - {Name: "risk_level_during_sign_in", Type: proto.ColumnType_STRING, Description: "Risk level during sign-in. The possible values are: none, low, medium, high, hidden, and unknownFutureValue."}, - {Name: "risk_state", Type: proto.ColumnType_STRING, Description: "Reports status of the risky user, sign-in, or a risk event. The possible values are: none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised, unknownFutureValue."}, - {Name: "resource_display_name", Type: proto.ColumnType_STRING, Description: "Name of the resource the user signed into."}, - {Name: "resource_id", Type: proto.ColumnType_STRING, Description: "ID of the resource that the user signed into."}, - - // Json fields - {Name: "risk_event_types", Type: proto.ColumnType_JSON, Description: "Risk event types associated with the sign-in. The possible values are: unlikelyTravel, anonymizedIPAddress, maliciousIPAddress, unfamiliarFeatures, malwareInfectedIPAddress, suspiciousIPAddress, leakedCredentials, investigationsThreatIntelligence, generic, and unknownFutureValue."}, - {Name: "status", Type: proto.ColumnType_JSON, Description: "Sign-in status. Includes the error code and description of the error (in case of a sign-in failure)."}, - {Name: "device_detail", Type: proto.ColumnType_JSON, Description: "Device information from where the sign-in occurred; includes device ID, operating system, and browser."}, - {Name: "location", Type: proto.ColumnType_JSON, Description: " Provides the city, state, and country code where the sign-in originated."}, - {Name: "applied_conditional_access_policies", Type: proto.ColumnType_JSON, Description: "Provides a list of conditional access policies that are triggered by the corresponding sign-in activity."}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "Unique ID representing the sign-in activity.", Transform: transform.FromMethod("GetId")}, + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Date and time (UTC) the sign-in was initiated.", Transform: transform.FromMethod("GetCreatedDateTime")}, + {Name: "user_display_name", Type: proto.ColumnType_STRING, Description: "Display name of the user that initiated the sign-in.", Transform: transform.FromMethod("GetUserDisplayName")}, + {Name: "user_principal_name", Type: proto.ColumnType_STRING, Description: "User principal name of the user that initiated the sign-in.", Transform: transform.FromMethod("GetUserPrincipalName")}, + {Name: "user_id", Type: proto.ColumnType_STRING, Description: "ID of the user that initiated the sign-in.", Transform: transform.FromMethod("GetUserId")}, + {Name: "app_id", Type: proto.ColumnType_STRING, Description: "Unique GUID representing the app ID in the Azure Active Directory.", Transform: transform.FromMethod("GetAppId")}, + {Name: "app_display_name", Type: proto.ColumnType_STRING, Description: "App name displayed in the Azure Portal.", Transform: transform.FromMethod("GetAppDisplayName")}, + {Name: "ip_address", Type: proto.ColumnType_IPADDR, Description: "IP address of the client used to sign in.", Transform: transform.FromMethod("GetIpAddress")}, + {Name: "client_app_used", Type: proto.ColumnType_STRING, Description: "Identifies the legacy client used for sign-in activity.", Transform: transform.FromMethod("GetClientAppUsed")}, + {Name: "correlation_id", Type: proto.ColumnType_STRING, Description: "The request ID sent from the client when the sign-in is initiated; used to troubleshoot sign-in activity.", Transform: transform.FromMethod("GetCorrelationId")}, + {Name: "conditional_access_status", Type: proto.ColumnType_STRING, Description: "Reports status of an activated conditional access policy. Possible values are: success, failure, notApplied, and unknownFutureValue.", Transform: transform.FromMethod("GetConditionalAccessStatus")}, + {Name: "is_interactive", Type: proto.ColumnType_BOOL, Description: "Indicates if a sign-in is interactive or not.", Transform: transform.FromMethod("GetIsInteractive")}, + {Name: "risk_detail", Type: proto.ColumnType_STRING, Description: "Provides the 'reason' behind a specific state of a risky user, sign-in or a risk event. The possible values are: none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, unknownFutureValue.", Transform: transform.FromMethod("GetRiskDetail")}, + {Name: "risk_level_aggregated", Type: proto.ColumnType_STRING, Description: "Aggregated risk level. The possible values are: none, low, medium, high, hidden, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskLevelAggregated")}, + {Name: "risk_level_during_sign_in", Type: proto.ColumnType_STRING, Description: "Risk level during sign-in. The possible values are: none, low, medium, high, hidden, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskLevelDuringSignIn")}, + {Name: "risk_state", Type: proto.ColumnType_STRING, Description: "Reports status of the risky user, sign-in, or a risk event. The possible values are: none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised, unknownFutureValue.", Transform: transform.FromMethod("GetRiskState")}, + {Name: "resource_display_name", Type: proto.ColumnType_STRING, Description: "Name of the resource the user signed into.", Transform: transform.FromMethod("GetResourceDisplayName")}, + {Name: "resource_id", Type: proto.ColumnType_STRING, Description: "ID of the resource that the user signed into.", Transform: transform.FromMethod("GetResourceId")}, + + // JSON fields + {Name: "risk_event_types", Type: proto.ColumnType_JSON, Description: "Risk event types associated with the sign-in. The possible values are: unlikelyTravel, anonymizedIPAddress, maliciousIPAddress, unfamiliarFeatures, malwareInfectedIPAddress, suspiciousIPAddress, leakedCredentials, investigationsThreatIntelligence, generic, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskEventTypes").Transform(formatSignInReportRiskEventTypes)}, + {Name: "status", Type: proto.ColumnType_JSON, Description: "Sign-in status. Includes the error code and description of the error (in case of a sign-in failure).", Transform: transform.FromMethod("SignInStatus")}, + {Name: "device_detail", Type: proto.ColumnType_JSON, Description: "Device information from where the sign-in occurred; includes device ID, operating system, and browser.", Transform: transform.FromMethod("SignInDeviceDetail")}, + {Name: "location", Type: proto.ColumnType_JSON, Description: " Provides the city, state, and country code where the sign-in originated.", Transform: transform.FromMethod("SignInLocation")}, + {Name: "applied_conditional_access_policies", Type: proto.ColumnType_JSON, Description: "Provides a list of conditional access policies that are triggered by the corresponding sign-in activity.", Transform: transform.FromMethod("SignInAppliedConditionalAccessPolicies")}, // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("Id")}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenantId).WithCache(), Transform: transform.FromValue()}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromMethod("GetId")}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } } //// LIST FUNCTION -func listAdSigninReports(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - logger := plugin.Logger(ctx) - session, err := GetNewSession(ctx, d) +func listAdSignInReports(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewSignInLogsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - - input := odata.Query{} + // List operations + input := &signins.SignInsRequestBuilderGetQueryParameters{} // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. limit := d.QueryContext.Limit if limit != nil { if *limit > 0 && *limit <= 999 { - input.Top = int(*limit) + l := int32(*limit) + input.Top = &l } } - signInReports, _, err := client.List(ctx, input) + options := &signins.SignInsRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + result, err := client.AuditLogs().SignIns().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { - if isNotFoundError(err) { - return nil, nil - } - logger.Error("listAdSigninReports", "list", err) - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - for _, report := range *signInReports { - d.StreamListItem(ctx, report) + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateSignInCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + signIn := pageItem.(models.SignInable) + + d.StreamListItem(ctx, &ADSignInReportInfo{signIn}) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { - return nil, nil + return false } - } - return nil, err + return true + }) + + return nil, nil } -//// Hydrate Functions +//// HYDRATE FUNCTIONS -func getAdSigninReport(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - logger := plugin.Logger(ctx) - var signInId string - if h.Item != nil { - signInId = *h.Item.(msgraph.ServicePrincipal).ID - } else { - signInId = d.KeyColumnQuals["id"].GetStringValue() +func getAdSignInReport(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + signInID := d.KeyColumnQuals["id"].GetStringValue() + if signInID == "" { + return nil, nil } - if signInId == "" { - return nil, nil + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - session, err := GetNewSession(ctx, d) + + signIn, err := client.AuditLogs().SignInsById(signInID).Get() if err != nil { - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - client := msgraph.NewSignInLogsClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - client.BaseClient.DisableRetries = true + return &ADSignInReportInfo{signIn}, nil +} - signInReport, _, err := client.Get(ctx, signInId, odata.Query{}) - if err != nil { - logger.Error("getAdSigninReport", "get", err) - return nil, err +//// TRANSFORM FUNCTIONS + +func formatSignInReportRiskEventTypes(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADSignInReportInfo) + riskEventTypes := data.GetRiskEventTypes() + if riskEventTypes == nil || len(riskEventTypes) == 0 { + return nil, nil } - return *signInReport, nil + + return riskEventTypes, nil } diff --git a/azuread/table_azuread_sign_in_report1.go b/azuread/table_azuread_sign_in_report1.go deleted file mode 100644 index 2a04761..0000000 --- a/azuread/table_azuread_sign_in_report1.go +++ /dev/null @@ -1,151 +0,0 @@ -package azuread - -import ( - "context" - "errors" - "fmt" - - msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" - "github.com/microsoftgraph/msgraph-sdk-go/auditlogs/signins" - "github.com/microsoftgraph/msgraph-sdk-go/models" - - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" -) - -//// TABLE DEFINITION - -func tableAzureAdSignInReportTest() *plugin.Table { - return &plugin.Table{ - Name: "azuread_sign_in_report_test", - Description: "Represents an Azure Active Directory (Azure AD) sign in report", - Get: &plugin.GetConfig{ - Hydrate: getAdSignInReportTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), - }, - KeyColumns: plugin.SingleColumn("id"), - }, - List: &plugin.ListConfig{ - Hydrate: listAdSignInReportsTest, - }, - - Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "Unique ID representing the sign-in activity.", Transform: transform.FromMethod("GetId")}, - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Date and time (UTC) the sign-in was initiated.", Transform: transform.FromMethod("GetCreatedDateTime")}, - {Name: "user_display_name", Type: proto.ColumnType_STRING, Description: "Display name of the user that initiated the sign-in.", Transform: transform.FromMethod("GetUserDisplayName")}, - {Name: "user_principal_name", Type: proto.ColumnType_STRING, Description: "User principal name of the user that initiated the sign-in.", Transform: transform.FromMethod("GetUserPrincipalName")}, - {Name: "user_id", Type: proto.ColumnType_STRING, Description: "ID of the user that initiated the sign-in.", Transform: transform.FromMethod("GetUserId")}, - {Name: "app_id", Type: proto.ColumnType_STRING, Description: "Unique GUID representing the app ID in the Azure Active Directory.", Transform: transform.FromMethod("GetAppId")}, - {Name: "app_display_name", Type: proto.ColumnType_STRING, Description: "App name displayed in the Azure Portal.", Transform: transform.FromMethod("GetAppDisplayName")}, - {Name: "ip_address", Type: proto.ColumnType_IPADDR, Description: "IP address of the client used to sign in.", Transform: transform.FromMethod("GetIpAddress")}, - {Name: "client_app_used", Type: proto.ColumnType_STRING, Description: "Identifies the legacy client used for sign-in activity.", Transform: transform.FromMethod("GetClientAppUsed")}, - {Name: "correlation_id", Type: proto.ColumnType_STRING, Description: "The request ID sent from the client when the sign-in is initiated; used to troubleshoot sign-in activity.", Transform: transform.FromMethod("GetCorrelationId")}, - {Name: "conditional_access_status", Type: proto.ColumnType_STRING, Description: "Reports status of an activated conditional access policy. Possible values are: success, failure, notApplied, and unknownFutureValue.", Transform: transform.FromMethod("GetConditionalAccessStatus")}, - {Name: "is_interactive", Type: proto.ColumnType_BOOL, Description: "Indicates if a sign-in is interactive or not.", Transform: transform.FromMethod("GetIsInteractive")}, - {Name: "risk_detail", Type: proto.ColumnType_STRING, Description: "Provides the 'reason' behind a specific state of a risky user, sign-in or a risk event. The possible values are: none, adminGeneratedTemporaryPassword, userPerformedSecuredPasswordChange, userPerformedSecuredPasswordReset, adminConfirmedSigninSafe, aiConfirmedSigninSafe, userPassedMFADrivenByRiskBasedPolicy, adminDismissedAllRiskForUser, adminConfirmedSigninCompromised, unknownFutureValue.", Transform: transform.FromMethod("GetRiskDetail")}, - {Name: "risk_level_aggregated", Type: proto.ColumnType_STRING, Description: "Aggregated risk level. The possible values are: none, low, medium, high, hidden, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskLevelAggregated")}, - {Name: "risk_level_during_sign_in", Type: proto.ColumnType_STRING, Description: "Risk level during sign-in. The possible values are: none, low, medium, high, hidden, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskLevelDuringSignIn")}, - {Name: "risk_state", Type: proto.ColumnType_STRING, Description: "Reports status of the risky user, sign-in, or a risk event. The possible values are: none, confirmedSafe, remediated, dismissed, atRisk, confirmedCompromised, unknownFutureValue.", Transform: transform.FromMethod("GetRiskState")}, - {Name: "resource_display_name", Type: proto.ColumnType_STRING, Description: "Name of the resource the user signed into.", Transform: transform.FromMethod("GetResourceDisplayName")}, - {Name: "resource_id", Type: proto.ColumnType_STRING, Description: "ID of the resource that the user signed into.", Transform: transform.FromMethod("GetResourceId")}, - - // JSON fields - {Name: "risk_event_types", Type: proto.ColumnType_JSON, Description: "Risk event types associated with the sign-in. The possible values are: unlikelyTravel, anonymizedIPAddress, maliciousIPAddress, unfamiliarFeatures, malwareInfectedIPAddress, suspiciousIPAddress, leakedCredentials, investigationsThreatIntelligence, generic, and unknownFutureValue.", Transform: transform.FromMethod("GetRiskEventTypes").Transform(formatSignInReportRiskEventTypes)}, - {Name: "status", Type: proto.ColumnType_JSON, Description: "Sign-in status. Includes the error code and description of the error (in case of a sign-in failure).", Transform: transform.FromMethod("SignInStatus")}, - {Name: "device_detail", Type: proto.ColumnType_JSON, Description: "Device information from where the sign-in occurred; includes device ID, operating system, and browser.", Transform: transform.FromMethod("SignInDeviceDetail")}, - {Name: "location", Type: proto.ColumnType_JSON, Description: " Provides the city, state, and country code where the sign-in originated.", Transform: transform.FromMethod("SignInLocation")}, - {Name: "applied_conditional_access_policies", Type: proto.ColumnType_JSON, Description: "Provides a list of conditional access policies that are triggered by the corresponding sign-in activity.", Transform: transform.FromMethod("SignInAppliedConditionalAccessPolicies")}, - - // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromMethod("GetId")}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, - }, - } -} - -//// LIST FUNCTION - -func listAdSignInReportsTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - // List operations - input := &signins.SignInsRequestBuilderGetQueryParameters{} - - // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow - // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. - limit := d.QueryContext.Limit - if limit != nil { - if *limit > 0 && *limit <= 999 { - l := int32(*limit) - input.Top = &l - } - } - - options := &signins.SignInsRequestBuilderGetRequestConfiguration{ - QueryParameters: input, - } - - result, err := client.AuditLogs().SignIns().GetWithRequestConfigurationAndResponseHandler(options, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateSignInCollectionResponseFromDiscriminatorValue) - - err = pageIterator.Iterate(func(pageItem interface{}) bool { - signIn := pageItem.(models.SignInable) - - d.StreamListItem(ctx, &ADSignInReportInfo{signIn}) - - // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true - }) - - return nil, nil -} - -//// HYDRATE FUNCTIONS - -func getAdSignInReportTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - signInID := d.KeyColumnQuals["id"].GetStringValue() - if signInID == "" { - return nil, nil - } - - // Create client - client, _, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - signIn, err := client.AuditLogs().SignInsById(signInID).Get() - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - return &ADSignInReportInfo{signIn}, nil -} - -//// TRANSFORM FUNCTIONS - -func formatSignInReportRiskEventTypes(_ context.Context, d *transform.TransformData) (interface{}, error) { - data := d.HydrateItem.(*ADSignInReportInfo) - riskEventTypes := data.GetRiskEventTypes() - if riskEventTypes == nil || len(riskEventTypes) == 0 { - return nil, nil - } - - return riskEventTypes, nil -} diff --git a/azuread/table_azuread_user.go b/azuread/table_azuread_user.go index 6fc75b5..79a6f0d 100644 --- a/azuread/table_azuread_user.go +++ b/azuread/table_azuread_user.go @@ -2,17 +2,20 @@ package azuread import ( "context" + "errors" "fmt" + "os" "strings" - "github.com/ettle/strcase" - "github.com/manicminer/hamilton/msgraph" - "github.com/manicminer/hamilton/odata" - "github.com/turbot/go-kit/helpers" - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" + "github.com/iancoleman/strcase" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/microsoftgraph/msgraph-sdk-go/users" + "github.com/microsoftgraph/msgraph-sdk-go/users/item" + "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" ) //// TABLE DEFINITION @@ -22,15 +25,16 @@ func tableAzureAdUser() *plugin.Table { Name: "azuread_user", Description: "Represents an Azure AD user account.", Get: &plugin.GetConfig{ - Hydrate: getAdUser, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), - KeyColumns: plugin.SingleColumn("id"), + Hydrate: getAdUser, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), + }, + KeyColumns: plugin.SingleColumn("id"), }, List: &plugin.ListConfig{ Hydrate: listAdUsers, KeyColumns: plugin.KeyColumnSlice{ // Key fields - {Name: "id", Require: plugin.Optional}, {Name: "user_principal_name", Require: plugin.Optional}, {Name: "filter", Require: plugin.Optional}, @@ -43,36 +47,35 @@ func tableAzureAdUser() *plugin.Table { }, Columns: []*plugin.Column{ - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name."}, - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the user. Should be treated as an opaque identifier.", Transform: transform.FromGo()}, - {Name: "user_principal_name", Type: proto.ColumnType_STRING, Description: "Principal email of the active directory user."}, - {Name: "account_enabled", Type: proto.ColumnType_BOOL, Description: "True if the account is enabled; otherwise, false."}, - {Name: "user_type", Type: proto.ColumnType_STRING, Description: "A string value that can be used to classify user types in your directory."}, - {Name: "given_name", Type: proto.ColumnType_STRING, Description: "The given name (first name) of the user."}, - {Name: "surname", Type: proto.ColumnType_STRING, Description: "Family name or last name of the active directory user."}, + {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name.", Transform: transform.FromMethod("GetDisplayName")}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the user. Should be treated as an opaque identifier.", Transform: transform.FromMethod("GetId")}, + {Name: "user_principal_name", Type: proto.ColumnType_STRING, Description: "Principal email of the active directory user.", Transform: transform.FromMethod("GetUserPrincipalName")}, + {Name: "account_enabled", Type: proto.ColumnType_BOOL, Description: "True if the account is enabled; otherwise, false.", Transform: transform.FromMethod("GetAccountEnabled")}, + {Name: "user_type", Type: proto.ColumnType_STRING, Description: "A string value that can be used to classify user types in your directory.", Transform: transform.FromMethod("GetUserType")}, + {Name: "given_name", Type: proto.ColumnType_STRING, Description: "The given name (first name) of the user.", Transform: transform.FromMethod("GetGivenName")}, + {Name: "surname", Type: proto.ColumnType_STRING, Description: "Family name or last name of the active directory user.", Transform: transform.FromMethod("GetSurname")}, {Name: "filter", Type: proto.ColumnType_STRING, Transform: transform.FromQual("filter"), Description: "Odata query to search for resources."}, // Other fields - {Name: "on_premises_immutable_id", Type: proto.ColumnType_STRING, Description: "Used to associate an on-premises Active Directory user account with their Azure AD user object."}, - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the user was created."}, - {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the user, for example, jeff@contoso.onmicrosoft.com."}, - {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user."}, - {Name: "password_policies", Type: proto.ColumnType_STRING, Description: "Specifies password policies for the user. This value is an enumeration with one possible value being DisableStrongPassword, which allows weaker passwords than the default policy to be specified. DisablePasswordExpiration can also be specified. The two may be specified together; for example: DisablePasswordExpiration, DisableStrongPassword."}, - {Name: "refresh_tokens_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph)."}, - {Name: "sign_in_sessions_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph)."}, - {Name: "usage_location", Type: proto.ColumnType_STRING, Description: "A two letter country code (ISO standard 3166), required for users that will be assigned licenses due to legal requirement to check for availability of services in countries."}, + {Name: "on_premises_immutable_id", Type: proto.ColumnType_STRING, Description: "Used to associate an on-premises Active Directory user account with their Azure AD user object.", Transform: transform.FromMethod("GetOnPremisesImmutableId")}, + {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the user was created.", Transform: transform.FromMethod("GetCreatedDateTime")}, + {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the user, for example, jeff@contoso.onmicrosoft.com.", Transform: transform.FromMethod("GetMail")}, + {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user.", Transform: transform.FromMethod("GetMailNickname")}, + {Name: "password_policies", Type: proto.ColumnType_STRING, Description: "Specifies password policies for the user. This value is an enumeration with one possible value being DisableStrongPassword, which allows weaker passwords than the default policy to be specified. DisablePasswordExpiration can also be specified. The two may be specified together; for example: DisablePasswordExpiration, DisableStrongPassword.", Transform: transform.FromMethod("GetPasswordPolicies")}, + // {Name: "refresh_tokens_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph)."}, + {Name: "sign_in_sessions_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph).", Transform: transform.FromMethod("GetSignInSessionsValidFromDateTime")}, + {Name: "usage_location", Type: proto.ColumnType_STRING, Description: "A two letter country code (ISO standard 3166), required for users that will be assigned licenses due to legal requirement to check for availability of services in countries.", Transform: transform.FromMethod("GetUsageLocation")}, // Json fields - {Name: "member_of", Type: proto.ColumnType_JSON, Description: "A list the groups and directory roles that the user is a direct member of."}, - {Name: "additional_properties", Type: proto.ColumnType_JSON, Description: "A list of unmatched properties from the message are deserialized this collection."}, - {Name: "im_addresses", Type: proto.ColumnType_JSON, Description: "The instant message voice over IP (VOIP) session initiation protocol (SIP) addresses for the user."}, - {Name: "other_mails", Type: proto.ColumnType_JSON, Description: "A list of additional email addresses for the user."}, - {Name: "password_profile", Type: proto.ColumnType_JSON, Description: "Specifies the password profile for the user. The profile contains the user’s password. This property is required when a user is created."}, + {Name: "member_of", Type: proto.ColumnType_JSON, Description: "A list the groups and directory roles that the user is a direct member of.", Transform: transform.FromMethod("UserMemberOf")}, + {Name: "im_addresses", Type: proto.ColumnType_JSON, Description: "The instant message voice over IP (VOIP) session initiation protocol (SIP) addresses for the user.", Transform: transform.FromMethod("GetImAddresses")}, + {Name: "other_mails", Type: proto.ColumnType_JSON, Description: "A list of additional email addresses for the user.", Transform: transform.FromMethod("GetOtherMails")}, + {Name: "password_profile", Type: proto.ColumnType_JSON, Description: "Specifies the password profile for the user. The profile contains the user’s password. This property is required when a user is created.", Transform: transform.FromMethod("UserPasswordProfile")}, // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "UserPrincipalName")}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenantId).WithCache(), Transform: transform.FromValue()}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adUserTitle)}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } } @@ -80,35 +83,34 @@ func tableAzureAdUser() *plugin.Table { //// LIST FUNCTION func listAdUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - session, err := GetNewSession(ctx, d) + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } - client := msgraph.NewUsersClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer + // List operations + input := &users.UsersRequestBuilderGetQueryParameters{} - input := odata.Query{} - // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow - // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. limit := d.QueryContext.Limit if limit != nil { if *limit > 0 && *limit <= 999 { - input.Top = int(*limit) - } - } - - if helpers.StringSliceContains(d.QueryContext.Columns, "member_of") { - input.Expand = odata.Expand{ - Relationship: "memberOf", - Select: []string{"id", "displayName"}, + l := int32(*limit) + input.Top = &l } } equalQuals := d.KeyColumnQuals quals := d.Quals + // Check for query context and requests only for queried columns + givenColumns := d.QueryContext.Columns + selectColumns, expandColumns := buildUserRequestFields(ctx, givenColumns) + + input.Select = selectColumns + input.Expand = expandColumns + var queryFilter string filter := buildQueryFilter(equalQuals) filter = append(filter, buildBoolNEFilter(quals)...) @@ -118,78 +120,137 @@ func listAdUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData } if queryFilter != "" { - input.Filter = queryFilter + input.Filter = &queryFilter } else if len(filter) > 0 { - input.Filter = strings.Join(filter, " and ") + joinStr := strings.Join(filter, " and ") + input.Filter = &joinStr + } + + options := &users.UsersRequestBuilderGetRequestConfiguration{ + QueryParameters: input, } - users, _, err := client.List(ctx, input) + result, err := client.Users().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { - if isNotFoundError(err) { - return nil, nil - } - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - for _, user := range *users { - d.StreamListItem(ctx, user) + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateUserCollectionResponseFromDiscriminatorValue) + + err = pageIterator.Iterate(func(pageItem interface{}) bool { + user := pageItem.(models.Userable) + + d.StreamListItem(ctx, &ADUserInfo{user}) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { - return nil, nil + return false } - } - return nil, err + return true + }) + + return nil, nil } //// HYDRATE FUNCTIONS func getAdUser(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var userId string - if h.Item != nil { - userId = *h.Item.(msgraph.User).ID - } else { - userId = d.KeyColumnQuals["id"].GetStringValue() + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) } + userId := d.KeyColumnQuals["id"].GetStringValue() if userId == "" { return nil, nil } - session, err := GetNewSession(ctx, d) + + // Check for query context and requests only for queried columns + givenColumns := d.QueryContext.Columns + selectColumns, expandColumns := buildUserRequestFields(ctx, givenColumns) + + input := &item.UserItemRequestBuilderGetQueryParameters{} + input.Select = selectColumns + input.Expand = expandColumns + + options := &item.UserItemRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + user, err := client.UsersById(userId).GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { - return nil, err + errObj := getErrorObject(err) + return nil, errObj } - client := msgraph.NewUsersClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - client.BaseClient.DisableRetries = true + return &ADUserInfo{user}, nil +} + +func buildUserRequestFields(ctx context.Context, queryColumns []string) ([]string, []string) { + var selectColumns, expandColumns []string + + for _, columnName := range queryColumns { + if columnName == "title" || columnName == "filter" || columnName == "tenant_id" { + continue + } - input := odata.Query{} - if helpers.StringSliceContains(d.QueryContext.Columns, "member_of") { - input.Expand = odata.Expand{ - Relationship: "memberOf", - Select: []string{"id", "displayName"}, + if columnName == "member_of" { + expandColumns = append(expandColumns, fmt.Sprintf("%s($select=id,displayName)", strcase.ToLowerCamel(columnName))) + continue } + + if columnName == "refresh_tokens_valid_from_date_time" { + selectColumns = append(selectColumns, "signInSessionsValidFromDateTime") + continue + } + + selectColumns = append(selectColumns, strcase.ToLowerCamel(columnName)) } - user, _, err := client.Get(ctx, userId, input) - if err != nil { - return nil, err + return selectColumns, expandColumns +} + +func getTenant(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + var tenantID string + var err error + + // Read tenantID from config, or environment variables + azureADConfig := GetConfig(d.Connection) + if azureADConfig.TenantID != nil { + tenantID = *azureADConfig.TenantID + } else { + tenantID = os.Getenv("AZURE_TENANT_ID") + } + + // If not set in config, get tenantID from CLI + if tenantID == "" { + tenantID, err = getTenantFromCLI() + if err != nil { + return nil, err + } } - return *user, nil + + return tenantID, nil } -func getTenantId(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - plugin.Logger(ctx).Debug("getTenantId") +//// TRANSFORM FUNCTIONS - session, err := GetNewSession(ctx, d) - if err != nil { - return nil, err +func adUserTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADUserInfo) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetUserPrincipalName() } - return session.TenantID, nil + return title, nil } func buildQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { diff --git a/azuread/table_azuread_user1.go b/azuread/table_azuread_user1.go deleted file mode 100644 index 26da272..0000000 --- a/azuread/table_azuread_user1.go +++ /dev/null @@ -1,254 +0,0 @@ -package azuread - -import ( - "context" - "errors" - "fmt" - "os" - "strings" - - "github.com/iancoleman/strcase" - msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" - "github.com/microsoftgraph/msgraph-sdk-go/models" - "github.com/microsoftgraph/msgraph-sdk-go/users" - "github.com/microsoftgraph/msgraph-sdk-go/users/item" - - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" -) - -//// TABLE DEFINITION - -func tableAzureAdUserTest() *plugin.Table { - return &plugin.Table{ - Name: "azuread_user_test", - Description: "Represents an Azure AD user account.", - Get: &plugin.GetConfig{ - Hydrate: getAdUserTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), - }, - KeyColumns: plugin.SingleColumn("id"), - }, - List: &plugin.ListConfig{ - Hydrate: listAdUsersTest, - KeyColumns: plugin.KeyColumnSlice{ - // Key fields - {Name: "user_principal_name", Require: plugin.Optional}, - {Name: "filter", Require: plugin.Optional}, - - // Other fields for filtering OData - {Name: "user_type", Require: plugin.Optional}, - {Name: "account_enabled", Require: plugin.Optional, Operators: []string{"<>", "="}}, - {Name: "display_name", Require: plugin.Optional}, - {Name: "surname", Require: plugin.Optional}, - }, - }, - - Columns: []*plugin.Column{ - {Name: "display_name", Type: proto.ColumnType_STRING, Description: "The name displayed in the address book for the user. This is usually the combination of the user's first name, middle initial and last name.", Transform: transform.FromMethod("GetDisplayName")}, - {Name: "id", Type: proto.ColumnType_STRING, Description: "The unique identifier for the user. Should be treated as an opaque identifier.", Transform: transform.FromMethod("GetId")}, - {Name: "user_principal_name", Type: proto.ColumnType_STRING, Description: "Principal email of the active directory user.", Transform: transform.FromMethod("GetUserPrincipalName")}, - {Name: "account_enabled", Type: proto.ColumnType_BOOL, Description: "True if the account is enabled; otherwise, false.", Transform: transform.FromMethod("GetAccountEnabled")}, - {Name: "user_type", Type: proto.ColumnType_STRING, Description: "A string value that can be used to classify user types in your directory.", Transform: transform.FromMethod("GetUserType")}, - {Name: "given_name", Type: proto.ColumnType_STRING, Description: "The given name (first name) of the user.", Transform: transform.FromMethod("GetGivenName")}, - {Name: "surname", Type: proto.ColumnType_STRING, Description: "Family name or last name of the active directory user.", Transform: transform.FromMethod("GetSurname")}, - - {Name: "filter", Type: proto.ColumnType_STRING, Transform: transform.FromQual("filter"), Description: "Odata query to search for resources."}, - - // Other fields - {Name: "on_premises_immutable_id", Type: proto.ColumnType_STRING, Description: "Used to associate an on-premises Active Directory user account with their Azure AD user object.", Transform: transform.FromMethod("GetOnPremisesImmutableId")}, - {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the user was created.", Transform: transform.FromMethod("GetCreatedDateTime")}, - {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the user, for example, jeff@contoso.onmicrosoft.com.", Transform: transform.FromMethod("GetMail")}, - {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user.", Transform: transform.FromMethod("GetMailNickname")}, - {Name: "password_policies", Type: proto.ColumnType_STRING, Description: "Specifies password policies for the user. This value is an enumeration with one possible value being DisableStrongPassword, which allows weaker passwords than the default policy to be specified. DisablePasswordExpiration can also be specified. The two may be specified together; for example: DisablePasswordExpiration, DisableStrongPassword.", Transform: transform.FromMethod("GetPasswordPolicies")}, - {Name: "refresh_tokens_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph).", Transform: transform.FromMethod("GetSignInSessionsValidFromDateTime")}, - {Name: "sign_in_sessions_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph).", Transform: transform.FromMethod("GetSignInSessionsValidFromDateTime")}, - {Name: "usage_location", Type: proto.ColumnType_STRING, Description: "A two letter country code (ISO standard 3166), required for users that will be assigned licenses due to legal requirement to check for availability of services in countries.", Transform: transform.FromMethod("GetUsageLocation")}, - - // Json fields - {Name: "member_of", Type: proto.ColumnType_JSON, Description: "A list the groups and directory roles that the user is a direct member of.", Transform: transform.FromMethod("UserMemberOf")}, - {Name: "im_addresses", Type: proto.ColumnType_JSON, Description: "The instant message voice over IP (VOIP) session initiation protocol (SIP) addresses for the user.", Transform: transform.FromMethod("GetImAddresses")}, - {Name: "other_mails", Type: proto.ColumnType_JSON, Description: "A list of additional email addresses for the user.", Transform: transform.FromMethod("GetOtherMails")}, - {Name: "password_profile", Type: proto.ColumnType_JSON, Description: "Specifies the password profile for the user. The profile contains the user’s password. This property is required when a user is created.", Transform: transform.FromMethod("UserPasswordProfile")}, - - // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adUserTitle)}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, - }, - } -} - -//// LIST FUNCTION - -func listAdUsersTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - // List operations - input := &users.UsersRequestBuilderGetQueryParameters{} - - // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow - limit := d.QueryContext.Limit - if limit != nil { - if *limit > 0 && *limit <= 999 { - l := int32(*limit) - input.Top = &l - } - } - - equalQuals := d.KeyColumnQuals - quals := d.Quals - - // Check for query context and requests only for queried columns - givenColumns := d.QueryContext.Columns - selectColumns, expandColumns := buildUserRequestFields(ctx, givenColumns) - - input.Select = selectColumns - input.Expand = expandColumns - - var queryFilter string - filter := buildQueryFilter(equalQuals) - filter = append(filter, buildBoolNEFilter(quals)...) - - if equalQuals["filter"] != nil { - queryFilter = equalQuals["filter"].GetStringValue() - } - - if queryFilter != "" { - input.Filter = &queryFilter - } else if len(filter) > 0 { - joinStr := strings.Join(filter, " and ") - input.Filter = &joinStr - } - - options := &users.UsersRequestBuilderGetRequestConfiguration{ - QueryParameters: input, - } - - result, err := client.Users().GetWithRequestConfigurationAndResponseHandler(options, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateUserCollectionResponseFromDiscriminatorValue) - - err = pageIterator.Iterate(func(pageItem interface{}) bool { - user := pageItem.(models.Userable) - - d.StreamListItem(ctx, &ADUserInfo{user}) - - // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true - }) - - return nil, nil -} - -//// HYDRATE FUNCTIONS - -func getAdUserTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - - // Create client - client, _, err := GetGraphClient(ctx, d) - if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) - } - - userId := d.KeyColumnQuals["id"].GetStringValue() - if userId == "" { - return nil, nil - } - - // Check for query context and requests only for queried columns - givenColumns := d.QueryContext.Columns - selectColumns, expandColumns := buildUserRequestFields(ctx, givenColumns) - - input := &item.UserItemRequestBuilderGetQueryParameters{} - input.Select = selectColumns - input.Expand = expandColumns - - options := &item.UserItemRequestBuilderGetRequestConfiguration{ - QueryParameters: input, - } - - user, err := client.UsersById(userId).GetWithRequestConfigurationAndResponseHandler(options, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - return &ADUserInfo{user}, nil -} - -func buildUserRequestFields(ctx context.Context, queryColumns []string) ([]string, []string) { - var selectColumns, expandColumns []string - - for _, columnName := range queryColumns { - if columnName == "title" || columnName == "filter" || columnName == "tenant_id" { - continue - } - - if columnName == "member_of" { - expandColumns = append(expandColumns, fmt.Sprintf("%s($select=id,displayName)", strcase.ToLowerCamel(columnName))) - continue - } - - if columnName == "refresh_tokens_valid_from_date_time" { - selectColumns = append(selectColumns, "signInSessionsValidFromDateTime") - continue - } - - selectColumns = append(selectColumns, strcase.ToLowerCamel(columnName)) - } - - return selectColumns, expandColumns -} - -func getTenant(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var tenantID string - var err error - - // Read tenantID from config, or environment variables - azureADConfig := GetConfig(d.Connection) - if azureADConfig.TenantID != nil { - tenantID = *azureADConfig.TenantID - } else { - tenantID = os.Getenv("AZURE_TENANT_ID") - } - - // If not set in config, get tenantID from CLI - if tenantID == "" { - tenantID, err = getTenantFromCLI() - if err != nil { - return nil, err - } - } - - return tenantID, nil -} - -//// TRANSFORM FUNCTIONS - -func adUserTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { - data := d.HydrateItem.(*ADUserInfo) - if data == nil { - return nil, nil - } - - title := data.GetDisplayName() - if title == nil { - title = data.GetUserPrincipalName() - } - - return title, nil -} diff --git a/azuread/utils.go b/azuread/utils.go index 2d6221e..2287f93 100644 --- a/azuread/utils.go +++ b/azuread/utils.go @@ -1,6 +1,7 @@ package azuread import ( + "context" "fmt" "strconv" "strings" @@ -165,3 +166,14 @@ func buildCommaonQueryFilter(qualsColumns []QualsColumn, quals plugin.KeyColumnQ } return filter } + +func getTenantId(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + plugin.Logger(ctx).Debug("getTenantId") + + session, err := GetNewSession(ctx, d) + if err != nil { + return nil, err + } + + return session.TenantID, nil +} From 6c88d1db03fe68651bbb822e8ac2dc9012a3bdee Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Wed, 27 Jul 2022 21:19:24 +0530 Subject: [PATCH 09/21] Update table example docs --- docs/tables/azuread_application.md | 14 +++++++++----- docs/tables/azuread_conditional_access_policy.md | 4 ++-- docs/tables/azuread_directory_role.md | 2 +- docs/tables/azuread_service_principal.md | 4 +++- docs/tables/azuread_sign_in_report.md | 4 ++-- go.mod | 3 +-- go.sum | 2 -- 7 files changed, 18 insertions(+), 15 deletions(-) diff --git a/docs/tables/azuread_application.md b/docs/tables/azuread_application.md index 0f5e72e..993db4c 100644 --- a/docs/tables/azuread_application.md +++ b/docs/tables/azuread_application.md @@ -15,14 +15,18 @@ from azuread_application; ``` -### List applications with service authorization disabled +### List owners of an application ```sql select - display_name, - id + app.display_name as application_name, + app.id as application_id, + o as owner_id, + u.display_name as owner_display_name from - azuread_application + azuread_application as app, + jsonb_array_elements_text(owner_ids) as o + left join azuread_user as u on u.id = o where - not is_authorization_service_enabled; + app.id = 'a6656898-3879-4d35-8a58-b34237095a70'; ``` diff --git a/docs/tables/azuread_conditional_access_policy.md b/docs/tables/azuread_conditional_access_policy.md index 4d41a18..ffc6d12 100644 --- a/docs/tables/azuread_conditional_access_policy.md +++ b/docs/tables/azuread_conditional_access_policy.md @@ -17,7 +17,7 @@ from azuread_conditional_access_policy; ``` -### List coditional access policies with mfa enabled +### List conditional access policies with mfa enabled ```sql select @@ -28,4 +28,4 @@ from azuread_conditional_access_policy where built_in_controls ?& array['mfa']; -``` \ No newline at end of file +``` diff --git a/docs/tables/azuread_directory_role.md b/docs/tables/azuread_directory_role.md index bd35ddf..6fce608 100644 --- a/docs/tables/azuread_directory_role.md +++ b/docs/tables/azuread_directory_role.md @@ -28,4 +28,4 @@ from azuread_user as u where u.id = m_id; -``` \ No newline at end of file +``` diff --git a/docs/tables/azuread_service_principal.md b/docs/tables/azuread_service_principal.md index 5ce77e0..33c42e7 100644 --- a/docs/tables/azuread_service_principal.md +++ b/docs/tables/azuread_service_principal.md @@ -29,6 +29,7 @@ where ### List service principals related to applications +```sql select id, app_display_name, @@ -37,4 +38,5 @@ from azuread_service_principal where service_principal_type = 'Application' - and tenant_id = app_owner_organization_id; \ No newline at end of file + and tenant_id = app_owner_organization_id; +``` diff --git a/docs/tables/azuread_sign_in_report.md b/docs/tables/azuread_sign_in_report.md index b73e4d2..745dfc0 100644 --- a/docs/tables/azuread_sign_in_report.md +++ b/docs/tables/azuread_sign_in_report.md @@ -13,7 +13,7 @@ select user_display_name, user_principal_name, ip_address, - location -> 'city' as city + location ->> 'city' as city from azuread_sign_in_report; ``` @@ -30,4 +30,4 @@ from azuread_sign_in_report where user_principal_name = 'abc@myacc.onmicrosoft.com'; -``` \ No newline at end of file +``` diff --git a/go.mod b/go.mod index 73b2403..e31634a 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,11 @@ go 1.18 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 - github.com/ettle/strcase v0.1.1 github.com/iancoleman/strcase v0.2.0 github.com/manicminer/hamilton v0.24.0 github.com/microsoft/kiota-authentication-azure-go v0.3.1 github.com/microsoftgraph/msgraph-sdk-go v0.30.0 github.com/microsoftgraph/msgraph-sdk-go-core v0.26.2 - github.com/turbot/go-kit v0.4.0 github.com/turbot/steampipe-plugin-sdk/v3 v3.3.2 ) @@ -67,6 +65,7 @@ require ( github.com/stevenle/topsort v0.0.0-20130922064739-8130c1d7596b // indirect github.com/stretchr/testify v1.7.2 // indirect github.com/tkrajina/go-reflector v0.5.4 // indirect + github.com/turbot/go-kit v0.4.0 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/zclconf/go-cty v1.10.0 // indirect go.opentelemetry.io/otel v1.7.0 // indirect diff --git a/go.sum b/go.sum index 6314997..8dd0c7e 100644 --- a/go.sum +++ b/go.sum @@ -97,8 +97,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= -github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= From 312ef995116fba3f5eb3bf1595a62b80814c9f59 Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Thu, 28 Jul 2022 17:31:46 +0530 Subject: [PATCH 10/21] Fix lint error --- azuread/errors.go | 13 +- azuread/plugin.go | 8 -- azuread/service.go | 11 +- azuread/table_azuread_application.go | 15 ++- ...table_azuread_conditional_access_policy.go | 10 +- azuread/table_azuread_directory_role.go | 10 +- azuread/table_azuread_domain.go | 8 +- azuread/table_azuread_group.go | 18 ++- azuread/table_azuread_identity_provider1.go | 8 +- azuread/table_azuread_service_principal.go | 13 +- azuread/table_azuread_sign_in_report.go | 8 +- azuread/table_azuread_user.go | 8 +- azuread/utils.go | 116 ------------------ config/azuread.spc | 2 +- 14 files changed, 73 insertions(+), 175 deletions(-) diff --git a/azuread/errors.go b/azuread/errors.go index 00489e4..6b49dbd 100644 --- a/azuread/errors.go +++ b/azuread/errors.go @@ -15,7 +15,10 @@ type RequestError struct { } func (m *RequestError) Error() string { - errStr, _ := json.Marshal(m) + errStr, err := json.Marshal(m) + if err != nil { + return "" + } return string(errStr) } @@ -46,11 +49,3 @@ func isIgnorableErrorPredicate(ignoreErrorCodes []string) plugin.ErrorPredicateW return false } } - -// Remove after adding IgnoreConfig -func isResourceNotFound(errObj *RequestError) bool { - if errObj != nil && errObj.Code == "Request_ResourceNotFound" { - return true - } - return false -} diff --git a/azuread/plugin.go b/azuread/plugin.go index 781bfea..d86015f 100644 --- a/azuread/plugin.go +++ b/azuread/plugin.go @@ -32,14 +32,6 @@ func Plugin(ctx context.Context) *plugin.Plugin { "azuread_sign_in_report": tableAzureAdSignInReport(), "azuread_user": tableAzureAdUser(), "azuread_identity_provider_test": tableAzureAdIdentityProviderTest(), - // "azuread_application_test": tableAzureAdApplicationTest(), - // "azuread_conditional_access_policy_test": tableAzureAdConditionalAccessPolicyTest(), - // "azuread_directory_role_test": tableAzureAdDirectoryRoleTest(), - // "azuread_domain_test": tableAzureAdDomainTest(), - // "azuread_group_test": tableAzureAdGroupTest(), - // "azuread_service_principal_test": tableAzureAdServicePrincipalTest(), - // "azuread_sign_in_report_test": tableAzureAdSignInReportTest(), - // "azuread_user_test": tableAzureAdUserTest(), }, } diff --git a/azuread/service.go b/azuread/service.go index e086359..79138f0 100644 --- a/azuread/service.go +++ b/azuread/service.go @@ -6,9 +6,7 @@ import ( "crypto" "crypto/x509" "encoding/json" - "errors" "fmt" - "log" "os" "os/exec" "runtime" @@ -61,7 +59,8 @@ func GetNewSession(ctx context.Context, d *plugin.QueryData) (sess *Session, err authorizer, err := authConfig.NewAuthorizer(ctx, auth.MsGraph) if err != nil { - log.Fatal(err) + logger.Debug("GetNewSession__", "authorizer error", err) + return nil, err } if authMethod == "CLI" { @@ -300,8 +299,6 @@ func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.Gra cloudConfiguration = cloud.AzureChina case "AZUREUSGOVERNMENTCLOUD": cloudConfiguration = cloud.AzureGovernment - // case "AZUREGERMANCLOUD": - // cloudConfiguration = environments.Germany default: cloudConfiguration = cloud.AzurePublic } @@ -369,12 +366,12 @@ func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.Gra auth, err := a.NewAzureIdentityAuthenticationProvider(cred) if err != nil { - return nil, nil, errors.New(fmt.Sprintf("error creating authentication provider: %v", err)) + return nil, nil, fmt.Errorf("error creating authentication provider: %v", err) } adapter, err := msgraphsdkgo.NewGraphRequestAdapter(auth) if err != nil { - return nil, nil, errors.New(fmt.Sprintf("error creating graph adapter: %v", err)) + return nil, nil, fmt.Errorf("error creating graph adapter: %v", err) } client := msgraphsdkgo.NewGraphServiceClient(adapter) diff --git a/azuread/table_azuread_application.go b/azuread/table_azuread_application.go index 8d136bf..0dff376 100644 --- a/azuread/table_azuread_application.go +++ b/azuread/table_azuread_application.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "errors" "fmt" "strings" @@ -82,7 +81,7 @@ func listAdApplications(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydr // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } // List operations @@ -137,8 +136,11 @@ func listAdApplications(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydr return true }) + if err != nil { + return nil, err + } - return nil, err + return nil, nil } //// HYDRATE FUNCTIONS @@ -153,7 +155,7 @@ func getAdApplication(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } application, err := client.ApplicationsById(applicationId).Get() @@ -169,7 +171,7 @@ func getAdApplicationOwners(ctx context.Context, d *plugin.QueryData, h *plugin. // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } application := h.Item.(*ADApplicationInfo) @@ -203,6 +205,9 @@ func getAdApplicationOwners(ctx context.Context, d *plugin.QueryData, h *plugin. return true }) + if err != nil { + return nil, err + } return ownerIds, nil } diff --git a/azuread/table_azuread_conditional_access_policy.go b/azuread/table_azuread_conditional_access_policy.go index 5f8920a..4c86c88 100644 --- a/azuread/table_azuread_conditional_access_policy.go +++ b/azuread/table_azuread_conditional_access_policy.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "errors" "fmt" "strings" @@ -78,7 +77,7 @@ func listAdConditionalAccessPolicies(ctx context.Context, d *plugin.QueryData, _ // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } // List operations @@ -126,8 +125,11 @@ func listAdConditionalAccessPolicies(ctx context.Context, d *plugin.QueryData, _ return true }) + if err != nil { + return nil, err + } - return nil, err + return nil, nil } //// HYDRATE FUNCTIONS @@ -142,7 +144,7 @@ func getAdConditionalAccessPolicy(ctx context.Context, d *plugin.QueryData, h *p // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } policy, err := client.Identity().ConditionalAccess().PoliciesById(conditionalAccessPolicyId).Get() diff --git a/azuread/table_azuread_directory_role.go b/azuread/table_azuread_directory_role.go index 9f847fd..38659f1 100644 --- a/azuread/table_azuread_directory_role.go +++ b/azuread/table_azuread_directory_role.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "errors" "fmt" msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" @@ -59,7 +58,7 @@ func listAdDirectoryRoles(ctx context.Context, d *plugin.QueryData, _ *plugin.Hy // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } result, err := client.DirectoryRoles().Get() @@ -91,7 +90,7 @@ func getAdDirectoryRole(ctx context.Context, d *plugin.QueryData, h *plugin.Hydr // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } directoryRole, err := client.DirectoryRolesById(directoryRoleId).Get() @@ -107,7 +106,7 @@ func getDirectoryRoleMembers(ctx context.Context, d *plugin.QueryData, h *plugin // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } directoryRole := h.Item.(*ADDirectoryRoleInfo) @@ -141,6 +140,9 @@ func getDirectoryRoleMembers(ctx context.Context, d *plugin.QueryData, h *plugin return true }) + if err != nil { + return nil, err + } return memberIds, nil } diff --git a/azuread/table_azuread_domain.go b/azuread/table_azuread_domain.go index f4d5f0e..96439c7 100644 --- a/azuread/table_azuread_domain.go +++ b/azuread/table_azuread_domain.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "errors" "fmt" "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" @@ -63,7 +62,7 @@ func listAdDomains(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDa // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } // List operations @@ -103,6 +102,9 @@ func listAdDomains(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDa return true }) + if err != nil { + return nil, err + } return nil, nil } @@ -118,7 +120,7 @@ func getAdDomain(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } domain, err := client.DomainsById(domainId).Get() diff --git a/azuread/table_azuread_group.go b/azuread/table_azuread_group.go index c0f9c5e..bdb49b6 100644 --- a/azuread/table_azuread_group.go +++ b/azuread/table_azuread_group.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "errors" "fmt" "strings" @@ -112,7 +111,7 @@ func listAdGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDat // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } // List operations @@ -169,6 +168,9 @@ func listAdGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDat return true }) + if err != nil { + return nil, err + } return nil, nil } @@ -185,7 +187,7 @@ func getAdGroup(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } input := &item.GroupItemRequestBuilderGetQueryParameters{} @@ -207,7 +209,7 @@ func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.Hydra // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } group := h.Item.(*ADGroupInfo) @@ -241,6 +243,9 @@ func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.Hydra return true }) + if err != nil { + return nil, err + } return memberIds, nil } @@ -249,7 +254,7 @@ func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } group := h.Item.(*ADGroupInfo) @@ -283,6 +288,9 @@ func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat return true }) + if err != nil { + return nil, err + } return ownerIds, nil } diff --git a/azuread/table_azuread_identity_provider1.go b/azuread/table_azuread_identity_provider1.go index a4cf8e6..8135919 100644 --- a/azuread/table_azuread_identity_provider1.go +++ b/azuread/table_azuread_identity_provider1.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "errors" "fmt" "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" @@ -54,7 +53,7 @@ func listAdIdentityProvidersTest(ctx context.Context, d *plugin.QueryData, _ *pl // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } // List operations @@ -90,6 +89,9 @@ func listAdIdentityProvidersTest(ctx context.Context, d *plugin.QueryData, _ *pl return true }) + if err != nil { + return nil, err + } return nil, nil } @@ -106,7 +108,7 @@ func getAdIdentityProviderTest(ctx context.Context, d *plugin.QueryData, h *plug // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } identityProvider, err := client.Identity().IdentityProvidersById(identityProviderId).Get() diff --git a/azuread/table_azuread_service_principal.go b/azuread/table_azuread_service_principal.go index 41ffcb6..fce791d 100644 --- a/azuread/table_azuread_service_principal.go +++ b/azuread/table_azuread_service_principal.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "errors" "fmt" "strings" @@ -88,7 +87,7 @@ func listAdServicePrincipals(ctx context.Context, d *plugin.QueryData, _ *plugin // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } // List operations @@ -145,6 +144,9 @@ func listAdServicePrincipals(ctx context.Context, d *plugin.QueryData, _ *plugin return true }) + if err != nil { + return nil, err + } return nil, nil } @@ -161,7 +163,7 @@ func getAdServicePrincipal(ctx context.Context, d *plugin.QueryData, h *plugin.H // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } servicePrincipal, err := client.ServicePrincipalsById(servicePrincipalID).Get() @@ -177,7 +179,7 @@ func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plug // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } servicePrincipal := h.Item.(*ADServicePrincipalInfo) @@ -215,6 +217,9 @@ func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plug return true }) + if err != nil { + return nil, err + } return ownerIds, nil } diff --git a/azuread/table_azuread_sign_in_report.go b/azuread/table_azuread_sign_in_report.go index da9960b..91f2046 100644 --- a/azuread/table_azuread_sign_in_report.go +++ b/azuread/table_azuread_sign_in_report.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "errors" "fmt" msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" @@ -71,7 +70,7 @@ func listAdSignInReports(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } // List operations @@ -111,6 +110,9 @@ func listAdSignInReports(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd return true }) + if err != nil { + return nil, err + } return nil, nil } @@ -126,7 +128,7 @@ func getAdSignInReport(ctx context.Context, d *plugin.QueryData, h *plugin.Hydra // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } signIn, err := client.AuditLogs().SignInsById(signInID).Get() diff --git a/azuread/table_azuread_user.go b/azuread/table_azuread_user.go index 79a6f0d..ac9ef72 100644 --- a/azuread/table_azuread_user.go +++ b/azuread/table_azuread_user.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "errors" "fmt" "os" "strings" @@ -86,7 +85,7 @@ func listAdUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } // List operations @@ -150,6 +149,9 @@ func listAdUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData return true }) + if err != nil { + return nil, err + } return nil, nil } @@ -161,7 +163,7 @@ func getAdUser(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, errors.New(fmt.Sprintf("error creating client: %v", err)) + return nil, fmt.Errorf("error creating client: %v", err) } userId := d.KeyColumnQuals["id"].GetStringValue() diff --git a/azuread/utils.go b/azuread/utils.go index 2287f93..66713d3 100644 --- a/azuread/utils.go +++ b/azuread/utils.go @@ -2,11 +2,8 @@ package azuread import ( "context" - "fmt" - "strconv" "strings" - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" ) @@ -48,125 +45,12 @@ func TagsToMap(tags []string) (*map[string]bool, error) { return &turbotTagsMap, nil } -func getListValues(listValue *proto.QualValueList) []string { - values := make([]string, 0) - for _, value := range listValue.Values { - if strings.TrimRight(strings.TrimLeft(value.GetStringValue(), " "), " ") != "" { - values = append(values, value.GetStringValue()) - } - } - return values -} - -func getQualsValueByColumn(quals plugin.KeyColumnQualMap, columnName string, dataType string) interface{} { - var value interface{} - for _, q := range quals[columnName].Quals { - if dataType == "string" { - if q.Value.GetStringValue() != "" { - value = q.Value.GetStringValue() - } else { - value = getListValues(q.Value.GetListValue()) - } - } - if dataType == "bool" { - switch q.Operator { - case "<>": - value = !q.Value.GetBoolValue() - case "=": - value = q.Value.GetBoolValue() - } - } - if dataType == "int64" { - value = q.Value.GetInt64Value() - if q.Value.GetInt64Value() == 0 { - valueSlice := make([]*string, 0) - for _, value := range q.Value.GetListValue().Values { - val := strconv.FormatInt(value.GetInt64Value(), 10) - valueSlice = append(valueSlice, &val) - } - value = valueSlice - } - } - if dataType == "double" { - value = q.Value.GetDoubleValue() - if q.Value.GetDoubleValue() == 0 { - valueSlice := make([]*string, 0) - for _, value := range q.Value.GetListValue().Values { - val := strconv.FormatFloat(value.GetDoubleValue(), 'f', 4, 64) - valueSlice = append(valueSlice, &val) - } - value = valueSlice - } - - } - if dataType == "ipaddr" { - value = q.Value.GetInetValue().Addr - if q.Value.GetInetValue().Addr == "" { - valueSlice := make([]*string, 0) - for _, value := range q.Value.GetListValue().Values { - val := value.GetInetValue().Addr - valueSlice = append(valueSlice, &val) - } - value = valueSlice - } - } - if dataType == "cidr" { - value = q.Value.GetInetValue().Cidr - if q.Value.GetInetValue().Addr == "" { - valueSlice := make([]*string, 0) - for _, value := range q.Value.GetListValue().Values { - val := value.GetInetValue().Cidr - valueSlice = append(valueSlice, &val) - } - value = valueSlice - } - } - if dataType == "time" { - value = getListValues(q.Value.GetListValue()) - if len(getListValues(q.Value.GetListValue())) == 0 { - value = q.Value.GetTimestampValue().AsTime() - } - } - } - return value -} - type QualsColumn struct { ColumnName string ColumnType string FilterName string } -//// Build common query filter - -func buildCommaonQueryFilter(qualsColumns []QualsColumn, quals plugin.KeyColumnQualMap) []string { - var filter []string - for _, qualColumn := range qualsColumns { - if quals[qualColumn.ColumnName] != nil { - value := getQualsValueByColumn(quals, qualColumn.ColumnName, qualColumn.ColumnType) - switch qualColumn.ColumnType { - case "string": - val, ok := value.([]string) - if ok { - var valueSlice []string - for _, v := range val { - valueSlice = append(valueSlice, fmt.Sprintf("%s eq '%s'", qualColumn.FilterName, v)) - } - filter = append(filter, strings.Join(valueSlice, " or ")) - } else { - val := value.(string) - filter = append(filter, fmt.Sprintf("%s eq '%s'", qualColumn.FilterName, val)) - } - case "bool": - val := value.(bool) - filter = append(filter, fmt.Sprintf("%s eq %t", qualColumn.FilterName, val)) - } - } - - } - return filter -} - func getTenantId(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { plugin.Logger(ctx).Debug("getTenantId") diff --git a/config/azuread.spc b/config/azuread.spc index 8f91209..b1a7b6b 100644 --- a/config/azuread.spc +++ b/config/azuread.spc @@ -1,7 +1,7 @@ connection "azuread" { plugin = "azuread" - # Defaults to "AZUREPUBLICCLOUD". Valid environments are "AZUREPUBLICCLOUD", "AZURECHINACLOUD", "AZUREGERMANCLOUD" and "AZUREUSGOVERNMENTCLOUD" + # Defaults to "AZUREPUBLICCLOUD". Valid environments are "AZUREPUBLICCLOUD", "AZURECHINACLOUD" and "AZUREUSGOVERNMENTCLOUD" # environment = "AZUREPUBLICCLOUD" # You can connect to Azure using one of options below: From b806330541e8f20a37bbb44b61bd6b92d56bd5a8 Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Thu, 28 Jul 2022 19:50:01 +0530 Subject: [PATCH 11/21] reomve test tables --- azuread/plugin.go | 1 - azuread/table_azuread_identity_provider1.go | 137 -------------------- 2 files changed, 138 deletions(-) delete mode 100644 azuread/table_azuread_identity_provider1.go diff --git a/azuread/plugin.go b/azuread/plugin.go index d86015f..1f0d573 100644 --- a/azuread/plugin.go +++ b/azuread/plugin.go @@ -31,7 +31,6 @@ func Plugin(ctx context.Context) *plugin.Plugin { "azuread_service_principal": tableAzureAdServicePrincipal(), "azuread_sign_in_report": tableAzureAdSignInReport(), "azuread_user": tableAzureAdUser(), - "azuread_identity_provider_test": tableAzureAdIdentityProviderTest(), }, } diff --git a/azuread/table_azuread_identity_provider1.go b/azuread/table_azuread_identity_provider1.go deleted file mode 100644 index 8135919..0000000 --- a/azuread/table_azuread_identity_provider1.go +++ /dev/null @@ -1,137 +0,0 @@ -package azuread - -import ( - "context" - "fmt" - - "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" - "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" - - msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" - "github.com/microsoftgraph/msgraph-sdk-go/identity/identityproviders" - "github.com/microsoftgraph/msgraph-sdk-go/models" - - "github.com/turbot/steampipe-plugin-sdk/v3/plugin" -) - -//// TABLE DEFINITION - -func tableAzureAdIdentityProviderTest() *plugin.Table { - return &plugin.Table{ - Name: "azuread_identity_provider_test", - Description: "Represents an Azure Active Directory (Azure AD) identity provider", - Get: &plugin.GetConfig{ - Hydrate: getAdIdentityProviderTest, - IgnoreConfig: &plugin.IgnoreConfig{ - ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound", "Invalid object identifier"}), - }, - KeyColumns: plugin.SingleColumn("id"), - }, - List: &plugin.ListConfig{ - Hydrate: listAdIdentityProvidersTest, - }, - - Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "The ID of the identity provider.", Transform: transform.FromMethod("GetId")}, - {Name: "name", Type: proto.ColumnType_STRING, Description: "The display name of the identity provider.", Transform: transform.FromMethod("GetDisplayName")}, - - // Other fields - {Name: "type", Type: proto.ColumnType_STRING, Description: "The identity provider type is a required field. For B2B scenario: Google, Facebook. For B2C scenario: Microsoft, Google, Amazon, LinkedIn, Facebook, GitHub, Twitter, Weibo, QQ, WeChat, OpenIDConnect.", Transform: transform.FromMethod("GetIdentityProviderType")}, - // {Name: "client_id", Type: proto.ColumnType_STRING, Description: "The client ID for the application. This is the client ID obtained when registering the application with the identity provider.", Transform: transform.FromMethod("GetClientId")}, - // {Name: "client_secret", Type: proto.ColumnType_STRING, Description: "The client secret for the application. This is the client secret obtained when registering the application with the identity provider. This is write-only. A read operation will return ****.", Transform: transform.FromMethod("GetClientSecret")}, - - // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adIdentityProviderTitle)}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, - }, - } -} - -//// LIST FUNCTION - -func listAdIdentityProvidersTest(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - // Create client - client, adapter, err := GetGraphClient(ctx, d) - if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) - } - - // List operations - input := &identityproviders.IdentityProvidersRequestBuilderGetQueryParameters{} - - limit := d.QueryContext.Limit - if limit != nil { - l := int32(*limit) - input.Top = &l - } - - options := &identityproviders.IdentityProvidersRequestBuilderGetRequestConfiguration{ - QueryParameters: input, - } - - result, err := client.Identity().IdentityProviders().GetWithRequestConfigurationAndResponseHandler(options, nil) - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateBuiltInIdentityProviderFromDiscriminatorValue) - - err = pageIterator.Iterate(func(pageItem interface{}) bool { - identityProvider := pageItem.(*models.BuiltInIdentityProvider) - - d.StreamListItem(ctx, identityProvider) - - // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true - }) - if err != nil { - return nil, err - } - - return nil, nil -} - -//// HYDRATE FUNCTIONS -// https://docs.microsoft.com/en-us/graph/api/identityproviderbase-get?view=graph-rest-1.0&tabs=go - -func getAdIdentityProviderTest(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - identityProviderId := d.KeyColumnQuals["id"].GetStringValue() - if identityProviderId == "" { - return nil, nil - } - - // Create client - client, _, err := GetGraphClient(ctx, d) - if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) - } - - identityProvider, err := client.Identity().IdentityProvidersById(identityProviderId).Get() - if err != nil { - errObj := getErrorObject(err) - return nil, errObj - } - - return identityProvider, nil -} - -//// TRANSFORM FUNCTIONS - -func adIdentityProviderTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { - data := d.HydrateItem.(models.IdentityProviderBaseable) - if data == nil { - return nil, nil - } - - title := data.GetDisplayName() - if title == nil { - title = data.GetId() - } - - return title, nil -} From bd9e7b4a7d7708996647c8baffa25d969d7f17ab Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Thu, 4 Aug 2022 20:58:56 +0530 Subject: [PATCH 12/21] Add missing columns for user, group and application table --- azuread/helpers.go | 4 ++ azuread/table_azuread_application.go | 9 +++-- azuread/table_azuread_group.go | 57 ++++++++++++++++++++++++++-- azuread/table_azuread_user.go | 14 +++---- 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/azuread/helpers.go b/azuread/helpers.go index de2e4c4..023b306 100644 --- a/azuread/helpers.go +++ b/azuread/helpers.go @@ -4,6 +4,7 @@ import "github.com/microsoftgraph/msgraph-sdk-go/models" type ADApplicationInfo struct { models.Applicationable + IsAuthorizationServiceEnabled interface{} } type ADConditionalAccessPolicyInfo struct { @@ -12,6 +13,8 @@ type ADConditionalAccessPolicyInfo struct { type ADGroupInfo struct { models.Groupable + ResourceBehaviorOptions []string + ResourceProvisioningOptions []string } type ADServicePrincipalInfo struct { @@ -24,6 +27,7 @@ type ADSignInReportInfo struct { type ADUserInfo struct { models.Userable + RefreshTokensValidFromDateTime interface{} } func (application *ADApplicationInfo) ApplicationAPI() map[string]interface{} { diff --git a/azuread/table_azuread_application.go b/azuread/table_azuread_application.go index 0dff376..d45b2d2 100644 --- a/azuread/table_azuread_application.go +++ b/azuread/table_azuread_application.go @@ -50,7 +50,7 @@ func tableAzureAdApplication() *plugin.Table { // Other fields {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The date and time the application was registered. The DateTimeOffset type represents date and time information using ISO 8601 format and is always in UTC time.", Transform: transform.FromMethod("GetCreatedDateTime")}, {Name: "description", Type: proto.ColumnType_STRING, Description: "Free text field to provide a description of the application object to end users.", Transform: transform.FromMethod("GetDescription")}, - // {Name: "is_authorization_service_enabled", Type: proto.ColumnType_BOOL, Description: "Is authorization service enabled."}, + {Name: "is_authorization_service_enabled", Type: proto.ColumnType_BOOL, Description: "Is authorization service enabled.", Default: false}, {Name: "oauth2_require_post_response", Type: proto.ColumnType_BOOL, Description: "Specifies whether, as part of OAuth 2.0 token requests, Azure AD allows POST requests, as opposed to GET requests. The default is false, which specifies that only GET requests are allowed.", Transform: transform.FromMethod("GetOauth2RequirePostResponse"), Default: false}, {Name: "publisher_domain", Type: proto.ColumnType_STRING, Description: "The verified publisher domain for the application.", Transform: transform.FromMethod("GetPublisherDomain")}, {Name: "sign_in_audience", Type: proto.ColumnType_STRING, Description: "Specifies the Microsoft accounts that are supported for the current application.", Transform: transform.FromMethod("GetSignInAudience")}, @@ -127,7 +127,9 @@ func listAdApplications(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydr err = pageIterator.Iterate(func(pageItem interface{}) bool { application := pageItem.(models.Applicationable) - d.StreamListItem(ctx, &ADApplicationInfo{application}) + isAuthorizationServiceEnabled := application.GetAdditionalData()["isAuthorizationServiceEnabled"] + + d.StreamListItem(ctx, &ADApplicationInfo{application, isAuthorizationServiceEnabled}) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { @@ -163,8 +165,9 @@ func getAdApplication(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat errObj := getErrorObject(err) return nil, errObj } + isAuthorizationServiceEnabled := application.GetAdditionalData()["isAuthorizationServiceEnabled"] - return &ADApplicationInfo{application}, nil + return &ADApplicationInfo{application, isAuthorizationServiceEnabled}, nil } func getAdApplicationOwners(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { diff --git a/azuread/table_azuread_group.go b/azuread/table_azuread_group.go index bdb49b6..ff85fbf 100644 --- a/azuread/table_azuread_group.go +++ b/azuread/table_azuread_group.go @@ -6,6 +6,8 @@ import ( "strings" "github.com/iancoleman/strcase" + + jsonserialization "github.com/microsoft/kiota-serialization-json-go" msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" "github.com/microsoftgraph/msgraph-sdk-go/groups" "github.com/microsoftgraph/msgraph-sdk-go/groups/item" @@ -94,8 +96,8 @@ func tableAzureAdGroup() *plugin.Table { {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupMembers, Transform: transform.FromValue(), Description: "Id of Users and groups that are members of this group."}, {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupOwners, Transform: transform.FromValue(), Description: "Id od the owners of the group. The owners are a set of non-admin users who are allowed to modify this object."}, {Name: "proxy_addresses", Type: proto.ColumnType_JSON, Description: "Email addresses for the group that direct to the same group mailbox. For example: [\"SMTP: bob@contoso.com\", \"smtp: bob@sales.contoso.com\"]. The any operator is required to filter expressions on multi-valued properties.", Transform: transform.FromMethod("GetProxyAddresses")}, - // {Name: "resource_behavior_options", Type: proto.ColumnType_JSON, Description: "Specifies the group behaviors that can be set for a Microsoft 365 group during creation. Possible values are AllowOnlyMembersToPost, HideGroupInOutlook, SubscribeNewGroupMembers, WelcomeEmailDisabled."}, - // {Name: "resource_provisioning_options", Type: proto.ColumnType_JSON, Description: "Specifies the group resources that are provisioned as part of Microsoft 365 group creation, that are not normally part of default group creation. Possible value is Team."}, + {Name: "resource_behavior_options", Type: proto.ColumnType_JSON, Description: "Specifies the group behaviors that can be set for a Microsoft 365 group during creation. Possible values are AllowOnlyMembersToPost, HideGroupInOutlook, SubscribeNewGroupMembers, WelcomeEmailDisabled."}, + {Name: "resource_provisioning_options", Type: proto.ColumnType_JSON, Description: "Specifies the group resources that are provisioned as part of Microsoft 365 group creation, that are not normally part of default group creation. Possible value is Team."}, // Standard columns {Name: "tags", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTags, Transform: transform.From(adGroupTags)}, @@ -159,7 +161,10 @@ func listAdGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDat err = pageIterator.Iterate(func(pageItem interface{}) bool { group := pageItem.(models.Groupable) - d.StreamListItem(ctx, &ADGroupInfo{group}) + resourceBehaviorOptions := formatResourceBehaviorOptions(ctx, group) + resourceProvisioningOptions := formatResourceProvisioningOptions(ctx, group) + + d.StreamListItem(ctx, &ADGroupInfo{group, resourceBehaviorOptions, resourceProvisioningOptions}) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { @@ -201,8 +206,10 @@ func getAdGroup(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) errObj := getErrorObject(err) return nil, errObj } + resourceBehaviorOptions := formatResourceBehaviorOptions(ctx, group) + resourceProvisioningOptions := formatResourceProvisioningOptions(ctx, group) - return &ADGroupInfo{group}, nil + return &ADGroupInfo{group, resourceBehaviorOptions, resourceProvisioningOptions}, nil } func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { @@ -380,3 +387,45 @@ func buildGroupBoolNEFilter(quals plugin.KeyColumnQualMap) []string { return filters } + +func formatResourceBehaviorOptions(ctx context.Context, group models.Groupable) []string { + var resourceBehaviorOptions []string + data := group.GetAdditionalData()["resourceBehaviorOptions"] + if data != nil { + parsedData := group.GetAdditionalData()["resourceBehaviorOptions"].([]*jsonserialization.JsonParseNode) + + for _, r := range parsedData { + val, err := r.GetStringValue() + if err != nil { + plugin.Logger(ctx).Error("failed to parse resourceBehaviorOptions: %v", err) + val = nil + } + + if val != nil { + resourceBehaviorOptions = append(resourceBehaviorOptions, *val) + } + } + } + return resourceBehaviorOptions +} + +func formatResourceProvisioningOptions(ctx context.Context, group models.Groupable) []string { + var resourceProvisioningOptions []string + data := group.GetAdditionalData()["resourceProvisioningOptions"] + if data != nil { + parsedData := data.([]*jsonserialization.JsonParseNode) + + for _, r := range parsedData { + val, err := r.GetStringValue() + if err != nil { + plugin.Logger(ctx).Error("failed to parse resourceProvisioningOptions: %v", err) + val = nil + } + + if val != nil { + resourceProvisioningOptions = append(resourceProvisioningOptions, *val) + } + } + } + return resourceProvisioningOptions +} diff --git a/azuread/table_azuread_user.go b/azuread/table_azuread_user.go index ac9ef72..39adb4b 100644 --- a/azuread/table_azuread_user.go +++ b/azuread/table_azuread_user.go @@ -62,7 +62,7 @@ func tableAzureAdUser() *plugin.Table { {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the user, for example, jeff@contoso.onmicrosoft.com.", Transform: transform.FromMethod("GetMail")}, {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user.", Transform: transform.FromMethod("GetMailNickname")}, {Name: "password_policies", Type: proto.ColumnType_STRING, Description: "Specifies password policies for the user. This value is an enumeration with one possible value being DisableStrongPassword, which allows weaker passwords than the default policy to be specified. DisablePasswordExpiration can also be specified. The two may be specified together; for example: DisablePasswordExpiration, DisableStrongPassword.", Transform: transform.FromMethod("GetPasswordPolicies")}, - // {Name: "refresh_tokens_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph)."}, + {Name: "refresh_tokens_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph)."}, {Name: "sign_in_sessions_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph).", Transform: transform.FromMethod("GetSignInSessionsValidFromDateTime")}, {Name: "usage_location", Type: proto.ColumnType_STRING, Description: "A two letter country code (ISO standard 3166), required for users that will be assigned licenses due to legal requirement to check for availability of services in countries.", Transform: transform.FromMethod("GetUsageLocation")}, @@ -140,7 +140,9 @@ func listAdUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData err = pageIterator.Iterate(func(pageItem interface{}) bool { user := pageItem.(models.Userable) - d.StreamListItem(ctx, &ADUserInfo{user}) + refreshTokensValidFromDateTime := user.GetAdditionalData()["refreshTokensValidFromDateTime"] + + d.StreamListItem(ctx, &ADUserInfo{user, refreshTokensValidFromDateTime}) // Context can be cancelled due to manual cancellation or the limit has been hit if d.QueryStatus.RowsRemaining(ctx) == 0 { @@ -188,8 +190,9 @@ func getAdUser(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) errObj := getErrorObject(err) return nil, errObj } + refreshTokensValidFromDateTime := user.GetAdditionalData()["refreshTokensValidFromDateTime"] - return &ADUserInfo{user}, nil + return &ADUserInfo{user, refreshTokensValidFromDateTime}, nil } func buildUserRequestFields(ctx context.Context, queryColumns []string) ([]string, []string) { @@ -205,11 +208,6 @@ func buildUserRequestFields(ctx context.Context, queryColumns []string) ([]strin continue } - if columnName == "refresh_tokens_valid_from_date_time" { - selectColumns = append(selectColumns, "signInSessionsValidFromDateTime") - continue - } - selectColumns = append(selectColumns, strcase.ToLowerCamel(columnName)) } From 6f4bb4b4e707174ae443d8b3ea92c818df2360ae Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Fri, 5 Aug 2022 11:32:40 +0530 Subject: [PATCH 13/21] Fix linter error --- azuread/service.go | 19 +++++++++++---- azuread/table_azuread_application.go | 15 ++++++++---- ...table_azuread_conditional_access_policy.go | 10 ++++---- azuread/table_azuread_directory_role.go | 5 ++++ azuread/table_azuread_domain.go | 10 ++++---- azuread/table_azuread_group.go | 23 ++++++++++++++----- azuread/table_azuread_service_principal.go | 15 ++++++++---- azuread/table_azuread_sign_in_report.go | 12 +++++----- azuread/table_azuread_user.go | 10 ++++---- 9 files changed, 77 insertions(+), 42 deletions(-) diff --git a/azuread/service.go b/azuread/service.go index 79138f0..dd642ac 100644 --- a/azuread/service.go +++ b/azuread/service.go @@ -28,7 +28,8 @@ type Session struct { Authorizer auth.Authorizer } -/* GetNewSession creates an session configured from (~/.steampipe/config, environment variables and CLI) in the order: +/* +GetNewSession creates an session configured from (~/.steampipe/config, environment variables and CLI) in the order: 1. Client secret 2. Client certificate 3. Username and password @@ -310,8 +311,8 @@ func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.Gra &azidentity.AzureCLICredentialOptions{}, ) if err != nil { - logger.Error("GetGraphClient", "credential_error", err) - return nil, nil, fmt.Errorf("error creating credentials: %w", err) + logger.Error("GetGraphClient", "cli_credential_error", err) + return nil, nil, err } } else if tenantID != "" && clientID != "" && clientSecret != "" { // Client secret authentication cred, err = azidentity.NewClientSecretCredential( @@ -325,8 +326,8 @@ func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.Gra }, ) if err != nil { - logger.Error("GetGraphClient", "credential_error", err) - return nil, nil, fmt.Errorf("error creating credentials: %w", err) + logger.Error("GetGraphClient", "client_secret_credential_error", err) + return nil, nil, err } } else if tenantID != "" && clientID != "" && certificatePath != "" { // Client certificate authentication // Load certificate from given path @@ -358,10 +359,18 @@ func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.Gra }, }, ) + if err != nil { + logger.Error("GetGraphClient", "client_certificate_credential_error", err) + return nil, nil, err + } } else if enableMsi { // Managed identity authentication cred, err = azidentity.NewManagedIdentityCredential( &azidentity.ManagedIdentityCredentialOptions{}, ) + if err != nil { + logger.Error("GetGraphClient", "managed_identity_credential_error", err) + return nil, nil, err + } } auth, err := a.NewAzureIdentityAuthenticationProvider(cred) diff --git a/azuread/table_azuread_application.go b/azuread/table_azuread_application.go index d45b2d2..b2e0742 100644 --- a/azuread/table_azuread_application.go +++ b/azuread/table_azuread_application.go @@ -123,6 +123,10 @@ func listAdApplications(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydr } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateApplicationCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("listAdApplications", "create_iterator_instance_error", err) + return nil, err + } err = pageIterator.Iterate(func(pageItem interface{}) bool { application := pageItem.(models.Applicationable) @@ -132,11 +136,7 @@ func listAdApplications(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydr d.StreamListItem(ctx, &ADApplicationInfo{application, isAuthorizationServiceEnabled}) // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true + return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { return nil, err @@ -202,6 +202,11 @@ func getAdApplicationOwners(ctx context.Context, d *plugin.QueryData, h *plugin. } pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("getAdApplicationOwners", "create_iterator_instance_error", err) + return nil, err + } + err = pageIterator.Iterate(func(pageItem interface{}) bool { owner := pageItem.(models.DirectoryObjectable) ownerIds = append(ownerIds, owner.GetId()) diff --git a/azuread/table_azuread_conditional_access_policy.go b/azuread/table_azuread_conditional_access_policy.go index 4c86c88..3c8f4a4 100644 --- a/azuread/table_azuread_conditional_access_policy.go +++ b/azuread/table_azuread_conditional_access_policy.go @@ -112,6 +112,10 @@ func listAdConditionalAccessPolicies(ctx context.Context, d *plugin.QueryData, _ } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateConditionalAccessPolicyCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("listAdConditionalAccessPolicies", "create_iterator_instance_error", err) + return nil, err + } err = pageIterator.Iterate(func(pageItem interface{}) bool { policy := pageItem.(models.ConditionalAccessPolicyable) @@ -119,11 +123,7 @@ func listAdConditionalAccessPolicies(ctx context.Context, d *plugin.QueryData, _ d.StreamListItem(ctx, &ADConditionalAccessPolicyInfo{policy}) // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true + return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { return nil, err diff --git a/azuread/table_azuread_directory_role.go b/azuread/table_azuread_directory_role.go index 38659f1..ea85437 100644 --- a/azuread/table_azuread_directory_role.go +++ b/azuread/table_azuread_directory_role.go @@ -134,6 +134,11 @@ func getDirectoryRoleMembers(ctx context.Context, d *plugin.QueryData, h *plugin } pageIterator, err := msgraphcore.NewPageIterator(members, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("getDirectoryRoleMembers", "create_iterator_instance_error", err) + return nil, err + } + err = pageIterator.Iterate(func(pageItem interface{}) bool { member := pageItem.(models.DirectoryObjectable) memberIds = append(memberIds, member.GetId()) diff --git a/azuread/table_azuread_domain.go b/azuread/table_azuread_domain.go index 96439c7..e96f2f8 100644 --- a/azuread/table_azuread_domain.go +++ b/azuread/table_azuread_domain.go @@ -89,6 +89,10 @@ func listAdDomains(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDa } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateDomainCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("listAdDomains", "create_iterator_instance_error", err) + return nil, err + } err = pageIterator.Iterate(func(pageItem interface{}) bool { domain := pageItem.(models.Domainable) @@ -96,11 +100,7 @@ func listAdDomains(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDa d.StreamListItem(ctx, &ADDomainInfo{domain}) // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true + return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { return nil, err diff --git a/azuread/table_azuread_group.go b/azuread/table_azuread_group.go index ff85fbf..8b964e3 100644 --- a/azuread/table_azuread_group.go +++ b/azuread/table_azuread_group.go @@ -120,6 +120,7 @@ func listAdGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDat input := &groups.GroupsRequestBuilderGetQueryParameters{} // Restrict the limit value to be passed in the query parameter which is not between 1 and 999, otherwise API will throw an error as follow + // unexpected status 400 with OData error: Request_UnsupportedQuery: Invalid page size specified: '1000'. Must be between 1 and 999 inclusive. limit := d.QueryContext.Limit if limit != nil { if *limit > 0 && *limit <= 999 { @@ -157,6 +158,10 @@ func listAdGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDat } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateGroupCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("listAdGroups", "create_iterator_instance_error", err) + return nil, err + } err = pageIterator.Iterate(func(pageItem interface{}) bool { group := pageItem.(models.Groupable) @@ -167,11 +172,7 @@ func listAdGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDat d.StreamListItem(ctx, &ADGroupInfo{group, resourceBehaviorOptions, resourceProvisioningOptions}) // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true + return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { return nil, err @@ -244,6 +245,11 @@ func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.Hydra } pageIterator, err := msgraphcore.NewPageIterator(members, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("getAdGroupMembers", "create_iterator_instance_error", err) + return nil, err + } + err = pageIterator.Iterate(func(pageItem interface{}) bool { member := pageItem.(models.DirectoryObjectable) memberIds = append(memberIds, member.GetId()) @@ -289,6 +295,11 @@ func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat } pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("getAdGroupOwners", "create_iterator_instance_error", err) + return nil, err + } + err = pageIterator.Iterate(func(pageItem interface{}) bool { member := pageItem.(models.DirectoryObjectable) ownerIds = append(ownerIds, member.GetId()) @@ -311,7 +322,7 @@ func adGroupTags(ctx context.Context, d *transform.TransformData) (interface{}, } assignedLabels := group.GroupAssignedLabels() - if assignedLabels == nil || len(assignedLabels) == 0 { + if len(assignedLabels) == 0 { return nil, nil } diff --git a/azuread/table_azuread_service_principal.go b/azuread/table_azuread_service_principal.go index fce791d..88250ad 100644 --- a/azuread/table_azuread_service_principal.go +++ b/azuread/table_azuread_service_principal.go @@ -131,6 +131,10 @@ func listAdServicePrincipals(ctx context.Context, d *plugin.QueryData, _ *plugin } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateServicePrincipalCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("listAdServicePrincipals", "create_iterator_instance_error", err) + return nil, err + } err = pageIterator.Iterate(func(pageItem interface{}) bool { servicePrincipal := pageItem.(models.ServicePrincipalable) @@ -138,11 +142,7 @@ func listAdServicePrincipals(ctx context.Context, d *plugin.QueryData, _ *plugin d.StreamListItem(ctx, &ADServicePrincipalInfo{servicePrincipal}) // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true + return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { return nil, err @@ -211,6 +211,11 @@ func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plug } pageIterator, err := msgraphcore.NewPageIterator(owners, adapter, models.CreateDirectoryObjectCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("getServicePrincipalOwners", "create_iterator_instance_error", err) + return nil, err + } + err = pageIterator.Iterate(func(pageItem interface{}) bool { owner := pageItem.(models.DirectoryObjectable) ownerIds = append(ownerIds, owner.GetId()) diff --git a/azuread/table_azuread_sign_in_report.go b/azuread/table_azuread_sign_in_report.go index 91f2046..9f59ef2 100644 --- a/azuread/table_azuread_sign_in_report.go +++ b/azuread/table_azuread_sign_in_report.go @@ -97,6 +97,10 @@ func listAdSignInReports(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateSignInCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("listAdSignInReports", "create_iterator_instance_error", err) + return nil, err + } err = pageIterator.Iterate(func(pageItem interface{}) bool { signIn := pageItem.(models.SignInable) @@ -104,11 +108,7 @@ func listAdSignInReports(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd d.StreamListItem(ctx, &ADSignInReportInfo{signIn}) // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true + return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { return nil, err @@ -145,7 +145,7 @@ func getAdSignInReport(ctx context.Context, d *plugin.QueryData, h *plugin.Hydra func formatSignInReportRiskEventTypes(_ context.Context, d *transform.TransformData) (interface{}, error) { data := d.HydrateItem.(*ADSignInReportInfo) riskEventTypes := data.GetRiskEventTypes() - if riskEventTypes == nil || len(riskEventTypes) == 0 { + if len(riskEventTypes) == 0 { return nil, nil } diff --git a/azuread/table_azuread_user.go b/azuread/table_azuread_user.go index 39adb4b..520acf7 100644 --- a/azuread/table_azuread_user.go +++ b/azuread/table_azuread_user.go @@ -136,6 +136,10 @@ func listAdUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData } pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateUserCollectionResponseFromDiscriminatorValue) + if err != nil { + plugin.Logger(ctx).Error("listAdUsers", "create_iterator_instance_error", err) + return nil, err + } err = pageIterator.Iterate(func(pageItem interface{}) bool { user := pageItem.(models.Userable) @@ -145,11 +149,7 @@ func listAdUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData d.StreamListItem(ctx, &ADUserInfo{user, refreshTokensValidFromDateTime}) // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return false - } - - return true + return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { return nil, err From b255be11340d429bb6f56779add2c11e6827393a Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Fri, 5 Aug 2022 17:32:11 +0530 Subject: [PATCH 14/21] Update azuread_identity_provider table to use msgraph SDK --- azuread/helpers.go | 6 + azuread/plugin.go | 4 +- azuread/table_azuread_identity_provider.go | 144 ++++++++++++++------- azuread/table_azuread_user.go | 24 ---- azuread/utils.go | 44 +++---- go.mod | 4 +- go.sum | 2 - 7 files changed, 129 insertions(+), 99 deletions(-) diff --git a/azuread/helpers.go b/azuread/helpers.go index 023b306..c4ba311 100644 --- a/azuread/helpers.go +++ b/azuread/helpers.go @@ -17,6 +17,12 @@ type ADGroupInfo struct { ResourceProvisioningOptions []string } +type ADIdentityProviderInfo struct { + models.BuiltInIdentityProvider + ClientId interface{} + ClientSecret interface{} +} + type ADServicePrincipalInfo struct { models.ServicePrincipalable } diff --git a/azuread/plugin.go b/azuread/plugin.go index 1f0d573..959ae70 100644 --- a/azuread/plugin.go +++ b/azuread/plugin.go @@ -15,7 +15,9 @@ func Plugin(ctx context.Context) *plugin.Plugin { Name: pluginName, DefaultTransform: transform.FromCamel(), DefaultGetConfig: &plugin.GetConfig{ - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Request_ResourceNotFound"}), + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_ResourceNotFound"}), + }, }, ConnectionConfigSchema: &plugin.ConnectionConfigSchema{ NewInstance: ConfigInstance, diff --git a/azuread/table_azuread_identity_provider.go b/azuread/table_azuread_identity_provider.go index fb46000..07dc3d5 100644 --- a/azuread/table_azuread_identity_provider.go +++ b/azuread/table_azuread_identity_provider.go @@ -2,11 +2,17 @@ package azuread import ( "context" + "fmt" + "strings" - "github.com/manicminer/hamilton/msgraph" + "github.com/iancoleman/strcase" "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" + msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" + "github.com/microsoftgraph/msgraph-sdk-go/identity/identityproviders" + "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/turbot/steampipe-plugin-sdk/v3/plugin" ) @@ -16,27 +22,32 @@ func tableAzureAdIdentityProvider() *plugin.Table { return &plugin.Table{ Name: "azuread_identity_provider", Description: "Represents an Azure Active Directory (Azure AD) identity provider", - Get: &plugin.GetConfig{ - Hydrate: getAdIdentityProvider, - ShouldIgnoreError: isNotFoundErrorPredicate([]string{"Invalid object identifier"}), - KeyColumns: plugin.SingleColumn("id"), - }, List: &plugin.ListConfig{ Hydrate: listAdIdentityProviders, + IgnoreConfig: &plugin.IgnoreConfig{ + ShouldIgnoreErrorFunc: isIgnorableErrorPredicate([]string{"Request_UnsupportedQuery", "Invalid filter clause"}), + }, + KeyColumns: plugin.KeyColumnSlice{ + // Key fields + {Name: "id", Require: plugin.Optional}, + {Name: "name", Require: plugin.Optional}, + {Name: "filter", Require: plugin.Optional}, + }, }, Columns: []*plugin.Column{ - {Name: "id", Type: proto.ColumnType_STRING, Description: "The ID of the identity provider.", Transform: transform.FromGo()}, - {Name: "name", Type: proto.ColumnType_STRING, Description: "The display name of the identity provider."}, + {Name: "id", Type: proto.ColumnType_STRING, Description: "The ID of the identity provider.", Transform: transform.FromMethod("GetId")}, + {Name: "name", Type: proto.ColumnType_STRING, Description: "The display name of the identity provider.", Transform: transform.FromMethod("GetDisplayName")}, // Other fields - {Name: "type", Type: proto.ColumnType_STRING, Description: "The identity provider type is a required field. For B2B scenario: Google, Facebook. For B2C scenario: Microsoft, Google, Amazon, LinkedIn, Facebook, GitHub, Twitter, Weibo, QQ, WeChat, OpenIDConnect."}, + {Name: "type", Type: proto.ColumnType_STRING, Description: "The identity provider type is a required field. For B2B scenario: Google, Facebook. For B2C scenario: Microsoft, Google, Amazon, LinkedIn, Facebook, GitHub, Twitter, Weibo, QQ, WeChat, OpenIDConnect.", Transform: transform.FromMethod("GetIdentityProviderType")}, {Name: "client_id", Type: proto.ColumnType_STRING, Description: "The client ID for the application. This is the client ID obtained when registering the application with the identity provider."}, {Name: "client_secret", Type: proto.ColumnType_STRING, Description: "The client secret for the application. This is the client secret obtained when registering the application with the identity provider. This is write-only. A read operation will return ****."}, + {Name: "filter", Type: proto.ColumnType_STRING, Transform: transform.FromQual("filter"), Description: "Odata query to search for resources."}, // Standard columns - {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.FromField("DisplayName", "ID")}, - {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenantId).WithCache(), Transform: transform.FromValue()}, + {Name: "title", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTitle, Transform: transform.From(adIdentityProviderTitle)}, + {Name: "tenant_id", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTenant, Hydrate: plugin.HydrateFunc(getTenant).WithCache(), Transform: transform.FromValue()}, }, } } @@ -44,60 +55,103 @@ func tableAzureAdIdentityProvider() *plugin.Table { //// LIST FUNCTION func listAdIdentityProviders(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData) (interface{}, error) { - session, err := GetNewSession(ctx, d) + // Create client + client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, err + return nil, fmt.Errorf("error creating client: %v", err) } - client := msgraph.NewIdentityProvidersClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer + // List operations + input := &identityproviders.IdentityProvidersRequestBuilderGetQueryParameters{} - identityProviders, _, err := client.List(ctx) - if err != nil { - if isNotFoundError(err) { - return nil, nil - } - return nil, err + limit := d.QueryContext.Limit + if limit != nil { + l := int32(*limit) + input.Top = &l } - for _, identityProviders := range *identityProviders { - d.StreamListItem(ctx, identityProviders) + var queryFilter string + equalQuals := d.KeyColumnQuals + filter := buildIdentityProviderQueryFilter(equalQuals) - // Context can be cancelled due to manual cancellation or the limit has been hit - if d.QueryStatus.RowsRemaining(ctx) == 0 { - return nil, nil - } + if equalQuals["filter"] != nil { + queryFilter = equalQuals["filter"].GetStringValue() } - return nil, err -} - -//// Hydrate Functions + if queryFilter != "" { + input.Filter = &queryFilter + } else if len(filter) > 0 { + joinStr := strings.Join(filter, " and ") + input.Filter = &joinStr + } -func getAdIdentityProvider(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var identityProviderId string - if h.Item != nil { - identityProviderId = *h.Item.(msgraph.IdentityProvider).ID - } else { - identityProviderId = d.KeyColumnQuals["id"].GetStringValue() + options := &identityproviders.IdentityProvidersRequestBuilderGetRequestConfiguration{ + QueryParameters: input, } - if identityProviderId == "" { - return nil, nil + result, err := client.Identity().IdentityProviders().GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + return nil, errObj } - session, err := GetNewSession(ctx, d) + pageIterator, err := msgraphcore.NewPageIterator(result, adapter, models.CreateBuiltInIdentityProviderFromDiscriminatorValue) if err != nil { + plugin.Logger(ctx).Error("listAdIdentityProviders", "create_iterator_instance_error", err) return nil, err } - client := msgraph.NewIdentityProvidersClient(session.TenantID) - client.BaseClient.Authorizer = session.Authorizer - client.BaseClient.DisableRetries = true + err = pageIterator.Iterate(func(pageItem interface{}) bool { + identityProvider := pageItem.(*models.BuiltInIdentityProvider) - identityProvider, _, err := client.Get(ctx, identityProviderId) + clientID := identityProvider.GetAdditionalData()["clientId"] + clientSecret := identityProvider.GetAdditionalData()["clientSecret"] + + d.StreamListItem(ctx, &ADIdentityProviderInfo{*identityProvider, clientID, clientSecret}) + + // Context can be cancelled due to manual cancellation or the limit has been hit + return d.QueryStatus.RowsRemaining(ctx) != 0 + }) if err != nil { return nil, err } - return *identityProvider, nil + + return nil, nil +} + +//// TRANSFORM FUNCTIONS + +func buildIdentityProviderQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { + filters := []string{} + + filterQuals := map[string]string{ + "id": "string", + "name": "string", + } + + for qual := range filterQuals { + if equalQuals[qual] != nil { + if qual == "name" { + filters = append(filters, fmt.Sprintf("displayName eq '%s'", equalQuals[qual].GetStringValue())) + } else { + filters = append(filters, fmt.Sprintf("%s eq '%s'", strcase.ToCamel(qual), equalQuals[qual].GetStringValue())) + } + } + } + + return filters +} + +func adIdentityProviderTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(models.IdentityProviderBaseable) + if data == nil { + return nil, nil + } + + title := data.GetDisplayName() + if title == nil { + title = data.GetId() + } + + return title, nil } diff --git a/azuread/table_azuread_user.go b/azuread/table_azuread_user.go index 520acf7..597cdd1 100644 --- a/azuread/table_azuread_user.go +++ b/azuread/table_azuread_user.go @@ -3,7 +3,6 @@ package azuread import ( "context" "fmt" - "os" "strings" "github.com/iancoleman/strcase" @@ -214,29 +213,6 @@ func buildUserRequestFields(ctx context.Context, queryColumns []string) ([]strin return selectColumns, expandColumns } -func getTenant(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - var tenantID string - var err error - - // Read tenantID from config, or environment variables - azureADConfig := GetConfig(d.Connection) - if azureADConfig.TenantID != nil { - tenantID = *azureADConfig.TenantID - } else { - tenantID = os.Getenv("AZURE_TENANT_ID") - } - - // If not set in config, get tenantID from CLI - if tenantID == "" { - tenantID, err = getTenantFromCLI() - if err != nil { - return nil, err - } - } - - return tenantID, nil -} - //// TRANSFORM FUNCTIONS func adUserTitle(_ context.Context, d *transform.TransformData) (interface{}, error) { diff --git a/azuread/utils.go b/azuread/utils.go index 66713d3..ce792af 100644 --- a/azuread/utils.go +++ b/azuread/utils.go @@ -2,7 +2,7 @@ package azuread import ( "context" - "strings" + "os" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" ) @@ -14,23 +14,6 @@ const ( ColumnDescriptionTitle = "Title of the resource." ) -func isNotFoundError(err error) bool { - return strings.Contains(err.Error(), "Request_ResourceNotFound") -} - -func isNotFoundErrorPredicate(notFoundErrors []string) plugin.ErrorPredicate { - return func(err error) bool { - if err != nil { - for _, item := range notFoundErrors { - if strings.Contains(err.Error(), item) { - return true - } - } - } - return false - } -} - func TagsToMap(tags []string) (*map[string]bool, error) { var turbotTagsMap map[string]bool if tags == nil { @@ -51,13 +34,26 @@ type QualsColumn struct { FilterName string } -func getTenantId(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { - plugin.Logger(ctx).Debug("getTenantId") +func getTenant(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + plugin.Logger(ctx).Debug("getTenant") + var tenantID string + var err error + + // Read tenantID from config, or environment variables + azureADConfig := GetConfig(d.Connection) + if azureADConfig.TenantID != nil { + tenantID = *azureADConfig.TenantID + } else { + tenantID = os.Getenv("AZURE_TENANT_ID") + } - session, err := GetNewSession(ctx, d) - if err != nil { - return nil, err + // If not set in config, get tenantID from CLI + if tenantID == "" { + tenantID, err = getTenantFromCLI() + if err != nil { + return nil, err + } } - return session.TenantID, nil + return tenantID, nil } diff --git a/go.mod b/go.mod index e31634a..1b35977 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/iancoleman/strcase v0.2.0 github.com/manicminer/hamilton v0.24.0 github.com/microsoft/kiota-authentication-azure-go v0.3.1 + github.com/microsoft/kiota-serialization-json-go v0.5.5 github.com/microsoftgraph/msgraph-sdk-go v0.30.0 github.com/microsoftgraph/msgraph-sdk-go-core v0.26.2 github.com/turbot/steampipe-plugin-sdk/v3 v3.3.2 @@ -36,10 +37,8 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/uuid v1.3.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.2.0 // indirect github.com/hashicorp/go-plugin v1.4.4 // indirect - github.com/hashicorp/go-retryablehttp v0.7.0 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/go-version v1.5.0 // indirect github.com/hashicorp/hcl/v2 v2.12.0 // indirect @@ -50,7 +49,6 @@ require ( github.com/mattn/go-runewidth v0.0.13 // indirect github.com/microsoft/kiota-abstractions-go v0.8.1 // indirect github.com/microsoft/kiota-http-go v0.5.2 // indirect - github.com/microsoft/kiota-serialization-json-go v0.5.5 // indirect github.com/microsoft/kiota-serialization-text-go v0.4.1 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect diff --git a/go.sum b/go.sum index 8dd0c7e..33fead8 100644 --- a/go.sum +++ b/go.sum @@ -181,14 +181,12 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= -github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= From 7651e139813ffe1dbdfb1bfe5439d4f8059b6b02 Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Fri, 5 Aug 2022 21:42:27 +0530 Subject: [PATCH 15/21] Update error logs, remove unused functions and remove column refresh_tokens_valid_from_date_time from table azuread_user --- azuread/service.go | 266 ++++-------------- azuread/table_azuread_application.go | 13 +- ...table_azuread_conditional_access_policy.go | 9 +- azuread/table_azuread_directory_role.go | 14 +- azuread/table_azuread_domain.go | 10 +- azuread/table_azuread_group.go | 19 +- azuread/table_azuread_identity_provider.go | 5 +- azuread/table_azuread_service_principal.go | 14 +- azuread/table_azuread_sign_in_report.go | 10 +- azuread/table_azuread_user.go | 10 +- go.mod | 4 - go.sum | 18 -- 12 files changed, 129 insertions(+), 263 deletions(-) diff --git a/azuread/service.go b/azuread/service.go index dd642ac..018ad98 100644 --- a/azuread/service.go +++ b/azuread/service.go @@ -15,230 +15,18 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - "github.com/manicminer/hamilton/auth" - "github.com/manicminer/hamilton/environments" a "github.com/microsoft/kiota-authentication-azure-go" msgraphsdkgo "github.com/microsoftgraph/msgraph-sdk-go" "github.com/turbot/steampipe-plugin-sdk/v3/plugin" ) -// Session info -type Session struct { - TenantID string - Authorizer auth.Authorizer -} - /* -GetNewSession creates an session configured from (~/.steampipe/config, environment variables and CLI) in the order: +GetGraphClient creates a graph service client configured from (~/.steampipe/config, environment variables and CLI) in the order: 1. Client secret 2. Client certificate -3. Username and password -4. MSI -5. CLI +3. MSI +4. CLI */ -func GetNewSession(ctx context.Context, d *plugin.QueryData) (sess *Session, err error) { - logger := plugin.Logger(ctx) - - // Have we already created and cached the session? - // Hamilton SDK already acquires a new token when expired, so don't handle here again - sessionCacheKey := "GetNewSession" - if cachedData, ok := d.ConnectionManager.Cache.Get(sessionCacheKey); ok { - return cachedData.(*Session), nil - } - - azureADConfig := GetConfig(d.Connection) - var tenantID string - authMethod, authConfig, err := getApplicableAuthorizationDetails(ctx, azureADConfig) - if err != nil { - logger.Debug("GetNewSession__", "getApplicableAuthorizationDetails error", err) - return nil, err - } - - if authConfig.TenantID != "" { - tenantID = authConfig.TenantID - } - - authorizer, err := authConfig.NewAuthorizer(ctx, auth.MsGraph) - if err != nil { - logger.Debug("GetNewSession__", "authorizer error", err) - return nil, err - } - - if authMethod == "CLI" { - tenantID, err = getTenantFromCLI() - if err != nil { - logger.Debug("GetNewSession__", "getTenantFromCLI error", err) - return nil, err - } - } - - sess = &Session{ - Authorizer: authorizer, - TenantID: tenantID, - } - - // Save session into cache - d.ConnectionManager.Cache.Set(sessionCacheKey, sess) - - return sess, err -} - -func getApplicableAuthorizationDetails(ctx context.Context, config azureADConfig) (authMethod string, authConfig auth.Config, err error) { - - var environment, tenantID, clientID, clientSecret, certificatePath, certificatePassword, msiEndpoint string - var enableMsi bool - // username, password string - if config.TenantID != nil { - tenantID = *config.TenantID - } else { - tenantID = os.Getenv("AZURE_TENANT_ID") - } - - if config.Environment != nil { - environment = *config.Environment - } else { - environment = os.Getenv("AZURE_ENVIRONMENT") - } - - // Can be "AZURECHINACLOUD", "AZUREGERMANCLOUD", "AZUREPUBLICCLOUD", "AZUREUSGOVERNMENTCLOUD" - switch environment { - case "AZURECHINACLOUD": - authConfig.Environment = environments.China - case "AZUREUSGOVERNMENTCLOUD": - authConfig.Environment = environments.USGovernmentL4 - case "AZUREGERMANCLOUD": - authConfig.Environment = environments.Germany - default: - authConfig.Environment = environments.Global - } - - // 1. Client secret credentials - if config.ClientID != nil { - clientID = *config.ClientID - } else { - clientID = os.Getenv("AZURE_CLIENT_ID") - } - - if config.ClientSecret != nil { - clientSecret = *config.ClientSecret - } else { - clientSecret = os.Getenv("AZURE_CLIENT_SECRET") - } - - // 2. Client certificate credentials - if config.CertificatePath != nil { - certificatePath = *config.CertificatePath - } else { - certificatePath = os.Getenv("AZURE_CERTIFICATE_PATH") - } - - if config.CertificatePassword != nil { - certificatePassword = *config.CertificatePassword - } else { - certificatePassword = os.Getenv("AZURE_CERTIFICATE_PASSWORD") - } - - // TODO - // 3. Username and password - // if config.Username != nil { - // username = *config.Username - // } else { - // username = os.Getenv("AZURE_USERNAME") - // } - - // if config.Password != nil { - // password = *config.Password - // } else { - // password = os.Getenv("AZURE_PASSWORD") - // } - - // 4. MSI credentials - msiEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token" - if config.EnableMsi != nil { - enableMsi = *config.EnableMsi - - if config.MsiEndpoint != nil { - msiEndpoint = *config.MsiEndpoint - } - } - - // 5. Default to CLI credentials - authMethod = "CLI" - - if tenantID == "" { - authMethod = "CLI" - authConfig.EnableAzureCliToken = true - } else if tenantID != "" && clientID != "" && clientSecret != "" { - authConfig.TenantID = tenantID - authConfig.ClientID = clientID - authConfig.ClientSecret = clientSecret - authConfig.EnableClientSecretAuth = true - authMethod = "EnableClientSecretAuth" - } else if tenantID != "" && clientID != "" && certificatePath != "" && certificatePassword != "" { - authConfig.TenantID = tenantID - authConfig.ClientID = clientID - authConfig.ClientCertPath = certificatePath - authConfig.ClientCertPassword = certificatePassword - authConfig.EnableClientCertAuth = true - authMethod = "EnableClientCertificateAuth" - } else if enableMsi { - authConfig.EnableMsiAuth = true - authConfig.MsiEndpoint = msiEndpoint - authConfig.TenantID = tenantID - authConfig.ClientID = clientID - authMethod = "EnableMsiAuth" - } - return -} - -// https://github.com/Azure/go-autorest/blob/3fb5326fea196cd5af02cf105ca246a0fba59021/autorest/azure/cli/token.go#L126 -// NewAuthorizerFromCLIWithResource creates an Authorizer configured from Azure CLI 2.0 for local development scenarios. -func getTenantFromCLI() (string, error) { - // This is the path that a developer can set to tell this class what the install path for Azure CLI is. - const azureCLIPath = "AzureCLIPath" - - // The default install paths are used to find Azure CLI. This is for security, so that any path in the calling program's Path environment is not used to execute Azure CLI. - azureCLIDefaultPathWindows := fmt.Sprintf("%s\\Microsoft SDKs\\Azure\\CLI2\\wbin; %s\\Microsoft SDKs\\Azure\\CLI2\\wbin", os.Getenv("ProgramFiles(x86)"), os.Getenv("ProgramFiles")) - - // Default path for non-Windows. - const azureCLIDefaultPath = "/bin:/sbin:/usr/bin:/usr/local/bin" - - // Execute Azure CLI to get token - var cliCmd *exec.Cmd - if runtime.GOOS == "windows" { - cliCmd = exec.Command(fmt.Sprintf("%s\\system32\\cmd.exe", os.Getenv("windir"))) - cliCmd.Env = os.Environ() - cliCmd.Env = append(cliCmd.Env, fmt.Sprintf("PATH=%s;%s", os.Getenv(azureCLIPath), azureCLIDefaultPathWindows)) - cliCmd.Args = append(cliCmd.Args, "/c", "az") - } else { - cliCmd = exec.Command("az") - cliCmd.Env = os.Environ() - cliCmd.Env = append(cliCmd.Env, fmt.Sprintf("PATH=%s:%s", os.Getenv(azureCLIPath), azureCLIDefaultPath)) - } - cliCmd.Args = append(cliCmd.Args, "account", "get-access-token", "--resource-type=ms-graph", "-o", "json") - - var stderr bytes.Buffer - cliCmd.Stderr = &stderr - - output, err := cliCmd.Output() - if err != nil { - return "", fmt.Errorf("Invoking Azure CLI failed with the following error: %v", err) - } - - var tokenResponse struct { - AccessToken string `json:"accessToken"` - ExpiresOn string `json:"expiresOn"` - Tenant string `json:"tenant"` - TokenType string `json:"tokenType"` - } - err = json.Unmarshal(output, &tokenResponse) - if err != nil { - return "", err - } - - return tokenResponse.Tenant, nil -} - func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.GraphServiceClient, *msgraphsdkgo.GraphRequestAdapter, error) { logger := plugin.Logger(ctx) @@ -389,3 +177,51 @@ func GetGraphClient(ctx context.Context, d *plugin.QueryData) (*msgraphsdkgo.Gra return client, adapter, nil } + +// https://github.com/Azure/go-autorest/blob/3fb5326fea196cd5af02cf105ca246a0fba59021/autorest/azure/cli/token.go#L126 +// NewAuthorizerFromCLIWithResource creates an Authorizer configured from Azure CLI 2.0 for local development scenarios. +func getTenantFromCLI() (string, error) { + // This is the path that a developer can set to tell this class what the install path for Azure CLI is. + const azureCLIPath = "AzureCLIPath" + + // The default install paths are used to find Azure CLI. This is for security, so that any path in the calling program's Path environment is not used to execute Azure CLI. + azureCLIDefaultPathWindows := fmt.Sprintf("%s\\Microsoft SDKs\\Azure\\CLI2\\wbin; %s\\Microsoft SDKs\\Azure\\CLI2\\wbin", os.Getenv("ProgramFiles(x86)"), os.Getenv("ProgramFiles")) + + // Default path for non-Windows. + const azureCLIDefaultPath = "/bin:/sbin:/usr/bin:/usr/local/bin" + + // Execute Azure CLI to get token + var cliCmd *exec.Cmd + if runtime.GOOS == "windows" { + cliCmd = exec.Command(fmt.Sprintf("%s\\system32\\cmd.exe", os.Getenv("windir"))) + cliCmd.Env = os.Environ() + cliCmd.Env = append(cliCmd.Env, fmt.Sprintf("PATH=%s;%s", os.Getenv(azureCLIPath), azureCLIDefaultPathWindows)) + cliCmd.Args = append(cliCmd.Args, "/c", "az") + } else { + cliCmd = exec.Command("az") + cliCmd.Env = os.Environ() + cliCmd.Env = append(cliCmd.Env, fmt.Sprintf("PATH=%s:%s", os.Getenv(azureCLIPath), azureCLIDefaultPath)) + } + cliCmd.Args = append(cliCmd.Args, "account", "get-access-token", "--resource-type=ms-graph", "-o", "json") + + var stderr bytes.Buffer + cliCmd.Stderr = &stderr + + output, err := cliCmd.Output() + if err != nil { + return "", fmt.Errorf("Invoking Azure CLI failed with the following error: %v", err) + } + + var tokenResponse struct { + AccessToken string `json:"accessToken"` + ExpiresOn string `json:"expiresOn"` + Tenant string `json:"tenant"` + TokenType string `json:"tokenType"` + } + err = json.Unmarshal(output, &tokenResponse) + if err != nil { + return "", err + } + + return tokenResponse.Tenant, nil +} diff --git a/azuread/table_azuread_application.go b/azuread/table_azuread_application.go index b2e0742..ab00878 100644 --- a/azuread/table_azuread_application.go +++ b/azuread/table_azuread_application.go @@ -81,7 +81,8 @@ func listAdApplications(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydr // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_application.listAdApplications", "connection_error", err) + return nil, err } // List operations @@ -119,6 +120,7 @@ func listAdApplications(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydr result, err := client.Applications().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("listAdApplications", "list_application_error", errObj) return nil, errObj } @@ -139,6 +141,7 @@ func listAdApplications(ctx context.Context, d *plugin.QueryData, _ *plugin.Hydr return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { + plugin.Logger(ctx).Error("listAdApplications", "paging_error", err) return nil, err } @@ -157,12 +160,14 @@ func getAdApplication(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_application.getAdApplication", "connection_error", err) + return nil, err } application, err := client.ApplicationsById(applicationId).Get() if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdApplication", "get_application_error", errObj) return nil, errObj } isAuthorizationServiceEnabled := application.GetAdditionalData()["isAuthorizationServiceEnabled"] @@ -174,7 +179,8 @@ func getAdApplicationOwners(ctx context.Context, d *plugin.QueryData, h *plugin. // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_application.getAdApplicationOwners", "connection_error", err) + return nil, err } application := h.Item.(*ADApplicationInfo) @@ -198,6 +204,7 @@ func getAdApplicationOwners(ctx context.Context, d *plugin.QueryData, h *plugin. owners, err := client.ApplicationsById(*applicationID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdApplicationOwners", "get_application_owners_error", errObj) return nil, errObj } diff --git a/azuread/table_azuread_conditional_access_policy.go b/azuread/table_azuread_conditional_access_policy.go index 3c8f4a4..b2eb1bb 100644 --- a/azuread/table_azuread_conditional_access_policy.go +++ b/azuread/table_azuread_conditional_access_policy.go @@ -77,7 +77,8 @@ func listAdConditionalAccessPolicies(ctx context.Context, d *plugin.QueryData, _ // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_conditional_access_policy.listAdConditionalAccessPolicies", "connection_error", err) + return nil, err } // List operations @@ -108,6 +109,7 @@ func listAdConditionalAccessPolicies(ctx context.Context, d *plugin.QueryData, _ result, err := client.Identity().ConditionalAccess().Policies().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("listAdConditionalAccessPolicies", "list_conditional_access_policy_error", errObj) return nil, errObj } @@ -126,6 +128,7 @@ func listAdConditionalAccessPolicies(ctx context.Context, d *plugin.QueryData, _ return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { + plugin.Logger(ctx).Error("listAdConditionalAccessPolicies", "paging_error", err) return nil, err } @@ -144,12 +147,14 @@ func getAdConditionalAccessPolicy(ctx context.Context, d *plugin.QueryData, h *p // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_conditional_access_policy.getAdConditionalAccessPolicy", "connection_error", err) + return nil, err } policy, err := client.Identity().ConditionalAccess().PoliciesById(conditionalAccessPolicyId).Get() if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdConditionalAccessPolicy", "get_conditional_access_policy_error", errObj) return nil, errObj } return &ADConditionalAccessPolicyInfo{policy}, nil diff --git a/azuread/table_azuread_directory_role.go b/azuread/table_azuread_directory_role.go index ea85437..ea7dbc5 100644 --- a/azuread/table_azuread_directory_role.go +++ b/azuread/table_azuread_directory_role.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "fmt" msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" "github.com/microsoftgraph/msgraph-sdk-go/directoryroles/item/members" @@ -58,12 +57,14 @@ func listAdDirectoryRoles(ctx context.Context, d *plugin.QueryData, _ *plugin.Hy // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_directory_role.listAdDirectoryRoles", "connection_error", err) + return nil, err } result, err := client.DirectoryRoles().Get() if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("listAdDirectoryRoles", "list_directory_role_error", errObj) return nil, errObj } @@ -90,12 +91,14 @@ func getAdDirectoryRole(ctx context.Context, d *plugin.QueryData, h *plugin.Hydr // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_directory_role.getAdDirectoryRole", "connection_error", err) + return nil, err } directoryRole, err := client.DirectoryRolesById(directoryRoleId).Get() if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdDirectoryRole", "get_directory_role_error", errObj) return nil, errObj } @@ -106,7 +109,8 @@ func getDirectoryRoleMembers(ctx context.Context, d *plugin.QueryData, h *plugin // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_directory_role.getDirectoryRoleMembers", "connection_error", err) + return nil, err } directoryRole := h.Item.(*ADDirectoryRoleInfo) @@ -130,6 +134,7 @@ func getDirectoryRoleMembers(ctx context.Context, d *plugin.QueryData, h *plugin members, err := client.DirectoryRolesById(*directoryRoleID).Members().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getDirectoryRoleMembers", "get_directory_role_members_error", errObj) return nil, errObj } @@ -146,6 +151,7 @@ func getDirectoryRoleMembers(ctx context.Context, d *plugin.QueryData, h *plugin return true }) if err != nil { + plugin.Logger(ctx).Error("getDirectoryRoleMembers", "paging_error", err) return nil, err } diff --git a/azuread/table_azuread_domain.go b/azuread/table_azuread_domain.go index e96f2f8..58a9b90 100644 --- a/azuread/table_azuread_domain.go +++ b/azuread/table_azuread_domain.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "fmt" "github.com/turbot/steampipe-plugin-sdk/v3/grpc/proto" "github.com/turbot/steampipe-plugin-sdk/v3/plugin/transform" @@ -62,7 +61,8 @@ func listAdDomains(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDa // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_domain.listAdDomains", "connection_error", err) + return nil, err } // List operations @@ -85,6 +85,7 @@ func listAdDomains(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDa result, err := client.Domains().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("listAdDomains", "list_domain_error", errObj) return nil, errObj } @@ -103,6 +104,7 @@ func listAdDomains(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDa return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { + plugin.Logger(ctx).Error("listAdDomains", "paging_error", err) return nil, err } @@ -120,12 +122,14 @@ func getAdDomain(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_domain.getAdDomain", "connection_error", err) + return nil, err } domain, err := client.DomainsById(domainId).Get() if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdDomain", "get_domain_error", errObj) return nil, errObj } diff --git a/azuread/table_azuread_group.go b/azuread/table_azuread_group.go index 8b964e3..d63ec68 100644 --- a/azuread/table_azuread_group.go +++ b/azuread/table_azuread_group.go @@ -113,7 +113,8 @@ func listAdGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDat // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_group.listAdGroups", "connection_error", err) + return nil, err } // List operations @@ -154,6 +155,7 @@ func listAdGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDat result, err := client.Groups().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("listAdGroups", "list_group_error", errObj) return nil, errObj } @@ -175,6 +177,7 @@ func listAdGroups(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateDat return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { + plugin.Logger(ctx).Error("listAdGroups", "paging_error", err) return nil, err } @@ -193,7 +196,8 @@ func getAdGroup(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_group.getAdGroup", "connection_error", err) + return nil, err } input := &item.GroupItemRequestBuilderGetQueryParameters{} @@ -205,6 +209,7 @@ func getAdGroup(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) group, err := client.GroupsById(groupId).GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdGroup", "get_group_error", errObj) return nil, errObj } resourceBehaviorOptions := formatResourceBehaviorOptions(ctx, group) @@ -217,7 +222,8 @@ func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.Hydra // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_group.getAdGroupMembers", "connection_error", err) + return nil, err } group := h.Item.(*ADGroupInfo) @@ -241,6 +247,7 @@ func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.Hydra members, err := client.GroupsById(*groupID).Members().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdGroupMembers", "get_group_members_error", errObj) return nil, errObj } @@ -257,6 +264,7 @@ func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.Hydra return true }) if err != nil { + plugin.Logger(ctx).Error("getAdGroupMembers", "paging_error", err) return nil, err } @@ -267,7 +275,8 @@ func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_group.getAdGroupOwners", "connection_error", err) + return nil, err } group := h.Item.(*ADGroupInfo) @@ -291,6 +300,7 @@ func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat owners, err := client.GroupsById(*groupID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdGroupOwners", "get_group_owners_error", errObj) return nil, errObj } @@ -307,6 +317,7 @@ func getAdGroupOwners(ctx context.Context, d *plugin.QueryData, h *plugin.Hydrat return true }) if err != nil { + plugin.Logger(ctx).Error("getAdGroupMembers", "paging_error", err) return nil, err } diff --git a/azuread/table_azuread_identity_provider.go b/azuread/table_azuread_identity_provider.go index 07dc3d5..ac6a89e 100644 --- a/azuread/table_azuread_identity_provider.go +++ b/azuread/table_azuread_identity_provider.go @@ -58,7 +58,8 @@ func listAdIdentityProviders(ctx context.Context, d *plugin.QueryData, _ *plugin // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_identity_provider.listAdIdentityProviders", "connection_error", err) + return nil, err } // List operations @@ -92,6 +93,7 @@ func listAdIdentityProviders(ctx context.Context, d *plugin.QueryData, _ *plugin result, err := client.Identity().IdentityProviders().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("listAdIdentityProviders", "list_identity_provider_error", errObj) return nil, errObj } @@ -113,6 +115,7 @@ func listAdIdentityProviders(ctx context.Context, d *plugin.QueryData, _ *plugin return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { + plugin.Logger(ctx).Error("listAdIdentityProviders", "paging_error", err) return nil, err } diff --git a/azuread/table_azuread_service_principal.go b/azuread/table_azuread_service_principal.go index 88250ad..8c27873 100644 --- a/azuread/table_azuread_service_principal.go +++ b/azuread/table_azuread_service_principal.go @@ -87,7 +87,8 @@ func listAdServicePrincipals(ctx context.Context, d *plugin.QueryData, _ *plugin // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_service_principal.listAdServicePrincipals", "connection_error", err) + return nil, err } // List operations @@ -127,6 +128,7 @@ func listAdServicePrincipals(ctx context.Context, d *plugin.QueryData, _ *plugin result, err := client.ServicePrincipals().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("listAdServicePrincipals", "list_service_principal_error", errObj) return nil, errObj } @@ -145,6 +147,7 @@ func listAdServicePrincipals(ctx context.Context, d *plugin.QueryData, _ *plugin return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { + plugin.Logger(ctx).Error("listAdServicePrincipals", "paging_error", err) return nil, err } @@ -163,12 +166,14 @@ func getAdServicePrincipal(ctx context.Context, d *plugin.QueryData, h *plugin.H // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_service_principal.getAdServicePrincipal", "connection_error", err) + return nil, err } servicePrincipal, err := client.ServicePrincipalsById(servicePrincipalID).Get() if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdServicePrincipal", "get_service_principal_error", errObj) return nil, errObj } @@ -179,7 +184,8 @@ func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plug // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_service_principal.getServicePrincipalOwners", "connection_error", err) + return nil, err } servicePrincipal := h.Item.(*ADServicePrincipalInfo) @@ -207,6 +213,7 @@ func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plug owners, err := client.ServicePrincipalsById(*servicePrincipalID).Owners().GetWithRequestConfigurationAndResponseHandler(config, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getServicePrincipalOwners", "get_service_principal_owners_error", errObj) return nil, errObj } @@ -223,6 +230,7 @@ func getServicePrincipalOwners(ctx context.Context, d *plugin.QueryData, h *plug return true }) if err != nil { + plugin.Logger(ctx).Error("getServicePrincipalOwners", "paging_error", err) return nil, err } diff --git a/azuread/table_azuread_sign_in_report.go b/azuread/table_azuread_sign_in_report.go index 9f59ef2..b7ce223 100644 --- a/azuread/table_azuread_sign_in_report.go +++ b/azuread/table_azuread_sign_in_report.go @@ -2,7 +2,6 @@ package azuread import ( "context" - "fmt" msgraphcore "github.com/microsoftgraph/msgraph-sdk-go-core" "github.com/microsoftgraph/msgraph-sdk-go/auditlogs/signins" @@ -70,7 +69,8 @@ func listAdSignInReports(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_sign_in_report.listAdSignInReports", "connection_error", err) + return nil, err } // List operations @@ -93,6 +93,7 @@ func listAdSignInReports(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd result, err := client.AuditLogs().SignIns().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("listAdSignInReports", "list_sign_in_report_error", errObj) return nil, errObj } @@ -111,6 +112,7 @@ func listAdSignInReports(ctx context.Context, d *plugin.QueryData, _ *plugin.Hyd return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { + plugin.Logger(ctx).Error("listAdSignInReports", "paging_error", err) return nil, err } @@ -128,12 +130,14 @@ func getAdSignInReport(ctx context.Context, d *plugin.QueryData, h *plugin.Hydra // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_sign_in_report.getAdSignInReport", "connection_error", err) + return nil, err } signIn, err := client.AuditLogs().SignInsById(signInID).Get() if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdSignInReport", "get_sign_in_report_error", errObj) return nil, errObj } diff --git a/azuread/table_azuread_user.go b/azuread/table_azuread_user.go index 597cdd1..253dd6c 100644 --- a/azuread/table_azuread_user.go +++ b/azuread/table_azuread_user.go @@ -61,7 +61,6 @@ func tableAzureAdUser() *plugin.Table { {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the user, for example, jeff@contoso.onmicrosoft.com.", Transform: transform.FromMethod("GetMail")}, {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user.", Transform: transform.FromMethod("GetMailNickname")}, {Name: "password_policies", Type: proto.ColumnType_STRING, Description: "Specifies password policies for the user. This value is an enumeration with one possible value being DisableStrongPassword, which allows weaker passwords than the default policy to be specified. DisablePasswordExpiration can also be specified. The two may be specified together; for example: DisablePasswordExpiration, DisableStrongPassword.", Transform: transform.FromMethod("GetPasswordPolicies")}, - {Name: "refresh_tokens_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph)."}, {Name: "sign_in_sessions_valid_from_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Any refresh tokens or sessions tokens (session cookies) issued before this time are invalid, and applications will get an error when using an invalid refresh or sessions token to acquire a delegated access token (to access APIs such as Microsoft Graph).", Transform: transform.FromMethod("GetSignInSessionsValidFromDateTime")}, {Name: "usage_location", Type: proto.ColumnType_STRING, Description: "A two letter country code (ISO standard 3166), required for users that will be assigned licenses due to legal requirement to check for availability of services in countries.", Transform: transform.FromMethod("GetUsageLocation")}, @@ -84,7 +83,8 @@ func listAdUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData // Create client client, adapter, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_user.listAdUsers", "connection_error", err) + return nil, err } // List operations @@ -131,6 +131,7 @@ func listAdUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData result, err := client.Users().GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("listAdUsers", "list_user_error", errObj) return nil, errObj } @@ -151,6 +152,7 @@ func listAdUsers(ctx context.Context, d *plugin.QueryData, _ *plugin.HydrateData return d.QueryStatus.RowsRemaining(ctx) != 0 }) if err != nil { + plugin.Logger(ctx).Error("listAdUsers", "paging_error", err) return nil, err } @@ -164,7 +166,8 @@ func getAdUser(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) // Create client client, _, err := GetGraphClient(ctx, d) if err != nil { - return nil, fmt.Errorf("error creating client: %v", err) + plugin.Logger(ctx).Error("azuread_user.getAdUser", "connection_error", err) + return nil, err } userId := d.KeyColumnQuals["id"].GetStringValue() @@ -187,6 +190,7 @@ func getAdUser(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) user, err := client.UsersById(userId).GetWithRequestConfigurationAndResponseHandler(options, nil) if err != nil { errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdUser", "get_user_error", errObj) return nil, errObj } refreshTokensValidFromDateTime := user.GetAdditionalData()["refreshTokensValidFromDateTime"] diff --git a/go.mod b/go.mod index 1b35977..596589f 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 github.com/iancoleman/strcase v0.2.0 - github.com/manicminer/hamilton v0.24.0 github.com/microsoft/kiota-authentication-azure-go v0.3.1 github.com/microsoft/kiota-serialization-json-go v0.5.5 github.com/microsoftgraph/msgraph-sdk-go v0.30.0 @@ -39,7 +38,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/hashicorp/go-hclog v1.2.0 // indirect github.com/hashicorp/go-plugin v1.4.4 // indirect - github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/go-version v1.5.0 // indirect github.com/hashicorp/hcl/v2 v2.12.0 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect @@ -79,10 +77,8 @@ require ( go.opentelemetry.io/proto/otlp v0.16.0 // indirect golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 // indirect golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect - golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect golang.org/x/text v0.3.7 // indirect - google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac // indirect google.golang.org/grpc v1.46.0 // indirect google.golang.org/protobuf v1.28.0 // indirect diff --git a/go.sum b/go.sum index 33fead8..33804c7 100644 --- a/go.sum +++ b/go.sum @@ -180,17 +180,10 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= -github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -214,8 +207,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/manicminer/hamilton v0.24.0 h1:KUa8+NYP61esmb6QKGPy7t4zrxB0sujhWDRhiKpoScE= -github.com/manicminer/hamilton v0.24.0/go.mod h1:QryxpD/4+cdKuXNi0UjLDvgxYdP0LLmYz7dYU7DAX4U= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= @@ -338,7 +329,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 h1:Tgea0cVUD0ivh5ADBX4WwuI12DUd2to3nCYe2eayMIw= golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -400,9 +390,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -410,8 +398,6 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -456,7 +442,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -470,7 +455,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -544,8 +528,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= From 6c63c275f63c48ad619dafee5deab87b96b927ac Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Mon, 8 Aug 2022 22:02:05 +0530 Subject: [PATCH 16/21] Rename helpers.go => transforms.go --- azuread/table_azuread_group.go | 55 +++++++++++++++++++-------- azuread/{helpers.go => transforms.go} | 0 2 files changed, 40 insertions(+), 15 deletions(-) rename azuread/{helpers.go => transforms.go} (100%) diff --git a/azuread/table_azuread_group.go b/azuread/table_azuread_group.go index d63ec68..099248b 100644 --- a/azuread/table_azuread_group.go +++ b/azuread/table_azuread_group.go @@ -59,21 +59,7 @@ func tableAzureAdGroup() *plugin.Table { {Name: "created_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "The time at which the group was created.", Transform: transform.FromMethod("GetCreatedDateTime")}, {Name: "expiration_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Timestamp of when the group is set to expire.", Transform: transform.FromMethod("GetExpirationDateTime")}, {Name: "is_assignable_to_role", Type: proto.ColumnType_BOOL, Description: "Indicates whether this group can be assigned to an Azure Active Directory role or not.", Transform: transform.FromMethod("GetIsAssignableToRole")}, - - // Getting below error while requesting value for isSubscribedByMail - // { - // "error": { - // "code": "ErrorInvalidGroup", - // "message": "The requested group '[id@tenantId]' is invalid.", - // "innerError": { - // "date": "2022-07-13T11:06:23", - // "request-id": "63a83d86-a007-4c68-be75-21cea61d830e", - // "client-request-id": "d69d6667-e818-a322-c694-1fec40b438a8" - // } - // } - // } - // {Name: "is_subscribed_by_mail", Type: proto.ColumnType_BOOL, Description: "Indicates whether the signed-in user is subscribed to receive email conversations. Default value is true.", Transform: transform.FromMethod("GetIsSubscribedByMail")}, - + {Name: "is_subscribed_by_mail", Type: proto.ColumnType_BOOL, Description: "Indicates whether the signed-in user is subscribed to receive email conversations. Default value is true.", Hydrate: getAdGroupIsSubscribedByMail, Transform: transform.FromValue()}, {Name: "mail", Type: proto.ColumnType_STRING, Description: "The SMTP address for the group, for example, \"serviceadmins@contoso.onmicrosoft.com\".", Transform: transform.FromMethod("GetMail")}, {Name: "mail_enabled", Type: proto.ColumnType_BOOL, Description: "Specifies whether the group is mail-enabled.", Transform: transform.FromMethod("GetMailEnabled")}, {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user.", Transform: transform.FromMethod("GetMailNickname")}, @@ -218,6 +204,45 @@ func getAdGroup(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) return &ADGroupInfo{group, resourceBehaviorOptions, resourceProvisioningOptions}, nil } +// Returned only on $select. Supported only on the Get group API (GET /groups/{ID}). + +func getAdGroupIsSubscribedByMail(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { + + var groupId string + if h.Item != nil { + groupId = *h.Item.(*ADGroupInfo).GetId() + } else { + groupId = d.KeyColumnQuals["id"].GetStringValue() + } + if groupId == "" { + return nil, nil + } + + // Create client + client, _, err := GetGraphClient(ctx, d) + if err != nil { + plugin.Logger(ctx).Error("azuread_group.getAdGroupIsSubscribedByMail", "connection_error", err) + return nil, err + } + + input := &item.GroupItemRequestBuilderGetQueryParameters{ + Select: []string{"isSubscribedByMail"}, + } + + options := &item.GroupItemRequestBuilderGetRequestConfiguration{ + QueryParameters: input, + } + + group, err := client.GroupsById(groupId).GetWithRequestConfigurationAndResponseHandler(options, nil) + if err != nil { + errObj := getErrorObject(err) + plugin.Logger(ctx).Error("getAdGroupIsSubscribedByMail", "get_group_error", errObj) + return nil, nil + } + + return group.GetIsSubscribedByMail(), nil +} + func getAdGroupMembers(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) { // Create client client, adapter, err := GetGraphClient(ctx, d) diff --git a/azuread/helpers.go b/azuread/transforms.go similarity index 100% rename from azuread/helpers.go rename to azuread/transforms.go From 8bf0daf7a9bcbb37987ba51346fc99ab045c32ab Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Tue, 9 Aug 2022 12:58:22 +0530 Subject: [PATCH 17/21] Update group table to return [] for resourceBehaviorOptions and resourceProvisioningOptions --- azuread/table_azuread_group.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azuread/table_azuread_group.go b/azuread/table_azuread_group.go index 099248b..0581ca7 100644 --- a/azuread/table_azuread_group.go +++ b/azuread/table_azuread_group.go @@ -436,7 +436,7 @@ func buildGroupBoolNEFilter(quals plugin.KeyColumnQualMap) []string { } func formatResourceBehaviorOptions(ctx context.Context, group models.Groupable) []string { - var resourceBehaviorOptions []string + resourceBehaviorOptions := []string{} data := group.GetAdditionalData()["resourceBehaviorOptions"] if data != nil { parsedData := group.GetAdditionalData()["resourceBehaviorOptions"].([]*jsonserialization.JsonParseNode) @@ -457,7 +457,7 @@ func formatResourceBehaviorOptions(ctx context.Context, group models.Groupable) } func formatResourceProvisioningOptions(ctx context.Context, group models.Groupable) []string { - var resourceProvisioningOptions []string + resourceProvisioningOptions := []string{} data := group.GetAdditionalData()["resourceProvisioningOptions"] if data != nil { parsedData := data.([]*jsonserialization.JsonParseNode) From 595a99702b3553bd3ce887c33136e1c7a26c2798 Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Tue, 9 Aug 2022 17:05:21 +0530 Subject: [PATCH 18/21] update --- azuread/table_azuread_group.go | 36 +++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/azuread/table_azuread_group.go b/azuread/table_azuread_group.go index 0581ca7..fe829b4 100644 --- a/azuread/table_azuread_group.go +++ b/azuread/table_azuread_group.go @@ -65,7 +65,7 @@ func tableAzureAdGroup() *plugin.Table { {Name: "mail_nickname", Type: proto.ColumnType_STRING, Description: "The mail alias for the user.", Transform: transform.FromMethod("GetMailNickname")}, {Name: "membership_rule", Type: proto.ColumnType_STRING, Description: "The mail alias for the group, unique in the organization.", Transform: transform.FromMethod("GetMembershipRule")}, {Name: "membership_rule_processing_state", Type: proto.ColumnType_STRING, Description: "Indicates whether the dynamic membership processing is on or paused. Possible values are On or Paused.", Transform: transform.FromMethod("GetMembershipRuleProcessingState")}, - {Name: "on_premises_domain_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises Domanin name synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesDomainName")}, + {Name: "on_premises_domain_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises Domain name synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesDomainName")}, {Name: "on_premises_last_sync_date_time", Type: proto.ColumnType_TIMESTAMP, Description: "Indicates the last time at which the group was synced with the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesLastSyncDateTime")}, {Name: "on_premises_net_bios_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises NetBiosName synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesNetBiosName")}, {Name: "on_premises_sam_account_name", Type: proto.ColumnType_STRING, Description: "Contains the on-premises SAM account name synchronized from the on-premises directory.", Transform: transform.FromMethod("GetOnPremisesSamAccountName")}, @@ -82,8 +82,8 @@ func tableAzureAdGroup() *plugin.Table { {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupMembers, Transform: transform.FromValue(), Description: "Id of Users and groups that are members of this group."}, {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupOwners, Transform: transform.FromValue(), Description: "Id od the owners of the group. The owners are a set of non-admin users who are allowed to modify this object."}, {Name: "proxy_addresses", Type: proto.ColumnType_JSON, Description: "Email addresses for the group that direct to the same group mailbox. For example: [\"SMTP: bob@contoso.com\", \"smtp: bob@sales.contoso.com\"]. The any operator is required to filter expressions on multi-valued properties.", Transform: transform.FromMethod("GetProxyAddresses")}, - {Name: "resource_behavior_options", Type: proto.ColumnType_JSON, Description: "Specifies the group behaviors that can be set for a Microsoft 365 group during creation. Possible values are AllowOnlyMembersToPost, HideGroupInOutlook, SubscribeNewGroupMembers, WelcomeEmailDisabled."}, - {Name: "resource_provisioning_options", Type: proto.ColumnType_JSON, Description: "Specifies the group resources that are provisioned as part of Microsoft 365 group creation, that are not normally part of default group creation. Possible value is Team."}, + {Name: "resource_behavior_options", Type: proto.ColumnType_JSON, Description: "Specifies the group behaviors that can be set for a Microsoft 365 group during creation. Possible values are AllowOnlyMembersToPost, HideGroupInOutlook, SubscribeNewGroupMembers, WelcomeEmailDisabled.", Transform: transform.From(adGroupResourceBehaviorOptions)}, + {Name: "resource_provisioning_options", Type: proto.ColumnType_JSON, Description: "Specifies the group resources that are provisioned as part of Microsoft 365 group creation, that are not normally part of default group creation. Possible value is Team.", Transform: transform.From(adGroupResourceProvisioningOptions)}, // Standard columns {Name: "tags", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTags, Transform: transform.From(adGroupTags)}, @@ -384,6 +384,32 @@ func adGroupTitle(_ context.Context, d *transform.TransformData) (interface{}, e return title, nil } +func adGroupResourceBehaviorOptions(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADGroupInfo) + if data == nil { + return nil, nil + } + + if data.ResourceBehaviorOptions == nil || len(data.ResourceBehaviorOptions) == 0 { + return []string{}, nil + } + + return data.ResourceBehaviorOptions, nil +} + +func adGroupResourceProvisioningOptions(_ context.Context, d *transform.TransformData) (interface{}, error) { + data := d.HydrateItem.(*ADGroupInfo) + if data == nil { + return nil, nil + } + + if data.ResourceProvisioningOptions == nil || len(data.ResourceProvisioningOptions) == 0 { + return []string{}, nil + } + + return data.ResourceProvisioningOptions, nil +} + func buildGroupQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { filters := []string{} @@ -436,7 +462,7 @@ func buildGroupBoolNEFilter(quals plugin.KeyColumnQualMap) []string { } func formatResourceBehaviorOptions(ctx context.Context, group models.Groupable) []string { - resourceBehaviorOptions := []string{} + var resourceBehaviorOptions []string data := group.GetAdditionalData()["resourceBehaviorOptions"] if data != nil { parsedData := group.GetAdditionalData()["resourceBehaviorOptions"].([]*jsonserialization.JsonParseNode) @@ -457,7 +483,7 @@ func formatResourceBehaviorOptions(ctx context.Context, group models.Groupable) } func formatResourceProvisioningOptions(ctx context.Context, group models.Groupable) []string { - resourceProvisioningOptions := []string{} + var resourceProvisioningOptions []string data := group.GetAdditionalData()["resourceProvisioningOptions"] if data != nil { parsedData := data.([]*jsonserialization.JsonParseNode) From 7b32f77ad18dbb9df481d3594799084f2292dbf9 Mon Sep 17 00:00:00 2001 From: Subhajit Mondal Date: Tue, 9 Aug 2022 18:55:32 +0530 Subject: [PATCH 19/21] Fix lint error --- azuread/table_azuread_group.go | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/azuread/table_azuread_group.go b/azuread/table_azuread_group.go index fe829b4..00d29e9 100644 --- a/azuread/table_azuread_group.go +++ b/azuread/table_azuread_group.go @@ -82,8 +82,8 @@ func tableAzureAdGroup() *plugin.Table { {Name: "member_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupMembers, Transform: transform.FromValue(), Description: "Id of Users and groups that are members of this group."}, {Name: "owner_ids", Type: proto.ColumnType_JSON, Hydrate: getAdGroupOwners, Transform: transform.FromValue(), Description: "Id od the owners of the group. The owners are a set of non-admin users who are allowed to modify this object."}, {Name: "proxy_addresses", Type: proto.ColumnType_JSON, Description: "Email addresses for the group that direct to the same group mailbox. For example: [\"SMTP: bob@contoso.com\", \"smtp: bob@sales.contoso.com\"]. The any operator is required to filter expressions on multi-valued properties.", Transform: transform.FromMethod("GetProxyAddresses")}, - {Name: "resource_behavior_options", Type: proto.ColumnType_JSON, Description: "Specifies the group behaviors that can be set for a Microsoft 365 group during creation. Possible values are AllowOnlyMembersToPost, HideGroupInOutlook, SubscribeNewGroupMembers, WelcomeEmailDisabled.", Transform: transform.From(adGroupResourceBehaviorOptions)}, - {Name: "resource_provisioning_options", Type: proto.ColumnType_JSON, Description: "Specifies the group resources that are provisioned as part of Microsoft 365 group creation, that are not normally part of default group creation. Possible value is Team.", Transform: transform.From(adGroupResourceProvisioningOptions)}, + {Name: "resource_behavior_options", Type: proto.ColumnType_JSON, Description: "Specifies the group behaviors that can be set for a Microsoft 365 group during creation. Possible values are AllowOnlyMembersToPost, HideGroupInOutlook, SubscribeNewGroupMembers, WelcomeEmailDisabled."}, + {Name: "resource_provisioning_options", Type: proto.ColumnType_JSON, Description: "Specifies the group resources that are provisioned as part of Microsoft 365 group creation, that are not normally part of default group creation. Possible value is Team."}, // Standard columns {Name: "tags", Type: proto.ColumnType_STRING, Description: ColumnDescriptionTags, Transform: transform.From(adGroupTags)}, @@ -384,32 +384,6 @@ func adGroupTitle(_ context.Context, d *transform.TransformData) (interface{}, e return title, nil } -func adGroupResourceBehaviorOptions(_ context.Context, d *transform.TransformData) (interface{}, error) { - data := d.HydrateItem.(*ADGroupInfo) - if data == nil { - return nil, nil - } - - if data.ResourceBehaviorOptions == nil || len(data.ResourceBehaviorOptions) == 0 { - return []string{}, nil - } - - return data.ResourceBehaviorOptions, nil -} - -func adGroupResourceProvisioningOptions(_ context.Context, d *transform.TransformData) (interface{}, error) { - data := d.HydrateItem.(*ADGroupInfo) - if data == nil { - return nil, nil - } - - if data.ResourceProvisioningOptions == nil || len(data.ResourceProvisioningOptions) == 0 { - return []string{}, nil - } - - return data.ResourceProvisioningOptions, nil -} - func buildGroupQueryFilter(equalQuals plugin.KeyColumnEqualsQualMap) []string { filters := []string{} From 724175684876d2c1fe999b0b5c708c9ef6196056 Mon Sep 17 00:00:00 2001 From: cbruno10 Date: Tue, 9 Aug 2022 14:50:14 -0400 Subject: [PATCH 20/21] Bump timeout in golangci-lint to 15 minutes --- .github/workflows/golangci-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 8bd76ef..24f4c0b 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -21,4 +21,4 @@ jobs: with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version version: latest - args: --timeout=10m \ No newline at end of file + args: --timeout=15m From 3522d9e3cc41b33e48f142ab66126159b673b23c Mon Sep 17 00:00:00 2001 From: Cody Bruno Date: Tue, 9 Aug 2022 15:49:07 -0400 Subject: [PATCH 21/21] Revert timeout in golangci-lint to 10 minutes --- .github/workflows/golangci-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 24f4c0b..e9a2ac2 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -21,4 +21,4 @@ jobs: with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version version: latest - args: --timeout=15m + args: --timeout=10m