From 517041612403490518c0e3bd3fe1e5cb607a38cb Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Sat, 12 Dec 2020 14:07:49 +0000 Subject: [PATCH] Restructure resources into service packages - Provider and Client refactoring to accommodate additional API clients - Split aadgraph service package into packages by object type - Move AAD Graph helpers outside service package - Create parse package per service - Some schema deprecations in preparation for next major release --- internal/acceptance/check/that.go | 2 +- .../acceptance/helpers/check_destroyed.go | 2 +- internal/acceptance/helpers/delete.go | 2 +- internal/acceptance/helpers/exists.go | 2 +- internal/acceptance/testcase.go | 4 +- internal/acceptance/types/resource.go | 4 +- internal/clients/builder.go | 36 +- internal/clients/client.go | 33 +- .../client_options.go} | 5 +- .../graph => helpers/aadgraph}/application.go | 215 ++---------- .../graph => helpers/aadgraph}/credentials.go | 57 +--- .../graph => helpers/aadgraph}/errors.go | 2 +- .../graph => helpers/aadgraph}/group.go | 29 +- .../graph => helpers/aadgraph}/odata.go | 2 +- .../graph => helpers/aadgraph}/replication.go | 2 +- .../graph => helpers/aadgraph}/user.go | 2 +- internal/provider/provider.go | 11 +- internal/provider/services.go | 19 ++ .../aadgraph/application_data_source_test.go | 118 ------- internal/services/aadgraph/client/client.go | 41 --- .../application_app_role_resource.go | 34 +- .../application_app_role_resource_test.go | 13 +- .../application_certificate_resource.go | 35 +- .../application_certificate_resource_test.go | 15 +- .../application_data_source.go | 196 ++++++++--- .../application_data_source_test.go | 118 +++++++ .../application_oauth2_permission_resource.go | 34 +- ...ication_oauth2_permission_resource_test.go | 13 +- .../application_password_resource.go | 37 ++- .../application_password_resource_test.go | 15 +- .../application_resource.go | 307 +++++++++--------- .../application_resource_test.go | 10 +- .../services/applications/client/client.go | 19 ++ .../services/applications/parse/app_role.go | 31 ++ .../applications/parse/credentials.go | 60 ++++ .../applications/parse/oauth2_permission.go | 31 ++ .../parse/object.go} | 20 +- .../registration.go} | 21 +- internal/services/applications/schema.go | 51 +++ internal/services/domains/client/client.go | 19 ++ .../domains_data_source.go | 10 +- .../domains_data_source_test.go | 2 +- internal/services/domains/registration.go | 31 ++ internal/services/groups/client/client.go | 19 ++ .../{aadgraph => groups}/group_data_source.go | 46 ++- .../group_data_source_test.go | 27 +- .../group_member_resource.go | 32 +- .../group_member_resource_test.go | 56 +++- .../{aadgraph => groups}/group_resource.go | 75 +++-- .../group_resource_test.go | 102 +++--- .../groups_data_source.go | 61 ++-- .../groups_data_source_test.go | 28 +- .../services/groups/parse/group_member.go | 30 ++ internal/services/groups/parse/object.go | 57 ++++ internal/services/groups/registration.go | 35 ++ .../serviceprincipals/client/client.go | 19 ++ .../client_config_data_source.go | 12 +- .../client_config_data_source_test.go | 2 +- .../serviceprincipals/parse/credentials.go | 60 ++++ .../serviceprincipals/parse/object.go | 57 ++++ .../serviceprincipals/registration.go | 36 ++ internal/services/serviceprincipals/schema.go | 99 ++++++ .../service_principal_certificate_resource.go | 35 +- ...ice_principal_certificate_resource_test.go | 15 +- .../service_principal_data_source.go | 14 +- .../service_principal_data_source_test.go | 2 +- .../service_principal_password_resource.go | 37 ++- ...ervice_principal_password_resource_test.go | 15 +- .../service_principal_resource.go | 24 +- .../service_principal_resource_test.go | 6 +- internal/services/users/client/client.go | 19 ++ internal/services/users/registration.go | 34 ++ .../{aadgraph => users}/user_data_source.go | 10 +- .../user_data_source_test.go | 18 +- .../{aadgraph => users}/user_resource.go | 14 +- .../{aadgraph => users}/user_resource_test.go | 82 ++--- .../{aadgraph => users}/users_data_source.go | 10 +- .../users_data_source_test.go | 2 +- website/docs/d/application.html.markdown | 8 +- website/docs/d/group.html.markdown | 10 +- website/docs/r/application.html.markdown | 8 +- website/docs/r/group.markdown | 6 +- 82 files changed, 1826 insertions(+), 1106 deletions(-) rename internal/{services/configure_client.go => common/client_options.go} (94%) rename internal/{services/aadgraph/graph => helpers/aadgraph}/application.go (76%) rename internal/{services/aadgraph/graph => helpers/aadgraph}/credentials.go (89%) rename internal/{services/aadgraph/graph => helpers/aadgraph}/errors.go (95%) rename internal/{services/aadgraph/graph => helpers/aadgraph}/group.go (91%) rename internal/{services/aadgraph/graph => helpers/aadgraph}/odata.go (98%) rename internal/{services/aadgraph/graph => helpers/aadgraph}/replication.go (99%) rename internal/{services/aadgraph/graph => helpers/aadgraph}/user.go (99%) create mode 100644 internal/provider/services.go delete mode 100644 internal/services/aadgraph/application_data_source_test.go delete mode 100644 internal/services/aadgraph/client/client.go rename internal/services/{aadgraph => applications}/application_app_role_resource.go (88%) rename internal/services/{aadgraph => applications}/application_app_role_resource_test.go (93%) rename internal/services/{aadgraph => applications}/application_certificate_resource.go (83%) rename internal/services/{aadgraph => applications}/application_certificate_resource_test.go (93%) rename internal/services/{aadgraph => applications}/application_data_source.go (64%) create mode 100644 internal/services/applications/application_data_source_test.go rename internal/services/{aadgraph => applications}/application_oauth2_permission_resource.go (88%) rename internal/services/{aadgraph => applications}/application_oauth2_permission_resource_test.go (94%) rename internal/services/{aadgraph => applications}/application_password_resource.go (86%) rename internal/services/{aadgraph => applications}/application_password_resource_test.go (91%) rename internal/services/{aadgraph => applications}/application_resource.go (83%) rename internal/services/{aadgraph => applications}/application_resource_test.go (98%) create mode 100644 internal/services/applications/client/client.go create mode 100644 internal/services/applications/parse/app_role.go create mode 100644 internal/services/applications/parse/credentials.go create mode 100644 internal/services/applications/parse/oauth2_permission.go rename internal/services/{aadgraph/graph/object_resource.go => applications/parse/object.go} (86%) rename internal/services/{aadgraph/0registration.go => applications/registration.go} (56%) create mode 100644 internal/services/applications/schema.go create mode 100644 internal/services/domains/client/client.go rename internal/services/{aadgraph => domains}/domains_data_source.go (93%) rename internal/services/{aadgraph => domains}/domains_data_source_test.go (99%) create mode 100644 internal/services/domains/registration.go create mode 100644 internal/services/groups/client/client.go rename internal/services/{aadgraph => groups}/group_data_source.go (70%) rename internal/services/{aadgraph => groups}/group_data_source_test.go (83%) rename internal/services/{aadgraph => groups}/group_member_resource.go (77%) rename internal/services/{aadgraph => groups}/group_member_resource_test.go (80%) rename internal/services/{aadgraph => groups}/group_resource.go (76%) rename internal/services/{aadgraph => groups}/group_resource_test.go (84%) rename internal/services/{aadgraph => groups}/groups_data_source.go (60%) rename internal/services/{aadgraph => groups}/groups_data_source_test.go (75%) create mode 100644 internal/services/groups/parse/group_member.go create mode 100644 internal/services/groups/parse/object.go create mode 100644 internal/services/groups/registration.go create mode 100644 internal/services/serviceprincipals/client/client.go rename internal/services/{aadgraph => serviceprincipals}/client_config_data_source.go (83%) rename internal/services/{aadgraph => serviceprincipals}/client_config_data_source_test.go (96%) create mode 100644 internal/services/serviceprincipals/parse/credentials.go create mode 100644 internal/services/serviceprincipals/parse/object.go create mode 100644 internal/services/serviceprincipals/registration.go create mode 100644 internal/services/serviceprincipals/schema.go rename internal/services/{aadgraph => serviceprincipals}/service_principal_certificate_resource.go (83%) rename internal/services/{aadgraph => serviceprincipals}/service_principal_certificate_resource_test.go (93%) rename internal/services/{aadgraph => serviceprincipals}/service_principal_data_source.go (90%) rename internal/services/{aadgraph => serviceprincipals}/service_principal_data_source_test.go (99%) rename internal/services/{aadgraph => serviceprincipals}/service_principal_password_resource.go (86%) rename internal/services/{aadgraph => serviceprincipals}/service_principal_password_resource_test.go (91%) rename internal/services/{aadgraph => serviceprincipals}/service_principal_resource.go (86%) rename internal/services/{aadgraph => serviceprincipals}/service_principal_resource_test.go (94%) create mode 100644 internal/services/users/client/client.go create mode 100644 internal/services/users/registration.go rename internal/services/{aadgraph => users}/user_data_source.go (97%) rename internal/services/{aadgraph => users}/user_data_source_test.go (94%) rename internal/services/{aadgraph => users}/user_resource.go (97%) rename internal/services/{aadgraph => users}/user_resource_test.go (68%) rename internal/services/{aadgraph => users}/users_data_source.go (96%) rename internal/services/{aadgraph => users}/users_data_source_test.go (99%) diff --git a/internal/acceptance/check/that.go b/internal/acceptance/check/that.go index ba1badf6b..88befc3e9 100644 --- a/internal/acceptance/check/that.go +++ b/internal/acceptance/check/that.go @@ -27,7 +27,7 @@ func That(resourceName string) thatType { // ExistsInAzure validates that the specified resource exists within Azure func (t thatType) ExistsInAzure(testResource types.TestResource) resource.TestCheckFunc { return func(s *terraform.State) error { - client := acceptance.AzureADProvider.Meta().(*clients.AadClient) + client := acceptance.AzureADProvider.Meta().(*clients.Client) return helpers.ExistsInAzure(client, testResource, t.resourceName)(s) } } diff --git a/internal/acceptance/helpers/check_destroyed.go b/internal/acceptance/helpers/check_destroyed.go index 72997397f..e786b8789 100644 --- a/internal/acceptance/helpers/check_destroyed.go +++ b/internal/acceptance/helpers/check_destroyed.go @@ -10,7 +10,7 @@ import ( ) // CheckDestroyedFunc returns a TestCheckFunc which validates the resource no longer exists -func CheckDestroyedFunc(client *clients.AadClient, testResource types.TestResource, resourceType, resourceLabel string) func(state *terraform.State) error { +func CheckDestroyedFunc(client *clients.Client, testResource types.TestResource, resourceType, resourceLabel string) func(state *terraform.State) error { return func(state *terraform.State) error { ctx := client.StopContext diff --git a/internal/acceptance/helpers/delete.go b/internal/acceptance/helpers/delete.go index fc9129986..2bdd0261f 100644 --- a/internal/acceptance/helpers/delete.go +++ b/internal/acceptance/helpers/delete.go @@ -11,7 +11,7 @@ import ( // DeleteResourceFunc returns a TestCheckFunc which deletes the resource within Azure // this is only used within the Internal -func DeleteResourceFunc(client *clients.AadClient, testResource types.TestResourceVerifyingRemoved, resourceName string) func(state *terraform.State) error { +func DeleteResourceFunc(client *clients.Client, testResource types.TestResourceVerifyingRemoved, resourceName string) func(state *terraform.State) error { return func(state *terraform.State) error { ctx := client.StopContext diff --git a/internal/acceptance/helpers/exists.go b/internal/acceptance/helpers/exists.go index 759d7c3b2..8e25bd50a 100644 --- a/internal/acceptance/helpers/exists.go +++ b/internal/acceptance/helpers/exists.go @@ -10,7 +10,7 @@ import ( "github.com/terraform-providers/terraform-provider-azuread/internal/clients" ) -func ExistsInAzure(client *clients.AadClient, testResource types.TestResource, resourceName string) resource.TestCheckFunc { +func ExistsInAzure(client *clients.Client, testResource types.TestResource, resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { ctx := client.StopContext diff --git a/internal/acceptance/testcase.go b/internal/acceptance/testcase.go index df4bb2ee3..ce9981930 100644 --- a/internal/acceptance/testcase.go +++ b/internal/acceptance/testcase.go @@ -62,6 +62,6 @@ func PreCheck(t *testing.T) { } } -func buildClient() *clients.AadClient { - return AzureADProvider.Meta().(*clients.AadClient) +func buildClient() *clients.Client { + return AzureADProvider.Meta().(*clients.Client) } diff --git a/internal/acceptance/types/resource.go b/internal/acceptance/types/resource.go index e9e8af1e0..72dc5b97e 100644 --- a/internal/acceptance/types/resource.go +++ b/internal/acceptance/types/resource.go @@ -9,10 +9,10 @@ import ( ) type TestResource interface { - Exists(ctx context.Context, client *clients.AadClient, state *terraform.InstanceState) (*bool, error) + Exists(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) } type TestResourceVerifyingRemoved interface { TestResource - Destroy(ctx context.Context, client *clients.AadClient, state *terraform.InstanceState) (*bool, error) + Destroy(ctx context.Context, client *clients.Client, state *terraform.InstanceState) (*bool, error) } diff --git a/internal/clients/builder.go b/internal/clients/builder.go index 9c3a8dc34..c4da233ea 100644 --- a/internal/clients/builder.go +++ b/internal/clients/builder.go @@ -4,12 +4,10 @@ import ( "context" "fmt" - "github.com/Azure/go-autorest/autorest" "github.com/hashicorp/go-azure-helpers/authentication" "github.com/hashicorp/go-azure-helpers/sender" - "github.com/terraform-providers/terraform-provider-azuread/internal/services" - aad "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/client" + "github.com/terraform-providers/terraform-provider-azuread/internal/common" ) type ClientBuilder struct { @@ -18,8 +16,8 @@ type ClientBuilder struct { TerraformVersion string } -// Build is a helper method which returns a fully instantiated *AadClient based on the auth Config's current settings. -func (b *ClientBuilder) Build(ctx context.Context) (*AadClient, error) { +// Build is a helper method which returns a fully instantiated *Client based on the auth Config's current settings. +func (b *ClientBuilder) Build(ctx context.Context) (*Client, error) { env, err := authentication.AzureEnvironmentByNameFromEndpoint(ctx, b.AuthConfig.MetadataHost, b.AuthConfig.Environment) if err != nil { return nil, err @@ -36,7 +34,7 @@ func (b *ClientBuilder) Build(ctx context.Context) (*AadClient, error) { } // client declarations: - client := AadClient{ + client := Client{ ClientID: b.AuthConfig.ClientID, ObjectID: objectID, TenantID: b.AuthConfig.TenantID, @@ -53,25 +51,25 @@ func (b *ClientBuilder) Build(ctx context.Context) (*AadClient, error) { return nil, err } - o := &services.ClientOptions{ - PartnerID: b.PartnerID, - TenantID: b.AuthConfig.TenantID, - TerraformVersion: b.TerraformVersion, - Environment: *env, - } - // Graph Endpoints - graphEndpoint := env.GraphEndpoint // todo this should become AadGraphEndpoint? - graphAuthorizer, err := b.AuthConfig.GetAuthorizationToken(sender, oauth, graphEndpoint) + aadGraphEndpoint := env.GraphEndpoint + aadGraphAuthorizer, err := b.AuthConfig.GetAuthorizationToken(sender, oauth, aadGraphEndpoint) if err != nil { return nil, err } - client.AadGraph = aad.BuildClient(o, graphEndpoint, graphAuthorizer) - - autorest.Count429AsRetry = false + o := &common.ClientOptions{ + AadGraphAuthorizer: aadGraphAuthorizer, + AadGraphEndpoint: aadGraphEndpoint, + PartnerID: b.PartnerID, + TenantID: b.AuthConfig.TenantID, + TerraformVersion: b.TerraformVersion, + Environment: *env, + } - client.StopContext = ctx + if err := client.build(ctx, o); err != nil { + return nil, fmt.Errorf("Error building Client: %+v", err) + } return &client, nil } diff --git a/internal/clients/client.go b/internal/clients/client.go index 4272b58a2..d2144c22b 100644 --- a/internal/clients/client.go +++ b/internal/clients/client.go @@ -3,14 +3,19 @@ package clients import ( "context" + "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure" - aad "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/client" + "github.com/terraform-providers/terraform-provider-azuread/internal/common" + applications "github.com/terraform-providers/terraform-provider-azuread/internal/services/applications/client" + domains "github.com/terraform-providers/terraform-provider-azuread/internal/services/domains/client" + groups "github.com/terraform-providers/terraform-provider-azuread/internal/services/groups/client" + serviceprincipals "github.com/terraform-providers/terraform-provider-azuread/internal/services/serviceprincipals/client" + users "github.com/terraform-providers/terraform-provider-azuread/internal/services/users/client" ) -// AadClient contains the handles to all the specific Azure AD resource classes' respective clients -type AadClient struct { - // todo move this to an "Account" struct as in azurerm? +// Client contains the handles to all the specific Azure AD resource classes' respective clients +type Client struct { ClientID string ObjectID string TenantID string @@ -21,6 +26,22 @@ type AadClient struct { StopContext context.Context - // Azure AD clients - AadGraph *aad.Client + Applications *applications.Client + Domains *domains.Client + Groups *groups.Client + ServicePrincipals *serviceprincipals.Client + Users *users.Client +} + +func (client *Client) build(ctx context.Context, o *common.ClientOptions) error { //nolint:unparam + autorest.Count429AsRetry = false + client.StopContext = ctx + + client.Applications = applications.NewClient(o) + client.Domains = domains.NewClient(o) + client.Groups = groups.NewClient(o) + client.ServicePrincipals = serviceprincipals.NewClient(o) + client.Users = users.NewClient(o) + + return nil } diff --git a/internal/services/configure_client.go b/internal/common/client_options.go similarity index 94% rename from internal/services/configure_client.go rename to internal/common/client_options.go index 6fefa26a3..84423f19f 100644 --- a/internal/services/configure_client.go +++ b/internal/common/client_options.go @@ -1,4 +1,4 @@ -package services +package common import ( "fmt" @@ -21,6 +21,9 @@ type ClientOptions struct { PartnerID string TerraformVersion string + AadGraphAuthorizer autorest.Authorizer + AadGraphEndpoint string + SkipProviderReg bool } diff --git a/internal/services/aadgraph/graph/application.go b/internal/helpers/aadgraph/application.go similarity index 76% rename from internal/services/aadgraph/graph/application.go rename to internal/helpers/aadgraph/application.go index e377e0349..3e86101c1 100644 --- a/internal/services/aadgraph/graph/application.go +++ b/internal/helpers/aadgraph/application.go @@ -1,158 +1,17 @@ -package graph +package aadgraph import ( "context" "errors" "fmt" + "log" "reflect" "strings" "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "github.com/terraform-providers/terraform-provider-azuread/internal/utils" ) -func SchemaAppRolesComputed() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Computed: true, - }, - - "allowed_member_types": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - - "description": { - Type: schema.TypeString, - Computed: true, - }, - - "display_name": { - Type: schema.TypeString, - Computed: true, - }, - - "is_enabled": { - Type: schema.TypeBool, - Computed: true, - }, - - "value": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - } -} - -func SchemaOauth2PermissionsComputed() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "admin_consent_description": { - Type: schema.TypeString, - Computed: true, - }, - - "admin_consent_display_name": { - Type: schema.TypeString, - Computed: true, - }, - - "id": { - Type: schema.TypeString, - Computed: true, - }, - - "is_enabled": { - Type: schema.TypeBool, - Computed: true, - }, - - "type": { - Type: schema.TypeString, - Computed: true, - }, - - "user_consent_description": { - Type: schema.TypeString, - Computed: true, - }, - - "user_consent_display_name": { - Type: schema.TypeString, - Computed: true, - }, - - "value": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - } -} - -func SchemaOptionalClaims() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - }, - - "source": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice( - []string{"user"}, - false, - ), - }, - "essential": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "additional_properties": { - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice( - []string{ - "dns_domain_and_sam_account_name", - "emit_as_roles", - "netbios_domain_and_sam_account_name", - "sam_account_name", - }, - false, - ), - }, - }, - }, - }, - } -} - func FlattenAppRoles(in *[]graphrbac.AppRole) []map[string]interface{} { if in == nil { return []map[string]interface{}{} @@ -317,32 +176,30 @@ func ApplicationFindByName(ctx context.Context, client *graphrbac.ApplicationsCl return nil, nil } -type AppRoleId struct { - ObjectId string - RoleId string -} +func ApplicationSetOwnersTo(ctx context.Context, client *graphrbac.ApplicationsClient, id string, desiredOwners []string) error { + existingOwners, err := ApplicationAllOwners(ctx, client, id) + if err != nil { + return err + } -func (id AppRoleId) String() string { - return id.ObjectId + "/role/" + id.RoleId -} + ownersForRemoval := utils.Difference(existingOwners, desiredOwners) + ownersToAdd := utils.Difference(desiredOwners, existingOwners) -func AppRoleIdFrom(objectId, roleId string) AppRoleId { - return AppRoleId{ - ObjectId: objectId, - RoleId: roleId, + // add owners first to prevent a possible situation where terraform revokes its own access before adding it back. + if err := ApplicationAddOwners(ctx, client, id, ownersToAdd); err != nil { + return err } -} -func ParseAppRoleId(idString string) (*AppRoleId, error) { - id, err := ParseObjectSubResourceId(idString, "role") - if err != nil { - return nil, fmt.Errorf("unable to parse App Role ID: %v", err) + for _, ownerToDelete := range ownersForRemoval { + log.Printf("[DEBUG] Removing owner with id %q from Application with id %q", ownerToDelete, id) + if resp, err := client.RemoveOwner(ctx, id, ownerToDelete); err != nil { + if !utils.ResponseWasNotFound(resp) { + return fmt.Errorf("deleting owner %q from Application with ID %q: %+v", ownerToDelete, id, err) + } + } } - return &AppRoleId{ - ObjectId: id.objectId, - RoleId: id.subId, - }, nil + return nil } func AppRoleFindById(app graphrbac.Application, roleId string) (*graphrbac.AppRole, error) { @@ -478,7 +335,7 @@ func AppRolesSet(ctx context.Context, client *graphrbac.ApplicationsClient, appI return nil } - // first disable any existing permissions + // first disable any existing roles properties := graphrbac.ApplicationUpdateParameters{ AppRoles: app.AppRoles, } @@ -493,7 +350,7 @@ func AppRolesSet(ctx context.Context, client *graphrbac.ApplicationsClient, appI } } - // then set the new permissions + // then set the new roles properties = graphrbac.ApplicationUpdateParameters{ AppRoles: newRoles, } @@ -505,34 +362,6 @@ func AppRolesSet(ctx context.Context, client *graphrbac.ApplicationsClient, appI return nil } -type OAuth2PermissionId struct { - ObjectId string - PermissionId string -} - -func (id OAuth2PermissionId) String() string { - return id.ObjectId + "/scope/" + id.PermissionId -} - -func OAuth2PermissionIdFrom(objectId, permissionId string) OAuth2PermissionId { - return OAuth2PermissionId{ - ObjectId: objectId, - PermissionId: permissionId, - } -} - -func ParseOAuth2PermissionId(idString string) (*OAuth2PermissionId, error) { - id, err := ParseObjectSubResourceId(idString, "scope") - if err != nil { - return nil, fmt.Errorf("unable to parse OAuth2 Permission ID: %v", err) - } - - return &OAuth2PermissionId{ - ObjectId: id.objectId, - PermissionId: id.subId, - }, nil -} - func OAuth2PermissionFindById(app graphrbac.Application, permissionId string) (*graphrbac.OAuth2Permission, error) { if app.Oauth2Permissions == nil { return nil, nil diff --git a/internal/services/aadgraph/graph/credentials.go b/internal/helpers/aadgraph/credentials.go similarity index 89% rename from internal/services/aadgraph/graph/credentials.go rename to internal/helpers/aadgraph/credentials.go index 6179b598e..2eb62e033 100644 --- a/internal/services/aadgraph/graph/credentials.go +++ b/internal/helpers/aadgraph/credentials.go @@ -1,11 +1,10 @@ -package graph +package aadgraph import ( "context" "encoding/base64" "errors" "fmt" - "strings" "time" "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" @@ -141,60 +140,6 @@ func PasswordResourceSchema(idAttribute string) map[string]*schema.Schema { } } -type CredentialId struct { - ObjectId string - KeyType string - KeyId string -} - -func (id CredentialId) String() string { - return id.ObjectId + "/" + id.KeyType + "/" + id.KeyId -} - -func ParseCertificateId(idString string) (*CredentialId, error) { - id, err := ParseObjectSubResourceId(idString, "certificate") - if err != nil { - return nil, fmt.Errorf("unable to parse Certificate ID: %v", err) - } - - return &CredentialId{ - ObjectId: id.objectId, - KeyType: id.Type, - KeyId: id.subId, - }, nil -} - -func ParsePasswordId(idString string) (*CredentialId, error) { - id, err := ParseObjectSubResourceId(idString, "password") - if err != nil { - return nil, fmt.Errorf("unable to parse Password ID: %v", err) - } - - return &CredentialId{ - ObjectId: id.objectId, - KeyType: id.Type, - KeyId: id.subId, - }, nil -} - -func ParseOldPasswordId(id string) (*CredentialId, error) { - parts := strings.Split(id, "/") - if len(parts) != 2 { - return nil, fmt.Errorf("Password ID expected to be in the format {objectId}/{keyId} - but got %q", id) - } - - newId := parts[0] + "/password/" + parts[1] - return ParsePasswordId(newId) -} - -func CredentialIdFrom(objectId, keyType, keyId string) CredentialId { - return CredentialId{ - ObjectId: objectId, - KeyType: keyType, - KeyId: keyId, - } -} - func PasswordCredentialForResource(d *schema.ResourceData) (*graphrbac.PasswordCredential, error) { value := d.Get("value").(string) diff --git a/internal/services/aadgraph/graph/errors.go b/internal/helpers/aadgraph/errors.go similarity index 95% rename from internal/services/aadgraph/graph/errors.go rename to internal/helpers/aadgraph/errors.go index 83bbee43a..56190a3ff 100644 --- a/internal/services/aadgraph/graph/errors.go +++ b/internal/helpers/aadgraph/errors.go @@ -1,4 +1,4 @@ -package graph +package aadgraph import "fmt" diff --git a/internal/services/aadgraph/graph/group.go b/internal/helpers/aadgraph/group.go similarity index 91% rename from internal/services/aadgraph/graph/group.go rename to internal/helpers/aadgraph/group.go index 865b0bf08..1a2fbae53 100644 --- a/internal/services/aadgraph/graph/group.go +++ b/internal/helpers/aadgraph/group.go @@ -1,4 +1,4 @@ -package graph +package aadgraph import ( "context" @@ -13,33 +13,6 @@ import ( "github.com/terraform-providers/terraform-provider-azuread/internal/utils" ) -type GroupMemberId struct { - ObjectSubResourceId - GroupId string - MemberId string -} - -func GroupMemberIdFrom(groupId, memberId string) GroupMemberId { - return GroupMemberId{ - ObjectSubResourceId: ObjectSubResourceIdFrom(groupId, "member", memberId), - GroupId: groupId, - MemberId: memberId, - } -} - -func ParseGroupMemberId(idString string) (*GroupMemberId, error) { - id, err := ParseObjectSubResourceId(idString, "member") - if err != nil { - return nil, fmt.Errorf("unable to parse Member ID: %v", err) - } - - return &GroupMemberId{ - ObjectSubResourceId: *id, - GroupId: id.objectId, - MemberId: id.subId, - }, nil -} - func GroupGetByDisplayName(ctx context.Context, client *graphrbac.GroupsClient, displayName string) (*graphrbac.ADGroup, error) { filter := fmt.Sprintf("displayName eq '%s'", displayName) diff --git a/internal/services/aadgraph/graph/odata.go b/internal/helpers/aadgraph/odata.go similarity index 98% rename from internal/services/aadgraph/graph/odata.go rename to internal/helpers/aadgraph/odata.go index 884340f1b..d2c143490 100644 --- a/internal/services/aadgraph/graph/odata.go +++ b/internal/helpers/aadgraph/odata.go @@ -1,4 +1,4 @@ -package graph +package aadgraph import ( "encoding/json" diff --git a/internal/services/aadgraph/graph/replication.go b/internal/helpers/aadgraph/replication.go similarity index 99% rename from internal/services/aadgraph/graph/replication.go rename to internal/helpers/aadgraph/replication.go index b057f6352..96af88307 100644 --- a/internal/services/aadgraph/graph/replication.go +++ b/internal/helpers/aadgraph/replication.go @@ -1,4 +1,4 @@ -package graph +package aadgraph import ( "context" diff --git a/internal/services/aadgraph/graph/user.go b/internal/helpers/aadgraph/user.go similarity index 99% rename from internal/services/aadgraph/graph/user.go rename to internal/helpers/aadgraph/user.go index 478bf1627..7c44ca359 100644 --- a/internal/services/aadgraph/graph/user.go +++ b/internal/helpers/aadgraph/user.go @@ -1,4 +1,4 @@ -package graph +package aadgraph import ( "context" diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 3bf8e2756..c0413da69 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" ) @@ -48,15 +47,9 @@ func AzureADProvider() *schema.Provider { log.Printf(f, v...) } - // only one for now so keeping it simple, eventually we will need a way to differentiate between aadgraph and msgraph? - // looks like only an env var will work? - services := []ServiceRegistration{ - aadgraph.Registration{}, - } - dataSources := make(map[string]*schema.Resource) resources := make(map[string]*schema.Resource) - for _, service := range services { + for _, service := range SupportedServices() { debugLog("[DEBUG] Registering Resources for %q..", service.Name()) for k, v := range service.SupportedResources() { if existing := resources[k]; existing != nil { @@ -201,7 +194,7 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc { } } -func buildClient(ctx context.Context, p *schema.Provider, b *authentication.Builder, partnerId string) (*clients.AadClient, diag.Diagnostics) { +func buildClient(ctx context.Context, p *schema.Provider, b *authentication.Builder, partnerId string) (*clients.Client, diag.Diagnostics) { config, err := b.Build() if err != nil { return nil, tf.ErrorDiagF(err, "Building AzureAD Client") diff --git a/internal/provider/services.go b/internal/provider/services.go new file mode 100644 index 000000000..7b789e74a --- /dev/null +++ b/internal/provider/services.go @@ -0,0 +1,19 @@ +package provider + +import ( + "github.com/terraform-providers/terraform-provider-azuread/internal/services/applications" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/domains" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/groups" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/serviceprincipals" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/users" +) + +func SupportedServices() []ServiceRegistration { + return []ServiceRegistration{ + applications.Registration{}, + domains.Registration{}, + groups.Registration{}, + serviceprincipals.Registration{}, + users.Registration{}, + } +} diff --git a/internal/services/aadgraph/application_data_source_test.go b/internal/services/aadgraph/application_data_source_test.go deleted file mode 100644 index f675bdc5d..000000000 --- a/internal/services/aadgraph/application_data_source_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package aadgraph_test - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - - "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance" - "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance/check" -) - -type ApplicationDataSource struct{} - -func TestAccApplicationDataSource_byObjectId(t *testing.T) { - data := acceptance.BuildTestData(t, "data.azuread_application", "test") - r := ApplicationDataSource{} - - data.DataSourceTest(t, []resource.TestStep{ - { - Config: r.objectId(data), - Check: resource.ComposeTestCheckFunc( - check.That(data.ResourceName).Key("application_id").IsUuid(), - check.That(data.ResourceName).Key("object_id").IsUuid(), - check.That(data.ResourceName).Key("name").HasValue(fmt.Sprintf("acctest-APP-%d", data.RandomInteger)), - check.That(data.ResourceName).Key("homepage").HasValue(fmt.Sprintf("https://homepage-%d", data.RandomInteger)), - check.That(data.ResourceName).Key("identifier_uris.#").HasValue("1"), - check.That(data.ResourceName).Key("reply_urls.#").HasValue("1"), - check.That(data.ResourceName).Key("oauth2_allow_implicit_flow").HasValue("true"), - check.That(data.ResourceName).Key("optional_claims.#").HasValue("1"), - check.That(data.ResourceName).Key("optional_claims.0.access_token.#").HasValue("2"), - check.That(data.ResourceName).Key("optional_claims.0.id_token.#").HasValue("1"), - check.That(data.ResourceName).Key("required_resource_access.#").HasValue("2"), - check.That(data.ResourceName).Key("group_membership_claims").HasValue("All"), - ), - }, - }) -} - -func TestAccApplicationDataSource_byApplicationId(t *testing.T) { - data := acceptance.BuildTestData(t, "data.azuread_application", "test") - r := ApplicationDataSource{} - - data.DataSourceTest(t, []resource.TestStep{ - { - Config: r.applicationId(data), - Check: resource.ComposeTestCheckFunc( - check.That(data.ResourceName).Key("application_id").IsUuid(), - check.That(data.ResourceName).Key("object_id").IsUuid(), - check.That(data.ResourceName).Key("name").HasValue(fmt.Sprintf("acctest-APP-%d", data.RandomInteger)), - check.That(data.ResourceName).Key("homepage").HasValue(fmt.Sprintf("https://homepage-%d", data.RandomInteger)), - check.That(data.ResourceName).Key("identifier_uris.#").HasValue("1"), - check.That(data.ResourceName).Key("reply_urls.#").HasValue("1"), - check.That(data.ResourceName).Key("oauth2_allow_implicit_flow").HasValue("true"), - check.That(data.ResourceName).Key("optional_claims.#").HasValue("1"), - check.That(data.ResourceName).Key("optional_claims.0.access_token.#").HasValue("2"), - check.That(data.ResourceName).Key("optional_claims.0.id_token.#").HasValue("1"), - check.That(data.ResourceName).Key("required_resource_access.#").HasValue("2"), - check.That(data.ResourceName).Key("group_membership_claims").HasValue("All"), - ), - }, - }) -} - -func TestAccApplicationDataSource_byName(t *testing.T) { - data := acceptance.BuildTestData(t, "data.azuread_application", "test") - r := ApplicationDataSource{} - - data.DataSourceTest(t, []resource.TestStep{ - { - Config: r.name(data), - Check: resource.ComposeTestCheckFunc( - check.That(data.ResourceName).Key("application_id").IsUuid(), - check.That(data.ResourceName).Key("object_id").IsUuid(), - check.That(data.ResourceName).Key("name").HasValue(fmt.Sprintf("acctest-APP-%d", data.RandomInteger)), - check.That(data.ResourceName).Key("homepage").HasValue(fmt.Sprintf("https://homepage-%d", data.RandomInteger)), - check.That(data.ResourceName).Key("identifier_uris.#").HasValue("1"), - check.That(data.ResourceName).Key("reply_urls.#").HasValue("1"), - check.That(data.ResourceName).Key("oauth2_allow_implicit_flow").HasValue("true"), - check.That(data.ResourceName).Key("optional_claims.#").HasValue("1"), - check.That(data.ResourceName).Key("optional_claims.0.access_token.#").HasValue("2"), - check.That(data.ResourceName).Key("optional_claims.0.id_token.#").HasValue("1"), - check.That(data.ResourceName).Key("required_resource_access.#").HasValue("2"), - check.That(data.ResourceName).Key("group_membership_claims").HasValue("All"), - ), - }, - }) -} - -func (ApplicationDataSource) objectId(data acceptance.TestData) string { - return fmt.Sprintf(` -%[1]s - -data "azuread_application" "test" { - object_id = azuread_application.test.object_id -} -`, ApplicationResource{}.complete(data)) -} - -func (ApplicationDataSource) applicationId(data acceptance.TestData) string { - return fmt.Sprintf(` -%[1]s - -data "azuread_application" "test" { - application_id = azuread_application.test.application_id -} -`, ApplicationResource{}.complete(data)) -} - -func (ApplicationDataSource) name(data acceptance.TestData) string { - return fmt.Sprintf(` -%[1]s - -data "azuread_application" "test" { - name = azuread_application.test.name -} -`, ApplicationResource{}.complete(data)) -} diff --git a/internal/services/aadgraph/client/client.go b/internal/services/aadgraph/client/client.go deleted file mode 100644 index c13926ee5..000000000 --- a/internal/services/aadgraph/client/client.go +++ /dev/null @@ -1,41 +0,0 @@ -package client - -import ( - "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" - "github.com/Azure/go-autorest/autorest" - - "github.com/terraform-providers/terraform-provider-azuread/internal/services" -) - -type Client struct { - ApplicationsClient *graphrbac.ApplicationsClient - DomainsClient *graphrbac.DomainsClient - GroupsClient *graphrbac.GroupsClient - ServicePrincipalsClient *graphrbac.ServicePrincipalsClient - UsersClient *graphrbac.UsersClient -} - -func BuildClient(o *services.ClientOptions, endpoint string, authorizer autorest.Authorizer) *Client { - applicationsClient := graphrbac.NewApplicationsClientWithBaseURI(endpoint, o.TenantID) - o.ConfigureClient(&applicationsClient.Client, authorizer) - - domainsClient := graphrbac.NewDomainsClientWithBaseURI(endpoint, o.TenantID) - o.ConfigureClient(&domainsClient.Client, authorizer) - - groupsClient := graphrbac.NewGroupsClientWithBaseURI(endpoint, o.TenantID) - o.ConfigureClient(&groupsClient.Client, authorizer) - - servicePrincipalsClient := graphrbac.NewServicePrincipalsClientWithBaseURI(endpoint, o.TenantID) - o.ConfigureClient(&servicePrincipalsClient.Client, authorizer) - - usersClient := graphrbac.NewUsersClientWithBaseURI(endpoint, o.TenantID) - o.ConfigureClient(&usersClient.Client, authorizer) - - return &Client{ - ApplicationsClient: &applicationsClient, - DomainsClient: &domainsClient, - GroupsClient: &groupsClient, - ServicePrincipalsClient: &servicePrincipalsClient, - UsersClient: &usersClient, - } -} diff --git a/internal/services/aadgraph/application_app_role_resource.go b/internal/services/applications/application_app_role_resource.go similarity index 88% rename from internal/services/aadgraph/application_app_role_resource.go rename to internal/services/applications/application_app_role_resource.go index 3f99c6df8..42031fd8f 100644 --- a/internal/services/aadgraph/application_app_role_resource.go +++ b/internal/services/applications/application_app_role_resource.go @@ -1,4 +1,4 @@ -package aadgraph +package applications import ( "context" @@ -11,7 +11,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/applications/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" @@ -25,7 +26,7 @@ func applicationAppRoleResource() *schema.Resource { DeleteContext: applicationAppRoleResourceDelete, Importer: tf.ValidateResourceIDPriorToImport(func(id string) error { - _, err := graph.ParseAppRoleId(id) + _, err := parse.AppRoleID(id) return err }), @@ -62,6 +63,7 @@ func applicationAppRoleResource() *schema.Resource { ValidateDiagFunc: validate.NoEmptyStrings, }, + // TODO: v2.0 rename to `enabled` "is_enabled": { Type: schema.TypeBool, Optional: true, @@ -85,7 +87,7 @@ func applicationAppRoleResource() *schema.Resource { } func applicationAppRoleResourceCreateUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient objectId := d.Get("application_object_id").(string) @@ -119,7 +121,7 @@ func applicationAppRoleResourceCreateUpdate(ctx context.Context, d *schema.Resou role.Value = utils.String(v.(string)) } - id := graph.AppRoleIdFrom(objectId, *role.ID) + id := parse.NewAppRoleID(objectId, *role.ID) tf.LockByName(resourceApplicationName, id.ObjectId) defer tf.UnlockByName(resourceApplicationName, id.ObjectId) @@ -136,19 +138,19 @@ func applicationAppRoleResourceCreateUpdate(ctx context.Context, d *schema.Resou var newRoles *[]graphrbac.AppRole if d.IsNewResource() { - newRoles, err = graph.AppRoleAdd(app.AppRoles, &role) + newRoles, err = aadgraph.AppRoleAdd(app.AppRoles, &role) if err != nil { - if _, ok := err.(*graph.AlreadyExistsError); ok { + if _, ok := err.(*aadgraph.AlreadyExistsError); ok { return tf.ImportAsExistsDiag("azuread_application_app_role", id.String()) } return tf.ErrorDiagF(err, "Failed to add App Role") } } else { - if existing, _ := graph.AppRoleFindById(app, id.RoleId); existing == nil { + if existing, _ := aadgraph.AppRoleFindById(app, id.RoleId); existing == nil { return tf.ErrorDiagPathF(nil, "role_id", "App Role with ID %q was not found for Application %q", id.RoleId, id.ObjectId) } - newRoles, err = graph.AppRoleUpdate(app.AppRoles, &role) + newRoles, err = aadgraph.AppRoleUpdate(app.AppRoles, &role) if err != nil { return tf.ErrorDiagF(err, "Updating App Role with ID %q", *role.ID) } @@ -167,9 +169,9 @@ func applicationAppRoleResourceCreateUpdate(ctx context.Context, d *schema.Resou } func applicationAppRoleResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient - id, err := graph.ParseAppRoleId(d.Id()) + id, err := parse.AppRoleID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing App Role ID %q", d.Id()) } @@ -186,7 +188,7 @@ func applicationAppRoleResourceRead(ctx context.Context, d *schema.ResourceData, return tf.ErrorDiagPathF(err, "application_object_id", "Retrieving Application with object ID %q", id.ObjectId) } - role, err := graph.AppRoleFindById(app, id.RoleId) + role, err := aadgraph.AppRoleFindById(app, id.RoleId) if err != nil { return tf.ErrorDiagF(err, "Identifying App Role") } @@ -229,9 +231,9 @@ func applicationAppRoleResourceRead(ctx context.Context, d *schema.ResourceData, } func applicationAppRoleResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient - id, err := graph.ParseAppRoleId(d.Id()) + id, err := parse.AppRoleID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing App Role ID %q", d.Id()) } @@ -251,7 +253,7 @@ func applicationAppRoleResourceDelete(ctx context.Context, d *schema.ResourceDat } log.Printf("[DEBUG] Disabling App Role %q for Application %q prior to removal", id.RoleId, id.ObjectId) - newRoles, err := graph.AppRoleResultDisableById(app.AppRoles, id.RoleId) + newRoles, err := aadgraph.AppRoleResultDisableById(app.AppRoles, id.RoleId) if err != nil { return tf.ErrorDiagF(err, "Disabling App Role with ID %q for application %q", id.RoleId, id.ObjectId) } @@ -264,7 +266,7 @@ func applicationAppRoleResourceDelete(ctx context.Context, d *schema.ResourceDat } log.Printf("[DEBUG] Removing App Role %q for Application %q", id.RoleId, id.ObjectId) - newRoles, err = graph.AppRoleResultRemoveById(app.AppRoles, id.RoleId) + newRoles, err = aadgraph.AppRoleResultRemoveById(app.AppRoles, id.RoleId) if err != nil { return tf.ErrorDiagF(err, "Removing App Role with ID %q for application %q", id.RoleId, id.ObjectId) } diff --git a/internal/services/aadgraph/application_app_role_resource_test.go b/internal/services/applications/application_app_role_resource_test.go similarity index 93% rename from internal/services/aadgraph/application_app_role_resource_test.go rename to internal/services/applications/application_app_role_resource_test.go index 137d85b10..96e7d730a 100644 --- a/internal/services/aadgraph/application_app_role_resource_test.go +++ b/internal/services/applications/application_app_role_resource_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package applications_test import ( "context" @@ -11,7 +11,8 @@ import ( "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance" "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance/check" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/applications/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" ) @@ -96,13 +97,13 @@ func TestAccApplicationAppRole_requiresImport(t *testing.T) { }) } -func (a ApplicationAppRoleResource) Exists(ctx context.Context, clients *clients.AadClient, state *terraform.InstanceState) (*bool, error) { - id, err := graph.ParseAppRoleId(state.ID) +func (a ApplicationAppRoleResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + id, err := parse.AppRoleID(state.ID) if err != nil { return nil, fmt.Errorf("parsing App Role ID: %v", err) } - resp, err := clients.AadGraph.ApplicationsClient.Get(ctx, id.ObjectId) + resp, err := clients.Applications.AadClient.Get(ctx, id.ObjectId) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return nil, fmt.Errorf("Application with object ID %q does not exist", id.ObjectId) @@ -110,7 +111,7 @@ func (a ApplicationAppRoleResource) Exists(ctx context.Context, clients *clients return nil, fmt.Errorf("failed to retrieve Application with object ID %q: %+v", id.ObjectId, err) } - role, err := graph.AppRoleFindById(resp, id.RoleId) + role, err := aadgraph.AppRoleFindById(resp, id.RoleId) if err != nil { return nil, fmt.Errorf("failed to identity App Role: %s", err) } else if role != nil { diff --git a/internal/services/aadgraph/application_certificate_resource.go b/internal/services/applications/application_certificate_resource.go similarity index 83% rename from internal/services/aadgraph/application_certificate_resource.go rename to internal/services/applications/application_certificate_resource.go index b7e8d0522..fbc9a90d4 100644 --- a/internal/services/aadgraph/application_certificate_resource.go +++ b/internal/services/applications/application_certificate_resource.go @@ -1,4 +1,4 @@ -package aadgraph +package applications import ( "context" @@ -10,7 +10,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/applications/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" ) @@ -22,29 +23,29 @@ func applicationCertificateResource() *schema.Resource { DeleteContext: applicationCertificateResourceDelete, Importer: tf.ValidateResourceIDPriorToImport(func(id string) error { - _, err := graph.ParseCertificateId(id) + _, err := parse.CertificateID(id) return err }), - Schema: graph.CertificateResourceSchema("application_object_id"), + Schema: aadgraph.CertificateResourceSchema("application_object_id"), } } func applicationCertificateResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient objectId := d.Get("application_object_id").(string) - cred, err := graph.KeyCredentialForResource(d) + cred, err := aadgraph.KeyCredentialForResource(d) if err != nil { attr := "" - if kerr, ok := err.(graph.CredentialError); ok { + if kerr, ok := err.(aadgraph.CredentialError); ok { attr = kerr.Attr() } return tf.ErrorDiagPathF(err, attr, "Generating certificate credentials for application with object ID %q", objectId) } - id := graph.CredentialIdFrom(objectId, "certificate", *cred.KeyID) + id := parse.NewCredentialID(objectId, "certificate", *cred.KeyID) tf.LockByName(resourceApplicationName, id.ObjectId) defer tf.UnlockByName(resourceApplicationName, id.ObjectId) @@ -54,9 +55,9 @@ func applicationCertificateResourceCreate(ctx context.Context, d *schema.Resourc return tf.ErrorDiagPathF(err, "application_object_id", "Listing certificate credentials for application with ID %q", objectId) } - newCreds, err := graph.KeyCredentialResultAdd(existingCreds, cred) + newCreds, err := aadgraph.KeyCredentialResultAdd(existingCreds, cred) if err != nil { - if _, ok := err.(*graph.AlreadyExistsError); ok { + if _, ok := err.(*aadgraph.AlreadyExistsError); ok { return tf.ImportAsExistsDiag("azuread_application_certificate", id.String()) } return tf.ErrorDiagF(err, "Adding application certificate") @@ -66,7 +67,7 @@ func applicationCertificateResourceCreate(ctx context.Context, d *schema.Resourc return tf.ErrorDiagF(err, "Creating certificate credentials %q for application with object ID %q", id.KeyId, id.ObjectId) } - _, err = graph.WaitForKeyCredentialReplication(ctx, id.KeyId, d.Timeout(schema.TimeoutCreate), func() (graphrbac.KeyCredentialListResult, error) { + _, err = aadgraph.WaitForKeyCredentialReplication(ctx, id.KeyId, d.Timeout(schema.TimeoutCreate), func() (graphrbac.KeyCredentialListResult, error) { return client.ListKeyCredentials(ctx, id.ObjectId) }) if err != nil { @@ -79,9 +80,9 @@ func applicationCertificateResourceCreate(ctx context.Context, d *schema.Resourc } func applicationCertificateResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient - id, err := graph.ParseCertificateId(d.Id()) + id, err := parse.CertificateID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing certificate credential with ID %q", d.Id()) } @@ -103,7 +104,7 @@ func applicationCertificateResourceRead(ctx context.Context, d *schema.ResourceD return tf.ErrorDiagPathF(err, "application_object_id", "Listing certificate credentials for application with object ID %q", id.ObjectId) } - credential := graph.KeyCredentialResultFindByKeyId(credentials, id.KeyId) + credential := aadgraph.KeyCredentialResultFindByKeyId(credentials, id.KeyId) if credential == nil { log.Printf("[DEBUG] Certificate credential %q (ID %q) was not found - removing from state!", id.KeyId, id.ObjectId) d.SetId("") @@ -146,9 +147,9 @@ func applicationCertificateResourceRead(ctx context.Context, d *schema.ResourceD } func applicationCertificateResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient - id, err := graph.ParseCertificateId(d.Id()) + id, err := parse.CertificateID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing certificate credential with ID %q", d.Id()) } @@ -172,7 +173,7 @@ func applicationCertificateResourceDelete(ctx context.Context, d *schema.Resourc return tf.ErrorDiagF(err, "Listing certificate credential for application with object ID %q", id.ObjectId) } - newCreds, err := graph.KeyCredentialResultRemoveByKeyId(existing, id.KeyId) + newCreds, err := aadgraph.KeyCredentialResultRemoveByKeyId(existing, id.KeyId) if err != nil { return tf.ErrorDiagF(err, "Removing certificate credential %q from application with object ID %q", id.KeyId, id.ObjectId) } diff --git a/internal/services/aadgraph/application_certificate_resource_test.go b/internal/services/applications/application_certificate_resource_test.go similarity index 93% rename from internal/services/aadgraph/application_certificate_resource_test.go rename to internal/services/applications/application_certificate_resource_test.go index f40236e37..1d128dc74 100644 --- a/internal/services/aadgraph/application_certificate_resource_test.go +++ b/internal/services/applications/application_certificate_resource_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package applications_test import ( "context" @@ -12,7 +12,8 @@ import ( "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance" "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance/check" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/applications/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" ) @@ -107,13 +108,13 @@ func TestAccApplicationCertificate_requiresImport(t *testing.T) { }) } -func (ApplicationCertificateResource) Exists(ctx context.Context, clients *clients.AadClient, state *terraform.InstanceState) (*bool, error) { - id, err := graph.ParseCertificateId(state.ID) +func (ApplicationCertificateResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + id, err := parse.CertificateID(state.ID) if err != nil { return nil, fmt.Errorf("parsing Application Certificate ID: %v", err) } - resp, err := clients.AadGraph.ApplicationsClient.Get(ctx, id.ObjectId) + resp, err := clients.Applications.AadClient.Get(ctx, id.ObjectId) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return nil, fmt.Errorf("Application with object ID %q does not exist", id.ObjectId) @@ -121,12 +122,12 @@ func (ApplicationCertificateResource) Exists(ctx context.Context, clients *clien return nil, fmt.Errorf("failed to retrieve Application with object ID %q: %+v", id.ObjectId, err) } - credentials, err := clients.AadGraph.ApplicationsClient.ListKeyCredentials(ctx, id.ObjectId) + credentials, err := clients.Applications.AadClient.ListKeyCredentials(ctx, id.ObjectId) if err != nil { return nil, fmt.Errorf("listing Key Credentials for Application %q: %+v", id.ObjectId, err) } - cred := graph.KeyCredentialResultFindByKeyId(credentials, id.KeyId) + cred := aadgraph.KeyCredentialResultFindByKeyId(credentials, id.KeyId) if cred != nil { return utils.Bool(true), nil } diff --git a/internal/services/aadgraph/application_data_source.go b/internal/services/applications/application_data_source.go similarity index 64% rename from internal/services/aadgraph/application_data_source.go rename to internal/services/applications/application_data_source.go index 9a428c916..9051c8425 100644 --- a/internal/services/aadgraph/application_data_source.go +++ b/internal/services/applications/application_data_source.go @@ -1,4 +1,4 @@ -package aadgraph +package applications import ( "context" @@ -9,22 +9,22 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" ) -func applicationData() *schema.Resource { +func applicationDataSource() *schema.Resource { return &schema.Resource{ - ReadContext: applicationDataRead, + ReadContext: applicationDataSourceRead, Schema: map[string]*schema.Schema{ "object_id": { Type: schema.TypeString, Optional: true, Computed: true, - ExactlyOneOf: []string{"application_id", "name", "object_id"}, + ExactlyOneOf: []string{"application_id", "display_name", "name", "object_id"}, ValidateDiagFunc: validate.UUID, }, @@ -32,32 +32,87 @@ func applicationData() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, - ExactlyOneOf: []string{"application_id", "name", "object_id"}, + ExactlyOneOf: []string{"application_id", "display_name", "name", "object_id"}, ValidateDiagFunc: validate.UUID, }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"application_id", "display_name", "name", "object_id"}, + ValidateDiagFunc: validate.NoEmptyStrings, + }, + "name": { Type: schema.TypeString, Optional: true, Computed: true, - ExactlyOneOf: []string{"application_id", "name", "object_id"}, + Deprecated: "This property has been renamed to `display_name` and will be removed in version 2.0 of this provider.", + ExactlyOneOf: []string{"application_id", "display_name", "name", "object_id"}, ValidateDiagFunc: validate.NoEmptyStrings, }, - "homepage": { + "app_roles": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + + "allowed_member_types": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "description": { + Type: schema.TypeString, + Computed: true, + }, + + "display_name": { + Type: schema.TypeString, + Computed: true, + }, + + // TODO: v2.0 rename to `enabled` + "is_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "value": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + // TODO: v2.0 move this to `sign_in_audience` property and support other values + "available_to_other_tenants": { + Type: schema.TypeBool, + Computed: true, + }, + + "group_membership_claims": { Type: schema.TypeString, Computed: true, }, - "identifier_uris": { - Type: schema.TypeList, + // TODO: v2.0 put this in a `web` block and remove Computed + "homepage": { + Type: schema.TypeString, Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, }, - "reply_urls": { + "identifier_uris": { Type: schema.TypeList, Computed: true, Elem: &schema.Schema{ @@ -65,32 +120,68 @@ func applicationData() *schema.Resource { }, }, + // TODO: v2.0 put this in a `web` block "logout_url": { Type: schema.TypeString, Computed: true, }, - "available_to_other_tenants": { - Type: schema.TypeBool, - Computed: true, - }, - + // TODO: v2.0 put this in an `implicit_grant` block and rename to `access_token_issuance_enabled` "oauth2_allow_implicit_flow": { Type: schema.TypeBool, Computed: true, }, - "group_membership_claims": { - Type: schema.TypeString, + // TODO: v2.0 put this in an `api` block and maybe rename to `oauth2_permission_scope` + "oauth2_permissions": { + Type: schema.TypeList, + Optional: true, Computed: true, - }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "admin_consent_description": { + Type: schema.TypeString, + Computed: true, + }, - "type": { - Type: schema.TypeString, - Computed: true, - }, + "admin_consent_display_name": { + Type: schema.TypeString, + Computed: true, + }, + + "id": { + Type: schema.TypeString, + Computed: true, + }, + + // TODO: v2.0 rename to `enabled` + "is_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "type": { + Type: schema.TypeString, + Computed: true, + }, + + "user_consent_description": { + Type: schema.TypeString, + Computed: true, + }, + + "user_consent_display_name": { + Type: schema.TypeString, + Computed: true, + }, - "app_roles": graph.SchemaAppRolesComputed(), + "value": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, "optional_claims": { Type: schema.TypeList, @@ -98,14 +189,30 @@ func applicationData() *schema.Resource { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "access_token": graph.SchemaOptionalClaims(), - "id_token": graph.SchemaOptionalClaims(), + "access_token": schemaOptionalClaims(), + "id_token": schemaOptionalClaims(), // TODO: enable when https://github.com/Azure/azure-sdk-for-go/issues/9714 resolved - //"saml_token": graph.SchemaOptionalClaims(), + //"saml_token": schemaOptionalClaims(), }, }, }, + "owners": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "reply_urls": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "required_resource_access": { Type: schema.TypeList, Computed: true, @@ -137,21 +244,17 @@ func applicationData() *schema.Resource { }, }, - "owners": { - Type: schema.TypeList, + // TODO: v2.0 drop this, there's no such distinction any more + "type": { + Type: schema.TypeString, Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, }, - - "oauth2_permissions": graph.SchemaOauth2PermissionsComputed(), }, } } -func applicationDataRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient +func applicationDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*clients.Client).Applications.AadClient var app graphrbac.Application @@ -171,11 +274,14 @@ func applicationDataRead(ctx context.Context, d *schema.ResourceData, meta inter if applicationId, ok := d.Get("application_id").(string); ok && applicationId != "" { fieldName = "appId" fieldValue = applicationId + } else if displayName, ok := d.Get("display_name").(string); ok && displayName != "" { + fieldName = "displayName" + fieldValue = displayName } else if name, ok := d.Get("name").(string); ok && name != "" { fieldName = "displayName" fieldValue = name } else { - return tf.ErrorDiagF(nil, "One of `object_id`, `application_id` or `name` must be specified") + return tf.ErrorDiagF(nil, "One of `object_id`, `application_id` or `displayName` must be specified") } filter := fmt.Sprintf("%s eq '%s'", fieldName, fieldValue) @@ -257,11 +363,11 @@ func applicationDataRead(ctx context.Context, d *schema.ResourceData, meta inter return dg } - if dg := tf.Set(d, "required_resource_access", flattenApplicationRequiredResourceAccess(app.RequiredResourceAccess)); dg != nil { + if dg := tf.Set(d, "required_resource_access", flattenApplicationRequiredResourceAccessAad(app.RequiredResourceAccess)); dg != nil { return dg } - if dg := tf.Set(d, "optional_claims", flattenApplicationOptionalClaims(app.OptionalClaims)); dg != nil { + if dg := tf.Set(d, "optional_claims", flattenApplicationOptionalClaimsAad(app.OptionalClaims)); dg != nil { return dg } @@ -276,7 +382,7 @@ func applicationDataRead(ctx context.Context, d *schema.ResourceData, meta inter return dg } - if dg := tf.Set(d, "app_roles", graph.FlattenAppRoles(app.AppRoles)); dg != nil { + if dg := tf.Set(d, "app_roles", aadgraph.FlattenAppRoles(app.AppRoles)); dg != nil { return dg } @@ -284,11 +390,11 @@ func applicationDataRead(ctx context.Context, d *schema.ResourceData, meta inter return dg } - if dg := tf.Set(d, "oauth2_permissions", graph.FlattenOauth2Permissions(app.Oauth2Permissions)); dg != nil { + if dg := tf.Set(d, "oauth2_permissions", aadgraph.FlattenOauth2Permissions(app.Oauth2Permissions)); dg != nil { return dg } - owners, err := graph.ApplicationAllOwners(ctx, client, d.Id()) + owners, err := aadgraph.ApplicationAllOwners(ctx, client, d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "owners", "Could not retrieve owners for application with object ID %q", *app.ObjectID) } diff --git a/internal/services/applications/application_data_source_test.go b/internal/services/applications/application_data_source_test.go new file mode 100644 index 000000000..d3e4e5bf1 --- /dev/null +++ b/internal/services/applications/application_data_source_test.go @@ -0,0 +1,118 @@ +package applications_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance/check" +) + +type ApplicationDataSource struct{} + +func TestAccApplicationDataSource_byObjectId(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_application", "test") + r := ApplicationDataSource{} + + data.DataSourceTest(t, []resource.TestStep{ + { + Config: r.objectId(data), + Check: r.testCheck(data), + }, + }) +} + +func TestAccApplicationDataSource_byApplicationId(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_application", "test") + r := ApplicationDataSource{} + + data.DataSourceTest(t, []resource.TestStep{ + { + Config: r.applicationId(data), + Check: r.testCheck(data), + }, + }) +} + +func TestAccApplicationDataSource_byDisplayName(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_application", "test") + r := ApplicationDataSource{} + + data.DataSourceTest(t, []resource.TestStep{ + { + Config: r.displayName(data), + Check: r.testCheck(data), + }, + }) +} + +func TestAccApplicationDataSource_byNameDeprecated(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_application", "test") + r := ApplicationDataSource{} + + data.DataSourceTest(t, []resource.TestStep{ + { + Config: r.nameDeprecated(data), + Check: r.testCheck(data), + }, + }) +} + +func (ApplicationDataSource) testCheck(data acceptance.TestData) resource.TestCheckFunc { + return resource.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("application_id").IsUuid(), + check.That(data.ResourceName).Key("object_id").IsUuid(), + check.That(data.ResourceName).Key("name").HasValue(fmt.Sprintf("acctest-APP-%d", data.RandomInteger)), + check.That(data.ResourceName).Key("homepage").HasValue(fmt.Sprintf("https://homepage-%d", data.RandomInteger)), + check.That(data.ResourceName).Key("identifier_uris.#").HasValue("1"), + check.That(data.ResourceName).Key("reply_urls.#").HasValue("1"), + check.That(data.ResourceName).Key("oauth2_allow_implicit_flow").HasValue("true"), + check.That(data.ResourceName).Key("optional_claims.#").HasValue("1"), + check.That(data.ResourceName).Key("optional_claims.0.access_token.#").HasValue("2"), + check.That(data.ResourceName).Key("optional_claims.0.id_token.#").HasValue("1"), + check.That(data.ResourceName).Key("required_resource_access.#").HasValue("2"), + check.That(data.ResourceName).Key("group_membership_claims").HasValue("All"), + ) +} + +func (ApplicationDataSource) objectId(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azuread_application" "test" { + object_id = azuread_application.test.object_id +} +`, ApplicationResource{}.complete(data)) +} + +func (ApplicationDataSource) applicationId(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azuread_application" "test" { + application_id = azuread_application.test.application_id +} +`, ApplicationResource{}.complete(data)) +} + +func (ApplicationDataSource) displayName(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azuread_application" "test" { + display_name = azuread_application.test.name +} +`, ApplicationResource{}.complete(data)) +} + +func (ApplicationDataSource) nameDeprecated(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azuread_application" "test" { + name = azuread_application.test.name +} +`, ApplicationResource{}.complete(data)) +} diff --git a/internal/services/aadgraph/application_oauth2_permission_resource.go b/internal/services/applications/application_oauth2_permission_resource.go similarity index 88% rename from internal/services/aadgraph/application_oauth2_permission_resource.go rename to internal/services/applications/application_oauth2_permission_resource.go index 1b649b524..c038fc2bc 100644 --- a/internal/services/aadgraph/application_oauth2_permission_resource.go +++ b/internal/services/applications/application_oauth2_permission_resource.go @@ -1,4 +1,4 @@ -package aadgraph +package applications import ( "context" @@ -11,7 +11,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/applications/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" @@ -25,7 +26,7 @@ func applicationOAuth2PermissionResource() *schema.Resource { DeleteContext: applicationOAuth2PermissionResourceDelete, Importer: tf.ValidateResourceIDPriorToImport(func(id string) error { - _, err := graph.ParseOAuth2PermissionId(id) + _, err := parse.OAuth2PermissionID(id) return err }), @@ -49,6 +50,7 @@ func applicationOAuth2PermissionResource() *schema.Resource { ValidateDiagFunc: validate.NoEmptyStrings, }, + // TODO: v2.0 rename to `enabled` "is_enabled": { Type: schema.TypeBool, Optional: true, @@ -94,7 +96,7 @@ func applicationOAuth2PermissionResource() *schema.Resource { } func applicationOAuth2PermissionResourceCreateUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient objectId := d.Get("application_object_id").(string) @@ -122,7 +124,7 @@ func applicationOAuth2PermissionResourceCreateUpdate(ctx context.Context, d *sch Value: utils.String(d.Get("value").(string)), } - id := graph.OAuth2PermissionIdFrom(objectId, *permission.ID) + id := parse.NewOAuth2PermissionID(objectId, *permission.ID) tf.LockByName(resourceApplicationName, id.ObjectId) defer tf.UnlockByName(resourceApplicationName, id.ObjectId) @@ -139,19 +141,19 @@ func applicationOAuth2PermissionResourceCreateUpdate(ctx context.Context, d *sch var newPermissions *[]graphrbac.OAuth2Permission if d.IsNewResource() { - newPermissions, err = graph.OAuth2PermissionAdd(app.Oauth2Permissions, &permission) + newPermissions, err = aadgraph.OAuth2PermissionAdd(app.Oauth2Permissions, &permission) if err != nil { - if _, ok := err.(*graph.AlreadyExistsError); ok { + if _, ok := err.(*aadgraph.AlreadyExistsError); ok { return tf.ImportAsExistsDiag("azuread_application_oauth2_permission", id.String()) } return tf.ErrorDiagF(err, "Failed to add OAuth2 Permission") } } else { - if existing, _ := graph.OAuth2PermissionFindById(app, id.PermissionId); existing == nil { + if existing, _ := aadgraph.OAuth2PermissionFindById(app, id.PermissionId); existing == nil { return tf.ErrorDiagPathF(nil, "role_id", "OAuth2 Permission with ID %q was not found for Application %q", id.PermissionId, id.ObjectId) } - newPermissions, err = graph.OAuth2PermissionUpdate(app.Oauth2Permissions, &permission) + newPermissions, err = aadgraph.OAuth2PermissionUpdate(app.Oauth2Permissions, &permission) if err != nil { return tf.ErrorDiagF(err, "Updating OAuth2 Permission with ID %q", *permission.ID) } @@ -170,9 +172,9 @@ func applicationOAuth2PermissionResourceCreateUpdate(ctx context.Context, d *sch } func applicationOAuth2PermissionResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient - id, err := graph.ParseOAuth2PermissionId(d.Id()) + id, err := parse.OAuth2PermissionID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing OAuth2 Permission ID %q", d.Id()) } @@ -189,7 +191,7 @@ func applicationOAuth2PermissionResourceRead(ctx context.Context, d *schema.Reso return tf.ErrorDiagPathF(err, "application_object_id", "Retrieving Application with object ID %q", id.ObjectId) } - permission, err := graph.OAuth2PermissionFindById(app, id.PermissionId) + permission, err := aadgraph.OAuth2PermissionFindById(app, id.PermissionId) if err != nil { return tf.ErrorDiagF(err, "Identifying OAuth2 Permission") } @@ -240,9 +242,9 @@ func applicationOAuth2PermissionResourceRead(ctx context.Context, d *schema.Reso } func applicationOAuth2PermissionResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient - id, err := graph.ParseOAuth2PermissionId(d.Id()) + id, err := parse.OAuth2PermissionID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing OAuth2 Permission ID %q", d.Id()) } @@ -264,7 +266,7 @@ func applicationOAuth2PermissionResourceDelete(ctx context.Context, d *schema.Re var newPermissions *[]graphrbac.OAuth2Permission log.Printf("[DEBUG] Disabling OAuth2 Permission %q for Application %q prior to removal", id.PermissionId, id.ObjectId) - newPermissions, err = graph.OAuth2PermissionResultDisableById(app.Oauth2Permissions, id.PermissionId) + newPermissions, err = aadgraph.OAuth2PermissionResultDisableById(app.Oauth2Permissions, id.PermissionId) if err != nil { return tf.ErrorDiagF(err, "Disabling OAuth2 Permission with ID %q for application %q", id.PermissionId, id.ObjectId) } @@ -277,7 +279,7 @@ func applicationOAuth2PermissionResourceDelete(ctx context.Context, d *schema.Re } log.Printf("[DEBUG] Removing OAuth2 Permission %q for Application %q", id.PermissionId, id.ObjectId) - newPermissions, err = graph.OAuth2PermissionResultRemoveById(app.Oauth2Permissions, id.PermissionId) + newPermissions, err = aadgraph.OAuth2PermissionResultRemoveById(app.Oauth2Permissions, id.PermissionId) if err != nil { return tf.ErrorDiagF(err, "Removing OAuth2 Permission with ID %q for application %q", id.PermissionId, id.ObjectId) } diff --git a/internal/services/aadgraph/application_oauth2_permission_resource_test.go b/internal/services/applications/application_oauth2_permission_resource_test.go similarity index 94% rename from internal/services/aadgraph/application_oauth2_permission_resource_test.go rename to internal/services/applications/application_oauth2_permission_resource_test.go index d511010ce..219342210 100644 --- a/internal/services/aadgraph/application_oauth2_permission_resource_test.go +++ b/internal/services/applications/application_oauth2_permission_resource_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package applications_test import ( "context" @@ -11,7 +11,8 @@ import ( "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance" "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance/check" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/applications/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" ) @@ -96,13 +97,13 @@ func TestAccApplicationOAuth2Permission_requiresImport(t *testing.T) { }) } -func (r ApplicationOAuth2PermissionResource) Exists(ctx context.Context, clients *clients.AadClient, state *terraform.InstanceState) (*bool, error) { - id, err := graph.ParseOAuth2PermissionId(state.ID) +func (r ApplicationOAuth2PermissionResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + id, err := parse.OAuth2PermissionID(state.ID) if err != nil { return nil, fmt.Errorf("parsing OAuth2 Permission ID: %v", err) } - resp, err := clients.AadGraph.ApplicationsClient.Get(ctx, id.ObjectId) + resp, err := clients.Applications.AadClient.Get(ctx, id.ObjectId) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return nil, fmt.Errorf("Application with object ID %q does not exist", id.ObjectId) @@ -110,7 +111,7 @@ func (r ApplicationOAuth2PermissionResource) Exists(ctx context.Context, clients return nil, fmt.Errorf("failed to retrieve Application with object ID %q: %+v", id.ObjectId, err) } - scope, err := graph.OAuth2PermissionFindById(resp, id.PermissionId) + scope, err := aadgraph.OAuth2PermissionFindById(resp, id.PermissionId) if err != nil { return nil, fmt.Errorf("failed to identity OAuth2 Permission: %s", err) } else if scope != nil { diff --git a/internal/services/aadgraph/application_password_resource.go b/internal/services/applications/application_password_resource.go similarity index 86% rename from internal/services/aadgraph/application_password_resource.go rename to internal/services/applications/application_password_resource.go index 534d295bc..4d68bbc75 100644 --- a/internal/services/aadgraph/application_password_resource.go +++ b/internal/services/applications/application_password_resource.go @@ -1,4 +1,4 @@ -package aadgraph +package applications import ( "context" @@ -12,7 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/applications/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" @@ -25,11 +26,11 @@ func applicationPasswordResource() *schema.Resource { DeleteContext: applicationPasswordResourceDelete, Importer: tf.ValidateResourceIDPriorToImport(func(id string) error { - _, err := graph.ParsePasswordId(id) + _, err := parse.PasswordID(id) return err }), - Schema: graph.PasswordResourceSchema("application_object_id"), + Schema: aadgraph.PasswordResourceSchema("application_object_id"), SchemaVersion: 1, StateUpgraders: []schema.StateUpgrader{ @@ -43,19 +44,19 @@ func applicationPasswordResource() *schema.Resource { } func applicationPasswordResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient objectId := d.Get("application_object_id").(string) - cred, err := graph.PasswordCredentialForResource(d) + cred, err := aadgraph.PasswordCredentialForResource(d) if err != nil { attr := "" - if kerr, ok := err.(graph.CredentialError); ok { + if kerr, ok := err.(aadgraph.CredentialError); ok { attr = kerr.Attr() } return tf.ErrorDiagPathF(err, attr, "Generating password credentials for application with object ID %q", objectId) } - id := graph.CredentialIdFrom(objectId, "password", *cred.KeyID) + id := parse.NewCredentialID(objectId, "password", *cred.KeyID) tf.LockByName(resourceApplicationName, id.ObjectId) defer tf.UnlockByName(resourceApplicationName, id.ObjectId) @@ -65,9 +66,9 @@ func applicationPasswordResourceCreate(ctx context.Context, d *schema.ResourceDa return tf.ErrorDiagPathF(err, "application_object_id", "Listing password credentials for application with ID %q", objectId) } - newCreds, err := graph.PasswordCredentialResultAdd(existingCreds, cred) + newCreds, err := aadgraph.PasswordCredentialResultAdd(existingCreds, cred) if err != nil { - if _, ok := err.(*graph.AlreadyExistsError); ok { + if _, ok := err.(*aadgraph.AlreadyExistsError); ok { return tf.ImportAsExistsDiag("azuread_application_password", id.String()) } return tf.ErrorDiagF(err, "Adding application password") @@ -77,7 +78,7 @@ func applicationPasswordResourceCreate(ctx context.Context, d *schema.ResourceDa return tf.ErrorDiagF(err, "Creating password credentials %q for application with object ID %q", id.KeyId, id.ObjectId) } - _, err = graph.WaitForPasswordCredentialReplication(ctx, id.KeyId, d.Timeout(schema.TimeoutCreate), func() (graphrbac.PasswordCredentialListResult, error) { + _, err = aadgraph.WaitForPasswordCredentialReplication(ctx, id.KeyId, d.Timeout(schema.TimeoutCreate), func() (graphrbac.PasswordCredentialListResult, error) { return client.ListPasswordCredentials(ctx, id.ObjectId) }) if err != nil { @@ -90,9 +91,9 @@ func applicationPasswordResourceCreate(ctx context.Context, d *schema.ResourceDa } func applicationPasswordResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient - id, err := graph.ParsePasswordId(d.Id()) + id, err := parse.PasswordID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing password credential with ID %q", d.Id()) } @@ -113,7 +114,7 @@ func applicationPasswordResourceRead(ctx context.Context, d *schema.ResourceData return tf.ErrorDiagPathF(err, "application_object_id", "Listing password credentials for application with object ID %q", id.ObjectId) } - credential := graph.PasswordCredentialResultFindByKeyId(credentials, id.KeyId) + credential := aadgraph.PasswordCredentialResultFindByKeyId(credentials, id.KeyId) if credential == nil { log.Printf("[DEBUG] Password credential %q (ID %q) was not found - removing from state!", id.KeyId, id.ObjectId) d.SetId("") @@ -156,9 +157,9 @@ func applicationPasswordResourceRead(ctx context.Context, d *schema.ResourceData } func applicationPasswordResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient - id, err := graph.ParsePasswordId(d.Id()) + id, err := parse.PasswordID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing password credential with ID %q", d.Id()) } @@ -182,7 +183,7 @@ func applicationPasswordResourceDelete(ctx context.Context, d *schema.ResourceDa return tf.ErrorDiagF(err, "Listing password credentials for application with object ID %q", id.ObjectId) } - newCreds, err := graph.PasswordCredentialResultRemoveByKeyId(existing, id.KeyId) + newCreds, err := aadgraph.PasswordCredentialResultRemoveByKeyId(existing, id.KeyId) if err != nil { return tf.ErrorDiagF(err, "Removing password credential %q from application with object ID %q", id.KeyId, id.ObjectId) } @@ -257,7 +258,7 @@ func resourceApplicationPasswordInstanceResourceV0() *schema.Resource { func resourceApplicationPasswordInstanceStateUpgradeV0(_ context.Context, rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) { log.Println("[DEBUG] Migrating ID from v0 to v1 format") - newId, err := graph.ParseOldPasswordId(rawState["id"].(string)) + newId, err := parse.OldPasswordID(rawState["id"].(string)) if err != nil { return rawState, fmt.Errorf("generating new ID: %s", err) } diff --git a/internal/services/aadgraph/application_password_resource_test.go b/internal/services/applications/application_password_resource_test.go similarity index 91% rename from internal/services/aadgraph/application_password_resource_test.go rename to internal/services/applications/application_password_resource_test.go index f8d6cd171..0c9077ffd 100644 --- a/internal/services/aadgraph/application_password_resource_test.go +++ b/internal/services/applications/application_password_resource_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package applications_test import ( "context" @@ -12,7 +12,8 @@ import ( "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance" "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance/check" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/applications/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" ) @@ -87,13 +88,13 @@ func TestAccApplicationPassword_requiresImport(t *testing.T) { }) } -func (r ApplicationPasswordResource) Exists(ctx context.Context, clients *clients.AadClient, state *terraform.InstanceState) (*bool, error) { - id, err := graph.ParsePasswordId(state.ID) +func (r ApplicationPasswordResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + id, err := parse.PasswordID(state.ID) if err != nil { return nil, fmt.Errorf("parsing Application Password ID: %v", err) } - resp, err := clients.AadGraph.ApplicationsClient.Get(ctx, id.ObjectId) + resp, err := clients.Applications.AadClient.Get(ctx, id.ObjectId) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return nil, fmt.Errorf("Application with object ID %q does not exist", id.ObjectId) @@ -101,12 +102,12 @@ func (r ApplicationPasswordResource) Exists(ctx context.Context, clients *client return nil, fmt.Errorf("failed to retrieve Application with object ID %q: %+v", id.ObjectId, err) } - credentials, err := clients.AadGraph.ApplicationsClient.ListPasswordCredentials(ctx, id.ObjectId) + credentials, err := clients.Applications.AadClient.ListPasswordCredentials(ctx, id.ObjectId) if err != nil { return nil, fmt.Errorf("listing Password Credentials for Application %q: %+v", id.ObjectId, err) } - cred := graph.PasswordCredentialResultFindByKeyId(credentials, id.KeyId) + cred := aadgraph.PasswordCredentialResultFindByKeyId(credentials, id.KeyId) if cred != nil { return utils.Bool(true), nil } diff --git a/internal/services/aadgraph/application_resource.go b/internal/services/applications/application_resource.go similarity index 83% rename from internal/services/aadgraph/application_resource.go rename to internal/services/applications/application_resource.go index 7e3a0cea6..1d95898a0 100644 --- a/internal/services/aadgraph/application_resource.go +++ b/internal/services/applications/application_resource.go @@ -1,4 +1,4 @@ -package aadgraph +package applications import ( "context" @@ -14,7 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" @@ -44,78 +44,22 @@ func applicationResource() *schema.Resource { }), Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: validate.NoEmptyStrings, - }, - - "available_to_other_tenants": { - Type: schema.TypeBool, - Optional: true, - }, - - "group_membership_claims": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{ - string(graphrbac.All), - string(graphrbac.None), - string(graphrbac.SecurityGroup), - "DirectoryRole", // missing from sdk: https://github.com/Azure/azure-sdk-for-go/issues/7857 - "ApplicationGroup", //missing from sdk:https://github.com/Azure/azure-sdk-for-go/issues/8244 - }, false), - }, - - "homepage": { + "display_name": { Type: schema.TypeString, Optional: true, Computed: true, - ValidateDiagFunc: validate.URLIsHTTPOrHTTPS, - }, - - "identifier_uris": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validate.URLIsAppURI, - }, + ExactlyOneOf: []string{"display_name", "name"}, + ValidateDiagFunc: validate.NoEmptyStrings, }, - "logout_url": { + // TODO: v2.0 remove this + "name": { Type: schema.TypeString, Optional: true, - ValidateDiagFunc: validate.URLIsHTTPOrHTTPS, - }, - - "oauth2_allow_implicit_flow": { - Type: schema.TypeBool, - Optional: true, - }, - - "public_client": { - Type: schema.TypeBool, - Optional: true, - Computed: true, - }, - - "reply_urls": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validate.NoEmptyStrings, - }, - }, - - "type": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice([]string{"webapp/api", "native"}, false), - Default: "webapp/api", + Computed: true, + Deprecated: "This property has been renamed to `display_name` and will be removed in version 2.0 of this provider.", + ExactlyOneOf: []string{"display_name", "name"}, + ValidateDiagFunc: validate.NoEmptyStrings, }, "app_role": { @@ -155,6 +99,7 @@ func applicationResource() *schema.Resource { ValidateDiagFunc: validate.NoEmptyStrings, }, + // TODO: v2.0 rename to `enabled` "is_enabled": { Type: schema.TypeBool, Optional: true, @@ -170,6 +115,57 @@ func applicationResource() *schema.Resource { }, }, + // TODO: v2.0 move this to `sign_in_audience` property and support other values + "available_to_other_tenants": { + Type: schema.TypeBool, + Optional: true, + }, + + "group_membership_claims": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + // TODO: v2.0 use SDK constants + "All", + "None", + "SecurityGroup", + "DirectoryRole", // missing from sdk: https://github.com/Azure/azure-sdk-for-go/issues/7857 + "ApplicationGroup", //missing from sdk:https://github.com/Azure/azure-sdk-for-go/issues/8244 + }, false), + }, + + // TODO: v2.0 put this in a `web` block and remove Computed + "homepage": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validate.URLIsHTTPOrHTTPS, + }, + + "identifier_uris": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: validate.URLIsAppURI, + }, + }, + + // TODO: v2.0 put this in a `web` block + "logout_url": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: validate.URLIsHTTPOrHTTPS, + }, + + // TODO: v2.0 put this in an `implicit_grant` block and rename to `access_token_issuance_enabled` + "oauth2_allow_implicit_flow": { + Type: schema.TypeBool, + Optional: true, + }, + + // TODO: v2.0 put this in an `api` block and maybe rename to `oauth2_permission_scope` "oauth2_permissions": { Type: schema.TypeSet, Optional: true, @@ -177,6 +173,12 @@ func applicationResource() *schema.Resource { ConfigMode: schema.SchemaConfigModeAttr, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + // TODO: v2.0 consider removing Computed - users could use random.uuid to generate their own + "id": { + Type: schema.TypeString, + Computed: true, + }, + "admin_consent_description": { Type: schema.TypeString, Optional: true, @@ -191,11 +193,7 @@ func applicationResource() *schema.Resource { ValidateDiagFunc: validate.NoEmptyStrings, }, - "id": { - Type: schema.TypeString, - Computed: true, - }, - + // TODO: v2.0 rename to `enabled` "is_enabled": { Type: schema.TypeBool, Optional: true, @@ -237,14 +235,42 @@ func applicationResource() *schema.Resource { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "access_token": graph.SchemaOptionalClaims(), - "id_token": graph.SchemaOptionalClaims(), + "access_token": schemaOptionalClaims(), + "id_token": schemaOptionalClaims(), // TODO: enable when https://github.com/Azure/azure-sdk-for-go/issues/9714 resolved - //"saml_token": graph.SchemaOptionalClaims(), + // or at v2.0, whichever comes first + //"saml2_token": schemaOptionalClaims(), }, }, }, + "owners": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: validate.NoEmptyStrings, + }, + }, + + // TODO: v2.0 rename this to `fallback_public_client` and remove Computed + "public_client": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + + "reply_urls": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: validate.NoEmptyStrings, + }, + }, + "required_resource_access": { Type: schema.TypeSet, Optional: true, @@ -281,14 +307,13 @@ func applicationResource() *schema.Resource { }, }, - "owners": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateDiagFunc: validate.NoEmptyStrings, - }, + // TODO: v2.0 drop this, there's no such distinction any more + "type": { + Type: schema.TypeString, + Optional: true, + Deprecated: "This property is deprecated and will be removed in version 2.0 of this provider.", + ValidateFunc: validation.StringInSlice([]string{"webapp/api", "native"}, false), + Default: "webapp/api", }, "application_id": { @@ -311,12 +336,12 @@ func applicationResource() *schema.Resource { } func applicationResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient name := d.Get("name").(string) if d.Get("prevent_duplicate_names").(bool) { - existingApp, err := graph.ApplicationFindByName(ctx, client, name) + existingApp, err := aadgraph.ApplicationFindByName(ctx, client, name) if err != nil { return tf.ErrorDiagPathF(err, "name", "Could not check for existing application(s)") } @@ -348,8 +373,8 @@ func applicationResourceCreate(ctx context.Context, d *schema.ResourceData, meta IdentifierUris: tf.ExpandStringSlicePtr(identUrls.([]interface{})), ReplyUrls: tf.ExpandStringSlicePtr(d.Get("reply_urls").(*schema.Set).List()), AvailableToOtherTenants: utils.Bool(d.Get("available_to_other_tenants").(bool)), - RequiredResourceAccess: expandApplicationRequiredResourceAccess(d), - OptionalClaims: expandApplicationOptionalClaims(d), + RequiredResourceAccess: expandApplicationRequiredResourceAccessAad(d), + OptionalClaims: expandApplicationOptionalClaimsAad(d), } if v, ok := d.GetOk("homepage"); ok { @@ -382,7 +407,7 @@ func applicationResourceCreate(ctx context.Context, d *schema.ResourceData, meta d.SetId(*app.ObjectID) - _, err = graph.WaitForCreationReplication(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { + _, err = aadgraph.WaitForCreationReplication(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { return client.Get(ctx, *app.ObjectID) }) if err != nil { @@ -390,7 +415,7 @@ func applicationResourceCreate(ctx context.Context, d *schema.ResourceData, meta } // follow suggested hack for azure-cli - // AAD graph doesn't have the API to create a native app, aka public client, the recommended hack is + // AAD aadgraph doesn't have the API to create a native app, aka public client, the recommended hack is // to create a web app first, then convert to a native one if appType == "native" { properties := graphrbac.ApplicationUpdateParameters{ @@ -404,18 +429,18 @@ func applicationResourceCreate(ctx context.Context, d *schema.ResourceData, meta } if v, ok := d.GetOk("app_role"); ok { - appRoles := expandApplicationAppRoles(v) + appRoles := expandApplicationAppRolesAad(v) if appRoles != nil { - if err := graph.AppRolesSet(ctx, client, *app.ObjectID, appRoles); err != nil { + if err := aadgraph.AppRolesSet(ctx, client, *app.ObjectID, appRoles); err != nil { return tf.ErrorDiagPathF(err, "app_role", "Could not set App Roles") } } } if v, ok := d.GetOk("oauth2_permissions"); ok { - oauth2Permissions := expandApplicationOAuth2Permissions(v) + oauth2Permissions := expandApplicationOAuth2PermissionsAad(v) if oauth2Permissions != nil { - if err := graph.OAuth2PermissionsSet(ctx, client, *app.ObjectID, oauth2Permissions); err != nil { + if err := aadgraph.OAuth2PermissionsSet(ctx, client, *app.ObjectID, oauth2Permissions); err != nil { return tf.ErrorDiagPathF(err, "oauth2_permissions", "Could not set OAuth2 Permissions") } } @@ -423,7 +448,7 @@ func applicationResourceCreate(ctx context.Context, d *schema.ResourceData, meta if v, ok := d.GetOk("owners"); ok { desiredOwners := *tf.ExpandStringSlicePtr(v.(*schema.Set).List()) - if err := applicationSetOwnersTo(ctx, client, *app.ObjectID, desiredOwners); err != nil { + if err := aadgraph.ApplicationSetOwnersTo(ctx, client, *app.ObjectID, desiredOwners); err != nil { return tf.ErrorDiagPathF(err, "owners", "Could not set Owners") } } @@ -432,12 +457,12 @@ func applicationResourceCreate(ctx context.Context, d *schema.ResourceData, meta } func applicationResourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient name := d.Get("name").(string) if d.HasChange("name") && d.Get("prevent_duplicate_names").(bool) { - existingApp, err := graph.ApplicationFindByName(ctx, client, name) + existingApp, err := aadgraph.ApplicationFindByName(ctx, client, name) if err != nil { return tf.ErrorDiagPathF(err, "name", "Could not check for existing application(s)") } @@ -488,11 +513,11 @@ func applicationResourceUpdate(ctx context.Context, d *schema.ResourceData, meta } if d.HasChange("required_resource_access") { - properties.RequiredResourceAccess = expandApplicationRequiredResourceAccess(d) + properties.RequiredResourceAccess = expandApplicationRequiredResourceAccessAad(d) } if d.HasChange("optional_claims") { - properties.OptionalClaims = expandApplicationOptionalClaims(d) + properties.OptionalClaims = expandApplicationOptionalClaimsAad(d) } if d.HasChange("group_membership_claims") { @@ -519,18 +544,18 @@ func applicationResourceUpdate(ctx context.Context, d *schema.ResourceData, meta } if d.HasChange("app_role") { - appRoles := expandApplicationAppRoles(d.Get("app_role")) + appRoles := expandApplicationAppRolesAad(d.Get("app_role")) if appRoles != nil { - if err := graph.AppRolesSet(ctx, client, d.Id(), appRoles); err != nil { + if err := aadgraph.AppRolesSet(ctx, client, d.Id(), appRoles); err != nil { return tf.ErrorDiagPathF(err, "app_role", "Could not set App Roles") } } } if d.HasChange("oauth2_permissions") { - oauth2Permissions := expandApplicationOAuth2Permissions(d.Get("oauth2_permissions")) + oauth2Permissions := expandApplicationOAuth2PermissionsAad(d.Get("oauth2_permissions")) if oauth2Permissions != nil { - if err := graph.OAuth2PermissionsSet(ctx, client, d.Id(), oauth2Permissions); err != nil { + if err := aadgraph.OAuth2PermissionsSet(ctx, client, d.Id(), oauth2Permissions); err != nil { return tf.ErrorDiagPathF(err, "oauth2_permissions", "Could not set OAuth2 Permissions") } } @@ -538,7 +563,7 @@ func applicationResourceUpdate(ctx context.Context, d *schema.ResourceData, meta if d.HasChange("owners") { desiredOwners := *tf.ExpandStringSlicePtr(d.Get("owners").(*schema.Set).List()) - if err := applicationSetOwnersTo(ctx, client, d.Id(), desiredOwners); err != nil { + if err := aadgraph.ApplicationSetOwnersTo(ctx, client, d.Id(), desiredOwners); err != nil { return tf.ErrorDiagPathF(err, "owners", "Could not set Owners") } } @@ -547,7 +572,7 @@ func applicationResourceUpdate(ctx context.Context, d *schema.ResourceData, meta } func applicationResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient app, err := client.Get(ctx, d.Id()) if err != nil { @@ -557,7 +582,7 @@ func applicationResourceRead(ctx context.Context, d *schema.ResourceData, meta i return nil } - return tf.ErrorDiagPathF(err, "application_object_id", "Retrieving Application with object ID %q", d.Id()) + return tf.ErrorDiagPathF(err, "id", "Retrieving Application with object ID %q", d.Id()) } if dg := tf.Set(d, "object_id", app.ObjectID); dg != nil { @@ -615,23 +640,23 @@ func applicationResourceRead(ctx context.Context, d *schema.ResourceData, meta i return dg } - if dg := tf.Set(d, "required_resource_access", flattenApplicationRequiredResourceAccess(app.RequiredResourceAccess)); dg != nil { + if dg := tf.Set(d, "required_resource_access", flattenApplicationRequiredResourceAccessAad(app.RequiredResourceAccess)); dg != nil { return dg } - if dg := tf.Set(d, "optional_claims", flattenApplicationOptionalClaims(app.OptionalClaims)); dg != nil { + if dg := tf.Set(d, "optional_claims", flattenApplicationOptionalClaimsAad(app.OptionalClaims)); dg != nil { return dg } - if dg := tf.Set(d, "app_role", graph.FlattenAppRoles(app.AppRoles)); dg != nil { + if dg := tf.Set(d, "app_role", aadgraph.FlattenAppRoles(app.AppRoles)); dg != nil { return dg } - if dg := tf.Set(d, "oauth2_permissions", graph.FlattenOauth2Permissions(app.Oauth2Permissions)); dg != nil { + if dg := tf.Set(d, "oauth2_permissions", aadgraph.FlattenOauth2Permissions(app.Oauth2Permissions)); dg != nil { return dg } - owners, err := graph.ApplicationAllOwners(ctx, client, d.Id()) + owners, err := aadgraph.ApplicationAllOwners(ctx, client, d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "owners", "Could not retrieve owners for application with object ID %q", *app.ObjectID) } @@ -651,7 +676,7 @@ func applicationResourceRead(ctx context.Context, d *schema.ResourceData, meta i } func applicationResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ApplicationsClient + client := meta.(*clients.Client).Applications.AadClient // in order to delete an application which is available to other tenants, we first have to disable this setting availableToOtherTenants := d.Get("available_to_other_tenants").(bool) @@ -676,7 +701,7 @@ func applicationResourceDelete(ctx context.Context, d *schema.ResourceData, meta return nil } -func expandApplicationRequiredResourceAccess(d *schema.ResourceData) *[]graphrbac.RequiredResourceAccess { +func expandApplicationRequiredResourceAccessAad(d *schema.ResourceData) *[]graphrbac.RequiredResourceAccess { requiredResourcesAccesses := d.Get("required_resource_access").(*schema.Set).List() result := make([]graphrbac.RequiredResourceAccess, 0) @@ -687,7 +712,7 @@ func expandApplicationRequiredResourceAccess(d *schema.ResourceData) *[]graphrba result = append(result, graphrbac.RequiredResourceAccess{ ResourceAppID: &resource_app_id, - ResourceAccess: expandApplicationResourceAccess( + ResourceAccess: expandApplicationResourceAccessAad( requiredResourceAccess["resource_access"].([]interface{}), ), }, @@ -696,7 +721,7 @@ func expandApplicationRequiredResourceAccess(d *schema.ResourceData) *[]graphrba return &result } -func expandApplicationResourceAccess(in []interface{}) *[]graphrbac.ResourceAccess { +func expandApplicationResourceAccessAad(in []interface{}) *[]graphrbac.ResourceAccess { resourceAccesses := make([]graphrbac.ResourceAccess, 0, len(in)) for _, resourceAccessRaw := range in { resourceAccess := resourceAccessRaw.(map[string]interface{}) @@ -715,7 +740,7 @@ func expandApplicationResourceAccess(in []interface{}) *[]graphrbac.ResourceAcce return &resourceAccesses } -func flattenApplicationRequiredResourceAccess(in *[]graphrbac.RequiredResourceAccess) []map[string]interface{} { +func flattenApplicationRequiredResourceAccessAad(in *[]graphrbac.RequiredResourceAccess) []map[string]interface{} { if in == nil { return []map[string]interface{}{} } @@ -727,7 +752,7 @@ func flattenApplicationRequiredResourceAccess(in *[]graphrbac.RequiredResourceAc resource["resource_app_id"] = *requiredResourceAccess.ResourceAppID } - resource["resource_access"] = flattenApplicationResourceAccess(requiredResourceAccess.ResourceAccess) + resource["resource_access"] = flattenApplicationResourceAccessAad(requiredResourceAccess.ResourceAccess) result = append(result, resource) } @@ -735,7 +760,7 @@ func flattenApplicationRequiredResourceAccess(in *[]graphrbac.RequiredResourceAc return result } -func flattenApplicationResourceAccess(in *[]graphrbac.ResourceAccess) []interface{} { +func flattenApplicationResourceAccessAad(in *[]graphrbac.ResourceAccess) []interface{} { if in == nil { return []interface{}{} } @@ -755,20 +780,20 @@ func flattenApplicationResourceAccess(in *[]graphrbac.ResourceAccess) []interfac return accesses } -func expandApplicationOptionalClaims(d *schema.ResourceData) *graphrbac.OptionalClaims { +func expandApplicationOptionalClaimsAad(d *schema.ResourceData) *graphrbac.OptionalClaims { result := graphrbac.OptionalClaims{} for _, raw := range d.Get("optional_claims").([]interface{}) { optionalClaims := raw.(map[string]interface{}) - result.AccessToken = expandApplicationOptionalClaim(optionalClaims["access_token"].([]interface{})) - result.IDToken = expandApplicationOptionalClaim(optionalClaims["id_token"].([]interface{})) + result.AccessToken = expandApplicationOptionalClaimAad(optionalClaims["access_token"].([]interface{})) + result.IDToken = expandApplicationOptionalClaimAad(optionalClaims["id_token"].([]interface{})) // TODO: enable when https://github.com/Azure/azure-sdk-for-go/issues/9714 resolved - //result.SamlToken = expandApplicationOptionalClaim(optionalClaims["saml_token"].([]interface{})) + //result.SamlToken = expandApplicationOptionalClaim(optionalClaims["saml2_token"].([]interface{})) } return &result } -func expandApplicationOptionalClaim(in []interface{}) *[]graphrbac.OptionalClaim { +func expandApplicationOptionalClaimAad(in []interface{}) *[]graphrbac.OptionalClaim { optionalClaims := make([]graphrbac.OptionalClaim, 0, len(in)) for _, optionalClaimRaw := range in { optionalClaim := optionalClaimRaw.(map[string]interface{}) @@ -799,7 +824,7 @@ func expandApplicationOptionalClaim(in []interface{}) *[]graphrbac.OptionalClaim return &optionalClaims } -func flattenApplicationOptionalClaims(in *graphrbac.OptionalClaims) interface{} { +func flattenApplicationOptionalClaimsAad(in *graphrbac.OptionalClaims) interface{} { var result []map[string]interface{} if in == nil { @@ -807,10 +832,10 @@ func flattenApplicationOptionalClaims(in *graphrbac.OptionalClaims) interface{} } optionalClaims := make(map[string]interface{}) - if claims := flattenApplicationOptionalClaimsList(in.AccessToken); len(claims) > 0 { + if claims := flattenApplicationOptionalClaimsListAad(in.AccessToken); len(claims) > 0 { optionalClaims["access_token"] = claims } - if claims := flattenApplicationOptionalClaimsList(in.IDToken); len(claims) > 0 { + if claims := flattenApplicationOptionalClaimsListAad(in.IDToken); len(claims) > 0 { optionalClaims["id_token"] = claims } // TODO: enable when https://github.com/Azure/azure-sdk-for-go/issues/9714 resolved @@ -824,7 +849,7 @@ func flattenApplicationOptionalClaims(in *graphrbac.OptionalClaims) interface{} return result } -func flattenApplicationOptionalClaimsList(in *[]graphrbac.OptionalClaim) []interface{} { +func flattenApplicationOptionalClaimsListAad(in *[]graphrbac.OptionalClaim) []interface{} { if in == nil { return []interface{}{} } @@ -854,7 +879,7 @@ func flattenApplicationOptionalClaimsList(in *[]graphrbac.OptionalClaim) []inter return optionalClaims } -func expandApplicationAppRoles(i interface{}) *[]graphrbac.AppRole { +func expandApplicationAppRolesAad(i interface{}) *[]graphrbac.AppRole { input := i.(*schema.Set).List() output := make([]graphrbac.AppRole, 0, len(input)) @@ -895,7 +920,7 @@ func expandApplicationAppRoles(i interface{}) *[]graphrbac.AppRole { return &output } -func expandApplicationOAuth2Permissions(i interface{}) *[]graphrbac.OAuth2Permission { +func expandApplicationOAuth2PermissionsAad(i interface{}) *[]graphrbac.OAuth2Permission { input := i.(*schema.Set).List() result := make([]graphrbac.OAuth2Permission, 0) @@ -931,32 +956,6 @@ func expandApplicationOAuth2Permissions(i interface{}) *[]graphrbac.OAuth2Permis return &result } -func applicationSetOwnersTo(ctx context.Context, client *graphrbac.ApplicationsClient, id string, desiredOwners []string) error { - existingOwners, err := graph.ApplicationAllOwners(ctx, client, id) - if err != nil { - return err - } - - ownersForRemoval := utils.Difference(existingOwners, desiredOwners) - ownersToAdd := utils.Difference(desiredOwners, existingOwners) - - // add owners first to prevent a possible situation where terraform revokes its own access before adding it back. - if err := graph.ApplicationAddOwners(ctx, client, id, ownersToAdd); err != nil { - return err - } - - for _, ownerToDelete := range ownersForRemoval { - log.Printf("[DEBUG] Removing owner with id %q from Application with id %q", ownerToDelete, id) - if resp, err := client.RemoveOwner(ctx, id, ownerToDelete); err != nil { - if !utils.ResponseWasNotFound(resp) { - return fmt.Errorf("deleting owner %q from Application with ID %q: %+v", ownerToDelete, id, err) - } - } - } - - return nil -} - func applicationValidateRolesScopes(appRoles, oauth2Permissions []interface{}) error { var values []string diff --git a/internal/services/aadgraph/application_resource_test.go b/internal/services/applications/application_resource_test.go similarity index 98% rename from internal/services/aadgraph/application_resource_test.go rename to internal/services/applications/application_resource_test.go index 71a0ca6aa..771ff9e6e 100644 --- a/internal/services/aadgraph/application_resource_test.go +++ b/internal/services/applications/application_resource_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package applications_test import ( "context" @@ -411,8 +411,8 @@ func TestAccApplication_ownersUpdate(t *testing.T) { }) } -func (r ApplicationResource) Exists(ctx context.Context, clients *clients.AadClient, state *terraform.InstanceState) (*bool, error) { - resp, err := clients.AadGraph.ApplicationsClient.Get(ctx, state.ID) +func (r ApplicationResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + resp, err := clients.Applications.AadClient.Get(ctx, state.ID) if err != nil { if utils.ResponseWasNotFound(resp.Response) { @@ -420,8 +420,9 @@ func (r ApplicationResource) Exists(ctx context.Context, clients *clients.AadCli } return nil, fmt.Errorf("failed to retrieve Application with object ID %q: %+v", state.ID, err) } + id := resp.ObjectID - return utils.Bool(resp.ObjectID != nil && *resp.ObjectID == state.ID), nil + return utils.Bool(id != nil && *id == state.ID), nil } func (ApplicationResource) basic(data acceptance.TestData) string { @@ -439,6 +440,7 @@ resource "azuread_application" "test" { identifier_uris = [] oauth2_permissions = [] reply_urls = [] + owners = [] group_membership_claims = "None" } `, data.RandomString) diff --git a/internal/services/applications/client/client.go b/internal/services/applications/client/client.go new file mode 100644 index 000000000..d2e7c6ddc --- /dev/null +++ b/internal/services/applications/client/client.go @@ -0,0 +1,19 @@ +package client + +import ( + "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" + "github.com/terraform-providers/terraform-provider-azuread/internal/common" +) + +type Client struct { + AadClient *graphrbac.ApplicationsClient +} + +func NewClient(o *common.ClientOptions) *Client { + aadClient := graphrbac.NewApplicationsClientWithBaseURI(o.AadGraphEndpoint, o.TenantID) + o.ConfigureClient(&aadClient.Client, o.AadGraphAuthorizer) + + return &Client{ + AadClient: &aadClient, + } +} diff --git a/internal/services/applications/parse/app_role.go b/internal/services/applications/parse/app_role.go new file mode 100644 index 000000000..0faa8d8b4 --- /dev/null +++ b/internal/services/applications/parse/app_role.go @@ -0,0 +1,31 @@ +package parse + +import "fmt" + +type AppRoleId struct { + ObjectId string + RoleId string +} + +func NewAppRoleID(objectId, roleId string) AppRoleId { + return AppRoleId{ + ObjectId: objectId, + RoleId: roleId, + } +} + +func (id AppRoleId) String() string { + return id.ObjectId + "/role/" + id.RoleId +} + +func AppRoleID(idString string) (*AppRoleId, error) { + id, err := ObjectSubResourceID(idString, "role") + if err != nil { + return nil, fmt.Errorf("unable to parse App Role ID: %v", err) + } + + return &AppRoleId{ + ObjectId: id.objectId, + RoleId: id.subId, + }, nil +} diff --git a/internal/services/applications/parse/credentials.go b/internal/services/applications/parse/credentials.go new file mode 100644 index 000000000..0e4674b42 --- /dev/null +++ b/internal/services/applications/parse/credentials.go @@ -0,0 +1,60 @@ +package parse + +import ( + "fmt" + "strings" +) + +type CredentialId struct { + ObjectId string + KeyType string + KeyId string +} + +func NewCredentialID(objectId, keyType, keyId string) CredentialId { + return CredentialId{ + ObjectId: objectId, + KeyType: keyType, + KeyId: keyId, + } +} + +func (id CredentialId) String() string { + return id.ObjectId + "/" + id.KeyType + "/" + id.KeyId +} + +func CertificateID(idString string) (*CredentialId, error) { + id, err := ObjectSubResourceID(idString, "certificate") + if err != nil { + return nil, fmt.Errorf("unable to parse Certificate ID: %v", err) + } + + return &CredentialId{ + ObjectId: id.objectId, + KeyType: id.Type, + KeyId: id.subId, + }, nil +} + +func PasswordID(idString string) (*CredentialId, error) { + id, err := ObjectSubResourceID(idString, "password") + if err != nil { + return nil, fmt.Errorf("unable to parse Password ID: %v", err) + } + + return &CredentialId{ + ObjectId: id.objectId, + KeyType: id.Type, + KeyId: id.subId, + }, nil +} + +func OldPasswordID(id string) (*CredentialId, error) { + parts := strings.Split(id, "/") + if len(parts) != 2 { + return nil, fmt.Errorf("Password ID expected to be in the format {objectId}/{keyId} - but got %q", id) + } + + newId := parts[0] + "/password/" + parts[1] + return PasswordID(newId) +} diff --git a/internal/services/applications/parse/oauth2_permission.go b/internal/services/applications/parse/oauth2_permission.go new file mode 100644 index 000000000..7ff4c18b6 --- /dev/null +++ b/internal/services/applications/parse/oauth2_permission.go @@ -0,0 +1,31 @@ +package parse + +import "fmt" + +type OAuth2PermissionId struct { + ObjectId string + PermissionId string +} + +func NewOAuth2PermissionID(objectId, permissionId string) OAuth2PermissionId { + return OAuth2PermissionId{ + ObjectId: objectId, + PermissionId: permissionId, + } +} + +func (id OAuth2PermissionId) String() string { + return id.ObjectId + "/scope/" + id.PermissionId +} + +func OAuth2PermissionID(idString string) (*OAuth2PermissionId, error) { + id, err := ObjectSubResourceID(idString, "scope") + if err != nil { + return nil, fmt.Errorf("unable to parse OAuth2 Permission ID: %v", err) + } + + return &OAuth2PermissionId{ + ObjectId: id.objectId, + PermissionId: id.subId, + }, nil +} diff --git a/internal/services/aadgraph/graph/object_resource.go b/internal/services/applications/parse/object.go similarity index 86% rename from internal/services/aadgraph/graph/object_resource.go rename to internal/services/applications/parse/object.go index 60e269b2d..9792fb6ba 100644 --- a/internal/services/aadgraph/graph/object_resource.go +++ b/internal/services/applications/parse/object.go @@ -1,4 +1,4 @@ -package graph +package parse import ( "fmt" @@ -13,11 +13,19 @@ type ObjectSubResourceId struct { Type string } +func NewObjectSubResourceID(objectId, typeId, subId string) ObjectSubResourceId { + return ObjectSubResourceId{ + objectId: objectId, + Type: typeId, + subId: subId, + } +} + func (id ObjectSubResourceId) String() string { return fmt.Sprintf("%s/%s/%s", id.objectId, id.Type, id.subId) } -func ParseObjectSubResourceId(idString, expectedType string) (*ObjectSubResourceId, error) { +func ObjectSubResourceID(idString, expectedType string) (*ObjectSubResourceId, error) { parts := strings.Split(idString, "/") if len(parts) != 3 { return nil, fmt.Errorf("Object Resource ID should be in the format {objectId}/{type}/{subId} - but got %q", idString) @@ -47,11 +55,3 @@ func ParseObjectSubResourceId(idString, expectedType string) (*ObjectSubResource return &id, nil } - -func ObjectSubResourceIdFrom(objectId, typeId, subId string) ObjectSubResourceId { - return ObjectSubResourceId{ - objectId: objectId, - Type: typeId, - subId: subId, - } -} diff --git a/internal/services/aadgraph/0registration.go b/internal/services/applications/registration.go similarity index 56% rename from internal/services/aadgraph/0registration.go rename to internal/services/applications/registration.go index 94eb1b5e7..e051c23a4 100644 --- a/internal/services/aadgraph/0registration.go +++ b/internal/services/applications/registration.go @@ -1,4 +1,4 @@ -package aadgraph +package applications import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -8,27 +8,20 @@ type Registration struct{} // Name is the name of this Service func (r Registration) Name() string { - return "AAD Graph" + return "Applications" } // WebsiteCategories returns a list of categories which can be used for the sidebar func (r Registration) WebsiteCategories() []string { return []string{ - "AAD Graph", + "Applications", } } // SupportedDataSources returns the supported Data Sources supported by this Service func (r Registration) SupportedDataSources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "azuread_application": applicationData(), - "azuread_domains": domainsData(), - "azuread_client_config": clientConfigData(), - "azuread_group": groupData(), - "azuread_groups": groupsData(), - "azuread_service_principal": servicePrincipalData(), - "azuread_user": userData(), - "azuread_users": usersData(), + "azuread_application": applicationDataSource(), } } @@ -40,11 +33,5 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azuread_application_certificate": applicationCertificateResource(), "azuread_application_oauth2_permission": applicationOAuth2PermissionResource(), "azuread_application_password": applicationPasswordResource(), - "azuread_group": groupResource(), - "azuread_group_member": groupMemberResource(), - "azuread_service_principal": servicePrincipalResource(), - "azuread_service_principal_certificate": servicePrincipalCertificateResource(), - "azuread_service_principal_password": servicePrincipalPasswordResource(), - "azuread_user": userResource(), } } diff --git a/internal/services/applications/schema.go b/internal/services/applications/schema.go new file mode 100644 index 000000000..6f2736c67 --- /dev/null +++ b/internal/services/applications/schema.go @@ -0,0 +1,51 @@ +package applications + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func schemaOptionalClaims() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "source": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice( + []string{"user"}, + false, + ), + }, + "essential": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "additional_properties": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice( + []string{ + "dns_domain_and_sam_account_name", + "emit_as_roles", + "netbios_domain_and_sam_account_name", + "sam_account_name", + }, + false, + ), + }, + }, + }, + }, + } +} diff --git a/internal/services/domains/client/client.go b/internal/services/domains/client/client.go new file mode 100644 index 000000000..fa0b47a68 --- /dev/null +++ b/internal/services/domains/client/client.go @@ -0,0 +1,19 @@ +package client + +import ( + "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" + "github.com/terraform-providers/terraform-provider-azuread/internal/common" +) + +type Client struct { + AadClient *graphrbac.DomainsClient +} + +func NewClient(o *common.ClientOptions) *Client { + aadClient := graphrbac.NewDomainsClientWithBaseURI(o.AadGraphEndpoint, o.TenantID) + o.ConfigureClient(&aadClient.Client, o.AadGraphAuthorizer) + + return &Client{ + AadClient: &aadClient, + } +} diff --git a/internal/services/aadgraph/domains_data_source.go b/internal/services/domains/domains_data_source.go similarity index 93% rename from internal/services/aadgraph/domains_data_source.go rename to internal/services/domains/domains_data_source.go index 013a2ebc0..03ffeb292 100644 --- a/internal/services/aadgraph/domains_data_source.go +++ b/internal/services/domains/domains_data_source.go @@ -1,4 +1,4 @@ -package aadgraph +package domains import ( "context" @@ -14,7 +14,7 @@ import ( func domainsData() *schema.Resource { return &schema.Resource{ - ReadContext: domainsDataRead, + ReadContext: domainsDataSourceRead, Schema: map[string]*schema.Schema{ "include_unverified": { @@ -64,9 +64,9 @@ func domainsData() *schema.Resource { } } -func domainsDataRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - tenantId := meta.(*clients.AadClient).TenantID - client := meta.(*clients.AadClient).AadGraph.DomainsClient +func domainsDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + tenantId := meta.(*clients.Client).TenantID + client := meta.(*clients.Client).Domains.AadClient includeUnverified := d.Get("include_unverified").(bool) onlyDefault := d.Get("only_default").(bool) diff --git a/internal/services/aadgraph/domains_data_source_test.go b/internal/services/domains/domains_data_source_test.go similarity index 99% rename from internal/services/aadgraph/domains_data_source_test.go rename to internal/services/domains/domains_data_source_test.go index de13bec3f..5c08ac9cd 100644 --- a/internal/services/aadgraph/domains_data_source_test.go +++ b/internal/services/domains/domains_data_source_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package domains_test import ( "testing" diff --git a/internal/services/domains/registration.go b/internal/services/domains/registration.go new file mode 100644 index 000000000..750de61d6 --- /dev/null +++ b/internal/services/domains/registration.go @@ -0,0 +1,31 @@ +package domains + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type Registration struct{} + +// Name is the name of this Service +func (r Registration) Name() string { + return "Domains" +} + +// WebsiteCategories returns a list of categories which can be used for the sidebar +func (r Registration) WebsiteCategories() []string { + return []string{ + "Domains", + } +} + +// SupportedDataSources returns the supported Data Sources supported by this Service +func (r Registration) SupportedDataSources() map[string]*schema.Resource { + return map[string]*schema.Resource{ + "azuread_domains": domainsData(), + } +} + +// SupportedResources returns the supported Resources supported by this Service +func (r Registration) SupportedResources() map[string]*schema.Resource { + return map[string]*schema.Resource{} +} diff --git a/internal/services/groups/client/client.go b/internal/services/groups/client/client.go new file mode 100644 index 000000000..05a07d740 --- /dev/null +++ b/internal/services/groups/client/client.go @@ -0,0 +1,19 @@ +package client + +import ( + "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" + "github.com/terraform-providers/terraform-provider-azuread/internal/common" +) + +type Client struct { + AadClient *graphrbac.GroupsClient +} + +func NewClient(o *common.ClientOptions) *Client { + aadClient := graphrbac.NewGroupsClientWithBaseURI(o.AadGraphEndpoint, o.TenantID) + o.ConfigureClient(&aadClient.Client, o.AadGraphAuthorizer) + + return &Client{ + AadClient: &aadClient, + } +} diff --git a/internal/services/aadgraph/group_data_source.go b/internal/services/groups/group_data_source.go similarity index 70% rename from internal/services/aadgraph/group_data_source.go rename to internal/services/groups/group_data_source.go index bbef0f5c0..4137b567f 100644 --- a/internal/services/aadgraph/group_data_source.go +++ b/internal/services/groups/group_data_source.go @@ -1,4 +1,4 @@ -package aadgraph +package groups import ( "context" @@ -9,15 +9,15 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" ) -func groupData() *schema.Resource { +func groupDataSource() *schema.Resource { return &schema.Resource{ - ReadContext: groupDataRead, + ReadContext: groupDataSourceRead, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, @@ -28,8 +28,8 @@ func groupData() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, + ExactlyOneOf: []string{"display_name", "name", "object_id"}, ValidateDiagFunc: validate.UUID, - ExactlyOneOf: []string{"name"}, }, "description": { @@ -37,12 +37,21 @@ func groupData() *schema.Resource { Computed: true, }, + "display_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"display_name", "name", "object_id"}, + ValidateDiagFunc: validate.NoEmptyStrings, + }, + "name": { Type: schema.TypeString, Optional: true, Computed: true, + Deprecated: "This property has been renamed to `display_name` and will be removed in v2.0 of this provider.", + ExactlyOneOf: []string{"display_name", "name", "object_id"}, ValidateDiagFunc: validate.NoEmptyStrings, - ExactlyOneOf: []string{"object_id"}, }, "members": { @@ -60,10 +69,17 @@ func groupData() *schema.Resource { } } -func groupDataRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.GroupsClient +func groupDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*clients.Client).Groups.AadClient var group graphrbac.ADGroup + var name string + + if v, ok := d.GetOk("display_name"); ok { + name = v.(string) + } else if v, ok := d.GetOk("name"); ok { + name = v.(string) + } if objectId, ok := d.Get("object_id").(string); ok && objectId != "" { resp, err := client.Get(ctx, objectId) @@ -76,14 +92,12 @@ func groupDataRead(ctx context.Context, d *schema.ResourceData, meta interface{} } group = resp - } else if name, ok := d.Get("name").(string); ok && name != "" { - g, err := graph.GroupGetByDisplayName(ctx, client, name) + } else if name != "" { + g, err := aadgraph.GroupGetByDisplayName(ctx, client, name) if err != nil { return tf.ErrorDiagPathF(err, "name", "No group found with display name: %q", name) } group = *g - } else { - return tf.ErrorDiagF(nil, "One of `object_id` or `name` must be specified") } if group.ObjectID == nil { @@ -96,6 +110,10 @@ func groupDataRead(ctx context.Context, d *schema.ResourceData, meta interface{} return dg } + if dg := tf.Set(d, "display_name", group.DisplayName); dg != nil { + return dg + } + if dg := tf.Set(d, "name", group.DisplayName); dg != nil { return dg } @@ -108,7 +126,7 @@ func groupDataRead(ctx context.Context, d *schema.ResourceData, meta interface{} return dg } - members, err := graph.GroupAllMembers(ctx, client, d.Id()) + members, err := aadgraph.GroupAllMembers(ctx, client, d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "owners", "Could not retrieve members for group with object ID %q", d.Id()) } @@ -117,7 +135,7 @@ func groupDataRead(ctx context.Context, d *schema.ResourceData, meta interface{} return dg } - owners, err := graph.GroupAllOwners(ctx, client, d.Id()) + owners, err := aadgraph.GroupAllOwners(ctx, client, d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "owners", "Could not retrieve owners for group with object ID %q", d.Id()) } diff --git a/internal/services/aadgraph/group_data_source_test.go b/internal/services/groups/group_data_source_test.go similarity index 83% rename from internal/services/aadgraph/group_data_source_test.go rename to internal/services/groups/group_data_source_test.go index d212067f7..39da42b18 100644 --- a/internal/services/aadgraph/group_data_source_test.go +++ b/internal/services/groups/group_data_source_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package groups_test import ( "fmt" @@ -25,6 +25,19 @@ func TestAccGroupDataSource_byName(t *testing.T) { }) } +func TestAccGroupDataSource_byNameDeprecated(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_group", "test") + + data.DataSourceTest(t, []resource.TestStep{ + { + Config: GroupDataSource{}.nameDeprecated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("name").HasValue(fmt.Sprintf("acctestGroup-%d", data.RandomInteger)), + ), + }, + }) +} + func TestAccGroupDataSource_byCaseInsensitiveName(t *testing.T) { data := acceptance.BuildTestData(t, "data.azuread_group", "test") @@ -83,6 +96,16 @@ func (GroupDataSource) name(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s +data "azuread_group" "test" { + display_name = azuread_group.test.name +} +`, GroupResource{}.basic(data)) +} + +func (GroupDataSource) nameDeprecated(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + data "azuread_group" "test" { name = azuread_group.test.name } @@ -93,7 +116,7 @@ func (GroupDataSource) caseInsensitiveName(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s data "azuread_group" "test" { - name = upper(azuread_group.test.name) + display_name = upper(azuread_group.test.name) } `, GroupResource{}.basic(data)) } diff --git a/internal/services/aadgraph/group_member_resource.go b/internal/services/groups/group_member_resource.go similarity index 77% rename from internal/services/aadgraph/group_member_resource.go rename to internal/services/groups/group_member_resource.go index 8b149e0d7..0854f6d35 100644 --- a/internal/services/aadgraph/group_member_resource.go +++ b/internal/services/groups/group_member_resource.go @@ -1,4 +1,4 @@ -package aadgraph +package groups import ( "context" @@ -8,7 +8,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/groups/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" ) @@ -22,7 +23,7 @@ func groupMemberResource() *schema.Resource { DeleteContext: groupMemberResourceDelete, Importer: tf.ValidateResourceIDPriorToImport(func(id string) error { - _, err := graph.ParseGroupMemberId(id) + _, err := parse.GroupMemberID(id) return err }), @@ -45,17 +46,17 @@ func groupMemberResource() *schema.Resource { } func groupMemberResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.GroupsClient + client := meta.(*clients.Client).Groups.AadClient groupID := d.Get("group_object_id").(string) memberID := d.Get("member_object_id").(string) - id := graph.GroupMemberIdFrom(groupID, memberID) + id := parse.NewGroupMemberID(groupID, memberID) tf.LockByName(groupMemberResourceName, groupID) defer tf.UnlockByName(groupMemberResourceName, groupID) - existingMembers, err := graph.GroupAllMembers(ctx, client, groupID) + existingMembers, err := aadgraph.GroupAllMembers(ctx, client, groupID) if err != nil { return tf.ErrorDiagF(err, "Listing existing members for group with object ID: %q", id.GroupId) } @@ -67,23 +68,24 @@ func groupMemberResourceCreate(ctx context.Context, d *schema.ResourceData, meta } } - if err := graph.GroupAddMember(ctx, client, groupID, memberID); err != nil { + if err := aadgraph.GroupAddMember(ctx, client, groupID, memberID); err != nil { return tf.ErrorDiagF(err, "Adding group member") } d.SetId(id.String()) + return groupMemberResourceRead(ctx, d, meta) } func groupMemberResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.GroupsClient + client := meta.(*clients.Client).Groups.AadClient - id, err := graph.ParseGroupMemberId(d.Id()) + id, err := parse.GroupMemberID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing Group Member ID %q", d.Id()) } - members, err := graph.GroupAllMembers(ctx, client, id.GroupId) + members, err := aadgraph.GroupAllMembers(ctx, client, id.GroupId) if err != nil { return tf.ErrorDiagF(err, "Retrieving members for group with object ID: %q", id.GroupId) } @@ -113,9 +115,9 @@ func groupMemberResourceRead(ctx context.Context, d *schema.ResourceData, meta i } func groupMemberResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.GroupsClient + client := meta.(*clients.Client).Groups.AadClient - id, err := graph.ParseGroupMemberId(d.Id()) + id, err := parse.GroupMemberID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing Group Member ID %q", d.Id()) } @@ -123,12 +125,12 @@ func groupMemberResourceDelete(ctx context.Context, d *schema.ResourceData, meta tf.LockByName(groupMemberResourceName, id.GroupId) defer tf.UnlockByName(groupMemberResourceName, id.GroupId) - if err := graph.GroupRemoveMember(ctx, client, d.Timeout(schema.TimeoutDelete), id.GroupId, id.MemberId); err != nil { + if err := aadgraph.GroupRemoveMember(ctx, client, d.Timeout(schema.TimeoutDelete), id.GroupId, id.MemberId); err != nil { return tf.ErrorDiagF(err, "Removing member %q from group with object ID: %q", id.MemberId, id.GroupId) } - if _, err := graph.WaitForListRemove(ctx, id.MemberId, func() ([]string, error) { - return graph.GroupAllMembers(ctx, client, id.GroupId) + if _, err := aadgraph.WaitForListRemove(ctx, id.MemberId, func() ([]string, error) { + return aadgraph.GroupAllMembers(ctx, client, id.GroupId) }); err != nil { return tf.ErrorDiagF(err, "Waiting for group membership removal") } diff --git a/internal/services/aadgraph/group_member_resource_test.go b/internal/services/groups/group_member_resource_test.go similarity index 80% rename from internal/services/aadgraph/group_member_resource_test.go rename to internal/services/groups/group_member_resource_test.go index ec80d7914..6032df77e 100644 --- a/internal/services/aadgraph/group_member_resource_test.go +++ b/internal/services/groups/group_member_resource_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package groups_test import ( "context" @@ -11,7 +11,8 @@ import ( "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance" "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance/check" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/groups/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" ) @@ -135,21 +136,20 @@ func TestAccGroupMember_requiresImport(t *testing.T) { }) } -func (r GroupMemberResource) Exists(ctx context.Context, clients *clients.AadClient, state *terraform.InstanceState) (*bool, error) { - id, err := graph.ParseGroupMemberId(state.ID) +func (r GroupMemberResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + id, err := parse.GroupMemberID(state.ID) if err != nil { return nil, fmt.Errorf("parsing Group Member ID: %v", err) } - if resp, err := clients.AadGraph.GroupsClient.Get(ctx, id.GroupId); err != nil { + if resp, err := clients.Groups.AadClient.Get(ctx, id.GroupId); err != nil { if utils.ResponseWasNotFound(resp.Response) { return nil, fmt.Errorf("Group with object ID %q does not exist", id.GroupId) } - return nil, fmt.Errorf("failed to retrieve Group with object ID %q: %+v", id.GroupId, err) } - members, err := graph.GroupAllMembers(ctx, clients.AadGraph.GroupsClient, id.GroupId) + members, err := aadgraph.GroupAllMembers(ctx, clients.Groups.AadClient, id.GroupId) if err != nil { return nil, fmt.Errorf("failed to retrieve Group members (groupId: %q): %+v", id.GroupId, err) } @@ -171,6 +171,33 @@ resource "azuread_group" "test" { `, data.RandomInteger) } +func (GroupMemberResource) templateThreeUsers(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azuread_domains" "test" { + only_initial = true +} + +resource "azuread_user" "testA" { + user_principal_name = "acctestUser.%[1]d.A@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "acctestUser-%[1]d-A" + password = "%[2]s" +} + +resource "azuread_user" "testB" { + user_principal_name = "acctestUser.%[1]d.B@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "acctestUser-%[1]d-B" + mail_nickname = "acctestUser-%[1]d-B" + password = "%[2]s" +} + +resource "azuread_user" "testC" { + user_principal_name = "acctestUser.%[1]d.C@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "acctestUser-%[1]d-C" + password = "%[2]s" +} +`, data.RandomInteger, data.RandomPassword) +} + func (r GroupMemberResource) group(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s @@ -189,13 +216,20 @@ resource "azuread_group_member" "test" { func (r GroupMemberResource) servicePrincipal(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s -%[2]s + +resource "azuread_application" "test" { + name = "acctestServicePrincipal-%[2]d" +} + +resource "azuread_service_principal" "test" { + application_id = azuread_application.test.application_id +} resource "azuread_group_member" "test" { group_object_id = azuread_group.test.object_id member_object_id = azuread_service_principal.test.object_id } -`, r.template(data), ServicePrincipalResource{}.basic(data)) +`, r.template(data), data.RandomInteger) } func (r GroupMemberResource) oneUser(data acceptance.TestData) string { @@ -207,7 +241,7 @@ resource "azuread_group_member" "testA" { group_object_id = azuread_group.test.object_id member_object_id = azuread_user.testA.object_id } -`, r.template(data), UserResource{}.threeUsersABC(data)) +`, r.template(data), r.templateThreeUsers(data)) } func (r GroupMemberResource) twoUsers(data acceptance.TestData) string { @@ -224,7 +258,7 @@ resource "azuread_group_member" "testB" { group_object_id = azuread_group.test.object_id member_object_id = azuread_user.testB.object_id } -`, r.template(data), UserResource{}.threeUsersABC(data)) +`, r.template(data), r.templateThreeUsers(data)) } func (r GroupMemberResource) requiresImport(data acceptance.TestData) string { diff --git a/internal/services/aadgraph/group_resource.go b/internal/services/groups/group_resource.go similarity index 76% rename from internal/services/aadgraph/group_resource.go rename to internal/services/groups/group_resource.go index 04f5d5c39..1d4d38b16 100644 --- a/internal/services/aadgraph/group_resource.go +++ b/internal/services/groups/group_resource.go @@ -1,4 +1,4 @@ -package aadgraph +package groups import ( "context" @@ -10,10 +10,9 @@ import ( "github.com/hashicorp/go-uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" @@ -34,11 +33,23 @@ func groupResource() *schema.Resource { }), Schema: map[string]*schema.Schema{ + "display_name": { + Type: schema.TypeString, + Optional: true, // TODO: v2.0 set Required + Computed: true, // TODO: v2.0 remove Computed + ExactlyOneOf: []string{"display_name", "name"}, + ForceNew: true, + ValidateDiagFunc: validate.NoEmptyStrings, + }, + "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.NoZeroValues, + Type: schema.TypeString, + Optional: true, + Computed: true, + Deprecated: "This property has been renamed to `display_name` and will be removed in v2.0 of this provider.", + ExactlyOneOf: []string{"display_name", "name"}, + ForceNew: true, + ValidateDiagFunc: validate.NoEmptyStrings, }, "description": { @@ -83,14 +94,19 @@ func groupResource() *schema.Resource { } func groupResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.GroupsClient + client := meta.(*clients.Client).Groups.AadClient - name := d.Get("name").(string) + var name string + if v, ok := d.GetOk("display_name"); ok && v.(string) != "" { + name = v.(string) + } else { + name = d.Get("name").(string) + } if d.Get("prevent_duplicate_names").(bool) { - existingGroup, err := graph.GroupFindByName(ctx, client, name) + existingGroup, err := aadgraph.GroupFindByName(ctx, client, name) if err != nil { - return tf.ErrorDiagPathF(err, "name", "Could not check for existing group(s)") + return tf.ErrorDiagPathF(err, "display_name", "Could not check for existing group(s)") } if existingGroup != nil { if existingGroup.ObjectID == nil { @@ -128,7 +144,7 @@ func groupResourceCreate(ctx context.Context, d *schema.ResourceData, meta inter d.SetId(*group.ObjectID) - _, err = graph.WaitForCreationReplication(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { + _, err = aadgraph.WaitForCreationReplication(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { return client.Get(ctx, *group.ObjectID) }) @@ -141,21 +157,21 @@ func groupResourceCreate(ctx context.Context, d *schema.ResourceData, meta inter members := tf.ExpandStringSlicePtr(v.(*schema.Set).List()) // we could lock here against the group member resource, but they should not be used together (todo conflicts with at a resource level?) - if err := graph.GroupAddMembers(ctx, client, *group.ObjectID, *members); err != nil { + if err := aadgraph.GroupAddMembers(ctx, client, *group.ObjectID, *members); err != nil { return tf.ErrorDiagF(err, "Adding group members") } } // Add owners if specified if v, ok := d.GetOk("owners"); ok { - existingOwners, err := graph.GroupAllOwners(ctx, client, *group.ObjectID) + existingOwners, err := aadgraph.GroupAllOwners(ctx, client, *group.ObjectID) if err != nil { return tf.ErrorDiagF(err, "Could not retrieve group owners") } members := *tf.ExpandStringSlicePtr(v.(*schema.Set).List()) ownersToAdd := utils.Difference(members, existingOwners) - if err := graph.GroupAddOwners(ctx, client, *group.ObjectID, ownersToAdd); err != nil { + if err := aadgraph.GroupAddOwners(ctx, client, *group.ObjectID, ownersToAdd); err != nil { return tf.ErrorDiagF(err, "Adding group owners") } } @@ -164,7 +180,7 @@ func groupResourceCreate(ctx context.Context, d *schema.ResourceData, meta inter } func groupResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.GroupsClient + client := meta.(*clients.Client).Groups.AadClient resp, err := client.Get(ctx, d.Id()) if err != nil { @@ -181,6 +197,11 @@ func groupResourceRead(ctx context.Context, d *schema.ResourceData, meta interfa return dg } + if dg := tf.Set(d, "display_name", resp.DisplayName); dg != nil { + return dg + } + + // TODO: v2.0 remove this if dg := tf.Set(d, "name", resp.DisplayName); dg != nil { return dg } @@ -193,7 +214,7 @@ func groupResourceRead(ctx context.Context, d *schema.ResourceData, meta interfa return dg } - members, err := graph.GroupAllMembers(ctx, client, d.Id()) + members, err := aadgraph.GroupAllMembers(ctx, client, d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "owners", "Could not retrieve members for group with object ID %q", d.Id()) } @@ -201,7 +222,7 @@ func groupResourceRead(ctx context.Context, d *schema.ResourceData, meta interfa return dg } - owners, err := graph.GroupAllOwners(ctx, client, d.Id()) + owners, err := aadgraph.GroupAllOwners(ctx, client, d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "owners", "Could not retrieve owners for group with object ID %q", d.Id()) } @@ -221,10 +242,10 @@ func groupResourceRead(ctx context.Context, d *schema.ResourceData, meta interfa } func groupResourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.GroupsClient + client := meta.(*clients.Client).Groups.AadClient if v, ok := d.GetOkExists("members"); ok && d.HasChange("members") { //nolint:SA1019 - existingMembers, err := graph.GroupAllMembers(ctx, client, d.Id()) + existingMembers, err := aadgraph.GroupAllMembers(ctx, client, d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "owners", "Could not retrieve members for group with object ID %q", d.Id()) } @@ -235,24 +256,24 @@ func groupResourceUpdate(ctx context.Context, d *schema.ResourceData, meta inter for _, existingMember := range membersForRemoval { log.Printf("[DEBUG] Removing member with id %q from Group with id %q", existingMember, d.Id()) - if err := graph.GroupRemoveMember(ctx, client, d.Timeout(schema.TimeoutDelete), d.Id(), existingMember); err != nil { + if err := aadgraph.GroupRemoveMember(ctx, client, d.Timeout(schema.TimeoutDelete), d.Id(), existingMember); err != nil { return tf.ErrorDiagF(err, "Removing group members") } - if _, err := graph.WaitForListRemove(ctx, existingMember, func() ([]string, error) { - return graph.GroupAllMembers(ctx, client, d.Id()) + if _, err := aadgraph.WaitForListRemove(ctx, existingMember, func() ([]string, error) { + return aadgraph.GroupAllMembers(ctx, client, d.Id()) }); err != nil { return tf.ErrorDiagF(err, "Waiting for group membership removal") } } - if err := graph.GroupAddMembers(ctx, client, d.Id(), membersToAdd); err != nil { + if err := aadgraph.GroupAddMembers(ctx, client, d.Id(), membersToAdd); err != nil { return tf.ErrorDiagF(err, "Adding group members") } } if v, ok := d.GetOkExists("owners"); ok && d.HasChange("owners") { //nolint:SA1019 - existingOwners, err := graph.GroupAllOwners(ctx, client, d.Id()) + existingOwners, err := aadgraph.GroupAllOwners(ctx, client, d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "owners", "Could not retrieve owners for group with object ID %q", d.Id()) } @@ -270,7 +291,7 @@ func groupResourceUpdate(ctx context.Context, d *schema.ResourceData, meta inter } } - if err := graph.GroupAddOwners(ctx, client, d.Id(), ownersToAdd); err != nil { + if err := aadgraph.GroupAddOwners(ctx, client, d.Id(), ownersToAdd); err != nil { return tf.ErrorDiagF(err, "Adding group owners") } } @@ -279,7 +300,7 @@ func groupResourceUpdate(ctx context.Context, d *schema.ResourceData, meta inter } func groupResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.GroupsClient + client := meta.(*clients.Client).Groups.AadClient if resp, err := client.Delete(ctx, d.Id()); err != nil { if !utils.ResponseWasNotFound(resp) { diff --git a/internal/services/aadgraph/group_resource_test.go b/internal/services/groups/group_resource_test.go similarity index 84% rename from internal/services/aadgraph/group_resource_test.go rename to internal/services/groups/group_resource_test.go index 00ad8eb08..7103b35fd 100644 --- a/internal/services/aadgraph/group_resource_test.go +++ b/internal/services/groups/group_resource_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package groups_test import ( "context" @@ -31,6 +31,21 @@ func TestAccGroup_basic(t *testing.T) { }) } +func TestAccGroup_basicDeprecated(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_group", "test") + r := GroupResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basicDeprecated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccGroup_complete(t *testing.T) { data := acceptance.BuildTestData(t, "azuread_group", "test") r := GroupResource{} @@ -197,13 +212,6 @@ func TestAccGroup_ownersUpdate(t *testing.T) { ), }, data.ImportStep(), - { - Config: r.noOwners(data), - Check: resource.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), }) } @@ -231,8 +239,8 @@ func TestAccGroup_preventDuplicateNamesFail(t *testing.T) { }) } -func (r GroupResource) Exists(ctx context.Context, clients *clients.AadClient, state *terraform.InstanceState) (*bool, error) { - resp, err := clients.AadGraph.GroupsClient.Get(ctx, state.ID) +func (r GroupResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + resp, err := clients.Groups.AadClient.Get(ctx, state.ID) if err != nil { if utils.ResponseWasNotFound(resp.Response) { @@ -240,8 +248,9 @@ func (r GroupResource) Exists(ctx context.Context, clients *clients.AadClient, s } return nil, fmt.Errorf("failed to retrieve Group with object ID %q: %+v", state.ID, err) } + id := resp.ObjectID - return utils.Bool(resp.ObjectID != nil && *resp.ObjectID == state.ID), nil + return utils.Bool(id != nil && *id == state.ID), nil } func (GroupResource) templateDiverseDirectoryObjects(data acceptance.TestData) string { @@ -299,6 +308,14 @@ resource "azuread_user" "testC" { func (GroupResource) basic(data acceptance.TestData) string { return fmt.Sprintf(` +resource "azuread_group" "test" { + display_name = "acctestGroup-%[1]d" +} +`, data.RandomInteger) +} + +func (GroupResource) basicDeprecated(data acceptance.TestData) string { + return fmt.Sprintf(` resource "azuread_group" "test" { name = "acctestGroup-%[1]d" } @@ -318,10 +335,10 @@ resource "azuread_user" "test" { } resource "azuread_group" "test" { - name = "acctestGroup-%[1]d" - description = "Please delete me as this is a.test.AD group!" - members = [azuread_user.test.object_id] - owners = [azuread_user.test.object_id] + display_name = "acctestGroup-%[1]d" + description = "Please delete me as this is a.test.AD group!" + members = [azuread_user.test.object_id] + owners = [azuread_user.test.object_id] } `, data.RandomInteger, data.RandomPassword) } @@ -329,17 +346,8 @@ resource "azuread_group" "test" { func (GroupResource) noMembers(data acceptance.TestData) string { return fmt.Sprintf(` resource "azuread_group" "test" { - name = "acctestGroup-%[1]d" - members = [] -} -`, data.RandomInteger) -} - -func (GroupResource) noOwners(data acceptance.TestData) string { - return fmt.Sprintf(` -resource "azuread_group" "test" { - name = "acctestGroup-%[1]d" - owners = [] + display_name = "acctestGroup-%[1]d" + members = [] } `, data.RandomInteger) } @@ -349,8 +357,8 @@ func (r GroupResource) withDiverseMembers(data acceptance.TestData) string { %[1]s resource "azuread_group" "test" { - name = "acctestGroup-%[2]d" - members = [azuread_user.test.object_id, azuread_group.member.object_id, azuread_service_principal.test.object_id] + display_name = "acctestGroup-%[2]d" + members = [azuread_user.test.object_id, azuread_group.member.object_id, azuread_service_principal.test.object_id] } `, r.templateDiverseDirectoryObjects(data), data.RandomInteger) } @@ -360,8 +368,8 @@ func (r GroupResource) withDiverseOwners(data acceptance.TestData) string { %[1]s resource "azuread_group" "test" { - name = "acctestGroup-%[2]d" - owners = [azuread_user.test.object_id, azuread_service_principal.test.object_id] + display_name = "acctestGroup-%[2]d" + owners = [azuread_user.test.object_id, azuread_service_principal.test.object_id] } `, r.templateDiverseDirectoryObjects(data), data.RandomInteger) } @@ -371,8 +379,8 @@ func (r GroupResource) withOneMember(data acceptance.TestData) string { %[1]s resource "azuread_group" "test" { - name = "acctestGroup-%[2]d" - members = [azuread_user.testA.object_id] + display_name = "acctestGroup-%[2]d" + members = [azuread_user.testA.object_id] } `, r.templateThreeUsers(data), data.RandomInteger) } @@ -382,8 +390,8 @@ func (r GroupResource) withOneOwner(data acceptance.TestData) string { %[1]s resource "azuread_group" "test" { - name = "acctestGroup-%[2]d" - owners = [azuread_user.testA.object_id] + display_name = "acctestGroup-%[2]d" + owners = [azuread_user.testA.object_id] } `, r.templateThreeUsers(data), data.RandomInteger) } @@ -393,8 +401,8 @@ func (r GroupResource) withThreeMembers(data acceptance.TestData) string { %[1]s resource "azuread_group" "test" { - name = "acctestGroup-%[2]d" - members = [azuread_user.testA.object_id, azuread_user.testB.object_id, azuread_user.testC.object_id] + display_name = "acctestGroup-%[2]d" + members = [azuread_user.testA.object_id, azuread_user.testB.object_id, azuread_user.testC.object_id] } `, r.templateThreeUsers(data), data.RandomInteger) } @@ -404,8 +412,8 @@ func (r GroupResource) withThreeOwners(data acceptance.TestData) string { %[1]s resource "azuread_group" "test" { - name = "acctestGroup-%[2]d" - owners = [azuread_user.testA.object_id, azuread_user.testB.object_id, azuread_user.testC.object_id] + display_name = "acctestGroup-%[2]d" + owners = [azuread_user.testA.object_id, azuread_user.testB.object_id, azuread_user.testC.object_id] } `, r.templateThreeUsers(data), data.RandomInteger) } @@ -415,9 +423,9 @@ func (r GroupResource) withOwnersAndMembers(data acceptance.TestData) string { %[1]s resource "azuread_group" "test" { - name = "acctestGroup-%[2]d" - owners = [azuread_user.testA.object_id] - members = [azuread_user.testB.object_id, azuread_user.testC.object_id] + display_name = "acctestGroup-%[2]d" + owners = [azuread_user.testA.object_id] + members = [azuread_user.testB.object_id, azuread_user.testC.object_id] } `, r.templateThreeUsers(data), data.RandomInteger) } @@ -433,8 +441,8 @@ resource "azuread_service_principal" "test" { } resource "azuread_group" "test" { - name = "acctestGroup-%[1]d" - members = [azuread_service_principal.test.object_id] + display_name = "acctestGroup-%[1]d" + members = [azuread_service_principal.test.object_id] } `, data.RandomInteger) } @@ -450,8 +458,8 @@ resource "azuread_service_principal" "test" { } resource "azuread_group" "test" { - name = "acctestGroup-%[1]d" - owners = [azuread_service_principal.test.object_id] + display_name = "acctestGroup-%[1]d" + owners = [azuread_service_principal.test.object_id] } `, data.RandomInteger) } @@ -459,7 +467,7 @@ resource "azuread_group" "test" { func (GroupResource) preventDuplicateNamesPass(data acceptance.TestData) string { return fmt.Sprintf(` resource "azuread_group" "test" { - name = "acctestGroup-%[1]d" + display_name = "acctestGroup-%[1]d" prevent_duplicate_names = true } `, data.RandomInteger) @@ -470,7 +478,7 @@ func (r GroupResource) preventDuplicateNamesFail(data acceptance.TestData) strin %[1]s resource "azuread_group" "duplicate" { - name = azuread_group.test.name + display_name = azuread_group.test.name prevent_duplicate_names = true } `, r.basic(data)) diff --git a/internal/services/aadgraph/groups_data_source.go b/internal/services/groups/groups_data_source.go similarity index 60% rename from internal/services/aadgraph/groups_data_source.go rename to internal/services/groups/groups_data_source.go index dfa2ea73a..c1e7bad6b 100644 --- a/internal/services/aadgraph/groups_data_source.go +++ b/internal/services/groups/groups_data_source.go @@ -1,4 +1,4 @@ -package aadgraph +package groups import ( "context" @@ -13,15 +13,15 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" ) -func groupsData() *schema.Resource { +func groupsDataSource() *schema.Resource { return &schema.Resource{ - ReadContext: groupsDataRead, + ReadContext: groupsDataSourceRead, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, @@ -32,18 +32,30 @@ func groupsData() *schema.Resource { Type: schema.TypeList, Optional: true, Computed: true, - ExactlyOneOf: []string{"names", "object_ids"}, + ExactlyOneOf: []string{"display_names", "names", "object_ids"}, Elem: &schema.Schema{ Type: schema.TypeString, ValidateDiagFunc: validate.UUID, }, }, + "display_names": { + Type: schema.TypeList, + Optional: true, + Computed: true, + ExactlyOneOf: []string{"display_names", "names", "object_ids"}, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateDiagFunc: validate.NoEmptyStrings, + }, + }, + "names": { Type: schema.TypeList, Optional: true, Computed: true, - ExactlyOneOf: []string{"names", "object_ids"}, + ExactlyOneOf: []string{"display_names", "names", "object_ids"}, + Deprecated: "This property has been renamed to `display_names` and will be removed in v2.0 of this provider.", Elem: &schema.Schema{ Type: schema.TypeString, ValidateDiagFunc: validate.NoEmptyStrings, @@ -53,18 +65,25 @@ func groupsData() *schema.Resource { } } -func groupsDataRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.GroupsClient +func groupsDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*clients.Client).Groups.AadClient var groups []graphrbac.ADGroup expectedCount := 0 - if names, ok := d.Get("names").([]interface{}); ok && len(names) > 0 { + var names []interface{} + if v, ok := d.GetOk("display_names"); ok { + names = v.([]interface{}) + } else if v, ok := d.GetOk("names"); ok { + names = v.([]interface{}) + } + + if len(names) > 0 { expectedCount = len(names) - for _, v := range names { - g, err := graph.GroupGetByDisplayName(ctx, client, v.(string)) + for _, name := range names { + g, err := aadgraph.GroupGetByDisplayName(ctx, client, name.(string)) if err != nil { - return tf.ErrorDiagPathF(err, "name", "No group found with display name: %q", v) + return tf.ErrorDiagPathF(err, "name", "No group found with display name: %q", name) } groups = append(groups, *g) } @@ -88,29 +107,33 @@ func groupsDataRead(ctx context.Context, d *schema.ResourceData, meta interface{ return tf.ErrorDiagF(fmt.Errorf("Expected: %d, Actual: %d", expectedCount, len(groups)), "Unexpected number of groups returned") } - names := make([]string, 0, len(groups)) - oids := make([]string, 0, len(groups)) + newNames := make([]string, 0, len(groups)) + newObjectIds := make([]string, 0, len(groups)) for _, u := range groups { if u.ObjectID == nil || u.DisplayName == nil { return tf.ErrorDiagF(errors.New("API returned group with nil object ID"), "Bad API response") } - oids = append(oids, *u.ObjectID) - names = append(names, *u.DisplayName) + newObjectIds = append(newObjectIds, *u.ObjectID) + newNames = append(newNames, *u.DisplayName) } h := sha1.New() - if _, err := h.Write([]byte(strings.Join(names, "-"))); err != nil { + if _, err := h.Write([]byte(strings.Join(newNames, "-"))); err != nil { return tf.ErrorDiagF(err, "Unable to compute hash for names") } d.SetId("groups#" + base64.URLEncoding.EncodeToString(h.Sum(nil))) - if dg := tf.Set(d, "object_ids", oids); dg != nil { + if dg := tf.Set(d, "object_ids", newObjectIds); dg != nil { + return dg + } + + if dg := tf.Set(d, "display_names", newNames); dg != nil { return dg } - if dg := tf.Set(d, "names", names); dg != nil { + if dg := tf.Set(d, "names", newNames); dg != nil { return dg } diff --git a/internal/services/aadgraph/groups_data_source_test.go b/internal/services/groups/groups_data_source_test.go similarity index 75% rename from internal/services/aadgraph/groups_data_source_test.go rename to internal/services/groups/groups_data_source_test.go index 6955a1f13..800970ac6 100644 --- a/internal/services/aadgraph/groups_data_source_test.go +++ b/internal/services/groups/groups_data_source_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package groups_test import ( "fmt" @@ -12,7 +12,7 @@ import ( type GroupsDataSource struct{} -func TestAccGroupsDataSource_byUserPrincipalNames(t *testing.T) { +func TestAccGroupsDataSource_byDisplayNames(t *testing.T) { data := acceptance.BuildTestData(t, "data.azuread_groups", "test") data.DataSourceTest(t, []resource.TestStep{ @@ -26,6 +26,20 @@ func TestAccGroupsDataSource_byUserPrincipalNames(t *testing.T) { }) } +func TestAccGroupsDataSource_byNamesDeprecated(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_groups", "test") + + data.DataSourceTest(t, []resource.TestStep{ + { + Config: GroupsDataSource{}.byNamesDeprecated(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("names.#").HasValue("2"), + check.That(data.ResourceName).Key("object_ids.#").HasValue("2"), + ), + }, + }) +} + func TestAccGroupsDataSource_byObjectIds(t *testing.T) { data := acceptance.BuildTestData(t, "data.azuread_groups", "test") @@ -70,6 +84,16 @@ func (GroupsDataSource) byDisplayNames(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s +data "azuread_groups" "test" { + display_names = [azuread_group.testA.name, azuread_group.testB.name] +} +`, GroupsDataSource{}.template(data)) +} + +func (GroupsDataSource) byNamesDeprecated(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + data "azuread_groups" "test" { names = [azuread_group.testA.name, azuread_group.testB.name] } diff --git a/internal/services/groups/parse/group_member.go b/internal/services/groups/parse/group_member.go new file mode 100644 index 000000000..e6ffdc04d --- /dev/null +++ b/internal/services/groups/parse/group_member.go @@ -0,0 +1,30 @@ +package parse + +import "fmt" + +type GroupMemberId struct { + ObjectSubResourceId + GroupId string + MemberId string +} + +func NewGroupMemberID(groupId, memberId string) GroupMemberId { + return GroupMemberId{ + ObjectSubResourceId: NewObjectSubResourceID(groupId, "member", memberId), + GroupId: groupId, + MemberId: memberId, + } +} + +func GroupMemberID(idString string) (*GroupMemberId, error) { + id, err := ObjectSubResourceID(idString, "member") + if err != nil { + return nil, fmt.Errorf("unable to parse Member ID: %v", err) + } + + return &GroupMemberId{ + ObjectSubResourceId: *id, + GroupId: id.objectId, + MemberId: id.subId, + }, nil +} diff --git a/internal/services/groups/parse/object.go b/internal/services/groups/parse/object.go new file mode 100644 index 000000000..9792fb6ba --- /dev/null +++ b/internal/services/groups/parse/object.go @@ -0,0 +1,57 @@ +package parse + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-uuid" +) + +type ObjectSubResourceId struct { + objectId string + subId string + Type string +} + +func NewObjectSubResourceID(objectId, typeId, subId string) ObjectSubResourceId { + return ObjectSubResourceId{ + objectId: objectId, + Type: typeId, + subId: subId, + } +} + +func (id ObjectSubResourceId) String() string { + return fmt.Sprintf("%s/%s/%s", id.objectId, id.Type, id.subId) +} + +func ObjectSubResourceID(idString, expectedType string) (*ObjectSubResourceId, error) { + parts := strings.Split(idString, "/") + if len(parts) != 3 { + return nil, fmt.Errorf("Object Resource ID should be in the format {objectId}/{type}/{subId} - but got %q", idString) + } + + id := ObjectSubResourceId{ + objectId: parts[0], + Type: parts[1], + subId: parts[2], + } + + if _, err := uuid.ParseUUID(id.objectId); err != nil { + return nil, fmt.Errorf("Object ID isn't a valid UUID (%q): %+v", id.objectId, err) + } + + if id.Type == "" { + return nil, fmt.Errorf("Type in {objectID}/{type}/{subID} should not blank") + } + + if id.Type != expectedType { + return nil, fmt.Errorf("Type in {objectID}/{type}/{subID} was expected to be %s, got %s", expectedType, parts[2]) + } + + if _, err := uuid.ParseUUID(id.subId); err != nil { + return nil, fmt.Errorf("Object Sub Resource ID isn't a valid UUID (%q): %+v", id.subId, err) + } + + return &id, nil +} diff --git a/internal/services/groups/registration.go b/internal/services/groups/registration.go new file mode 100644 index 000000000..d47e38eeb --- /dev/null +++ b/internal/services/groups/registration.go @@ -0,0 +1,35 @@ +package groups + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type Registration struct{} + +// Name is the name of this Service +func (r Registration) Name() string { + return "Groups" +} + +// WebsiteCategories returns a list of categories which can be used for the sidebar +func (r Registration) WebsiteCategories() []string { + return []string{ + "Groups", + } +} + +// SupportedDataSources returns the supported Data Sources supported by this Service +func (r Registration) SupportedDataSources() map[string]*schema.Resource { + return map[string]*schema.Resource{ + "azuread_group": groupDataSource(), + "azuread_groups": groupsDataSource(), + } +} + +// SupportedResources returns the supported Resources supported by this Service +func (r Registration) SupportedResources() map[string]*schema.Resource { + return map[string]*schema.Resource{ + "azuread_group": groupResource(), + "azuread_group_member": groupMemberResource(), + } +} diff --git a/internal/services/serviceprincipals/client/client.go b/internal/services/serviceprincipals/client/client.go new file mode 100644 index 000000000..cea7a655a --- /dev/null +++ b/internal/services/serviceprincipals/client/client.go @@ -0,0 +1,19 @@ +package client + +import ( + "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" + "github.com/terraform-providers/terraform-provider-azuread/internal/common" +) + +type Client struct { + AadClient *graphrbac.ServicePrincipalsClient +} + +func NewClient(o *common.ClientOptions) *Client { + aadClient := graphrbac.NewServicePrincipalsClientWithBaseURI(o.AadGraphEndpoint, o.TenantID) + o.ConfigureClient(&aadClient.Client, o.AadGraphAuthorizer) + + return &Client{ + AadClient: &aadClient, + } +} diff --git a/internal/services/aadgraph/client_config_data_source.go b/internal/services/serviceprincipals/client_config_data_source.go similarity index 83% rename from internal/services/aadgraph/client_config_data_source.go rename to internal/services/serviceprincipals/client_config_data_source.go index d0c28c88b..09d7c51ad 100644 --- a/internal/services/aadgraph/client_config_data_source.go +++ b/internal/services/serviceprincipals/client_config_data_source.go @@ -1,4 +1,4 @@ -package aadgraph +package serviceprincipals import ( "context" @@ -12,9 +12,9 @@ import ( "github.com/terraform-providers/terraform-provider-azuread/internal/tf" ) -func clientConfigData() *schema.Resource { +func clientConfigDataSource() *schema.Resource { return &schema.Resource{ - ReadContext: clientConfigDataRead, + ReadContext: clientConfigDataSourceRead, Timeouts: &schema.ResourceTimeout{ Read: schema.DefaultTimeout(5 * time.Minute), @@ -39,11 +39,11 @@ func clientConfigData() *schema.Resource { } } -func clientConfigDataRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient) +func clientConfigDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*clients.Client) if client.AuthenticatedAsAServicePrincipal { - spClient := client.AadGraph.ServicePrincipalsClient + spClient := client.ServicePrincipals.AadClient // Application & Service Principal is 1:1 per tenant. Since we know the appId (client_id) // here, we can query for the Service Principal whose appId matches. filter := fmt.Sprintf("appId eq '%s'", client.ClientID) diff --git a/internal/services/aadgraph/client_config_data_source_test.go b/internal/services/serviceprincipals/client_config_data_source_test.go similarity index 96% rename from internal/services/aadgraph/client_config_data_source_test.go rename to internal/services/serviceprincipals/client_config_data_source_test.go index 35dd55a67..e2aa1ce34 100644 --- a/internal/services/aadgraph/client_config_data_source_test.go +++ b/internal/services/serviceprincipals/client_config_data_source_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package serviceprincipals_test import ( "os" diff --git a/internal/services/serviceprincipals/parse/credentials.go b/internal/services/serviceprincipals/parse/credentials.go new file mode 100644 index 000000000..0e4674b42 --- /dev/null +++ b/internal/services/serviceprincipals/parse/credentials.go @@ -0,0 +1,60 @@ +package parse + +import ( + "fmt" + "strings" +) + +type CredentialId struct { + ObjectId string + KeyType string + KeyId string +} + +func NewCredentialID(objectId, keyType, keyId string) CredentialId { + return CredentialId{ + ObjectId: objectId, + KeyType: keyType, + KeyId: keyId, + } +} + +func (id CredentialId) String() string { + return id.ObjectId + "/" + id.KeyType + "/" + id.KeyId +} + +func CertificateID(idString string) (*CredentialId, error) { + id, err := ObjectSubResourceID(idString, "certificate") + if err != nil { + return nil, fmt.Errorf("unable to parse Certificate ID: %v", err) + } + + return &CredentialId{ + ObjectId: id.objectId, + KeyType: id.Type, + KeyId: id.subId, + }, nil +} + +func PasswordID(idString string) (*CredentialId, error) { + id, err := ObjectSubResourceID(idString, "password") + if err != nil { + return nil, fmt.Errorf("unable to parse Password ID: %v", err) + } + + return &CredentialId{ + ObjectId: id.objectId, + KeyType: id.Type, + KeyId: id.subId, + }, nil +} + +func OldPasswordID(id string) (*CredentialId, error) { + parts := strings.Split(id, "/") + if len(parts) != 2 { + return nil, fmt.Errorf("Password ID expected to be in the format {objectId}/{keyId} - but got %q", id) + } + + newId := parts[0] + "/password/" + parts[1] + return PasswordID(newId) +} diff --git a/internal/services/serviceprincipals/parse/object.go b/internal/services/serviceprincipals/parse/object.go new file mode 100644 index 000000000..9792fb6ba --- /dev/null +++ b/internal/services/serviceprincipals/parse/object.go @@ -0,0 +1,57 @@ +package parse + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-uuid" +) + +type ObjectSubResourceId struct { + objectId string + subId string + Type string +} + +func NewObjectSubResourceID(objectId, typeId, subId string) ObjectSubResourceId { + return ObjectSubResourceId{ + objectId: objectId, + Type: typeId, + subId: subId, + } +} + +func (id ObjectSubResourceId) String() string { + return fmt.Sprintf("%s/%s/%s", id.objectId, id.Type, id.subId) +} + +func ObjectSubResourceID(idString, expectedType string) (*ObjectSubResourceId, error) { + parts := strings.Split(idString, "/") + if len(parts) != 3 { + return nil, fmt.Errorf("Object Resource ID should be in the format {objectId}/{type}/{subId} - but got %q", idString) + } + + id := ObjectSubResourceId{ + objectId: parts[0], + Type: parts[1], + subId: parts[2], + } + + if _, err := uuid.ParseUUID(id.objectId); err != nil { + return nil, fmt.Errorf("Object ID isn't a valid UUID (%q): %+v", id.objectId, err) + } + + if id.Type == "" { + return nil, fmt.Errorf("Type in {objectID}/{type}/{subID} should not blank") + } + + if id.Type != expectedType { + return nil, fmt.Errorf("Type in {objectID}/{type}/{subID} was expected to be %s, got %s", expectedType, parts[2]) + } + + if _, err := uuid.ParseUUID(id.subId); err != nil { + return nil, fmt.Errorf("Object Sub Resource ID isn't a valid UUID (%q): %+v", id.subId, err) + } + + return &id, nil +} diff --git a/internal/services/serviceprincipals/registration.go b/internal/services/serviceprincipals/registration.go new file mode 100644 index 000000000..b57831509 --- /dev/null +++ b/internal/services/serviceprincipals/registration.go @@ -0,0 +1,36 @@ +package serviceprincipals + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type Registration struct{} + +// Name is the name of this Service +func (r Registration) Name() string { + return "Service Principals" +} + +// WebsiteCategories returns a list of categories which can be used for the sidebar +func (r Registration) WebsiteCategories() []string { + return []string{ + "Service Principals", + } +} + +// SupportedDataSources returns the supported Data Sources supported by this Service +func (r Registration) SupportedDataSources() map[string]*schema.Resource { + return map[string]*schema.Resource{ + "azuread_client_config": clientConfigDataSource(), + "azuread_service_principal": servicePrincipalData(), + } +} + +// SupportedResources returns the supported Resources supported by this Service +func (r Registration) SupportedResources() map[string]*schema.Resource { + return map[string]*schema.Resource{ + "azuread_service_principal": servicePrincipalResource(), + "azuread_service_principal_certificate": servicePrincipalCertificateResource(), + "azuread_service_principal_password": servicePrincipalPasswordResource(), + } +} diff --git a/internal/services/serviceprincipals/schema.go b/internal/services/serviceprincipals/schema.go new file mode 100644 index 000000000..d8befb000 --- /dev/null +++ b/internal/services/serviceprincipals/schema.go @@ -0,0 +1,99 @@ +package serviceprincipals + +import "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + +func schemaAppRolesComputed() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + + "allowed_member_types": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "description": { + Type: schema.TypeString, + Computed: true, + }, + + "display_name": { + Type: schema.TypeString, + Computed: true, + }, + + // TODO: v2.0 rename to `enabled` + "is_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "value": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + } +} + +func schemaOauth2PermissionsComputed() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "admin_consent_description": { + Type: schema.TypeString, + Computed: true, + }, + + "admin_consent_display_name": { + Type: schema.TypeString, + Computed: true, + }, + + "id": { + Type: schema.TypeString, + Computed: true, + }, + + // TODO: v2.0 rename to `enabled` + "is_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "type": { + Type: schema.TypeString, + Computed: true, + }, + + "user_consent_description": { + Type: schema.TypeString, + Computed: true, + }, + + "user_consent_display_name": { + Type: schema.TypeString, + Computed: true, + }, + + "value": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + } +} diff --git a/internal/services/aadgraph/service_principal_certificate_resource.go b/internal/services/serviceprincipals/service_principal_certificate_resource.go similarity index 83% rename from internal/services/aadgraph/service_principal_certificate_resource.go rename to internal/services/serviceprincipals/service_principal_certificate_resource.go index 15ea9612e..67110fe6e 100644 --- a/internal/services/aadgraph/service_principal_certificate_resource.go +++ b/internal/services/serviceprincipals/service_principal_certificate_resource.go @@ -1,4 +1,4 @@ -package aadgraph +package serviceprincipals import ( "context" @@ -10,7 +10,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/serviceprincipals/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" ) @@ -22,29 +23,29 @@ func servicePrincipalCertificateResource() *schema.Resource { DeleteContext: servicePrincipalCertificateResourceDelete, Importer: tf.ValidateResourceIDPriorToImport(func(id string) error { - _, err := graph.ParseCertificateId(id) + _, err := parse.CertificateID(id) return err }), - Schema: graph.CertificateResourceSchema("service_principal_id"), + Schema: aadgraph.CertificateResourceSchema("service_principal_id"), } } func servicePrincipalCertificateResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ServicePrincipalsClient + client := meta.(*clients.Client).ServicePrincipals.AadClient objectId := d.Get("service_principal_id").(string) - cred, err := graph.KeyCredentialForResource(d) + cred, err := aadgraph.KeyCredentialForResource(d) if err != nil { attr := "" - if kerr, ok := err.(graph.CredentialError); ok { + if kerr, ok := err.(aadgraph.CredentialError); ok { attr = kerr.Attr() } return tf.ErrorDiagPathF(err, attr, "Generating certificate credentials for service principal with object ID %q", objectId) } - id := graph.CredentialIdFrom(objectId, "certificate", *cred.KeyID) + id := parse.NewCredentialID(objectId, "certificate", *cred.KeyID) tf.LockByName(servicePrincipalResourceName, id.ObjectId) defer tf.UnlockByName(servicePrincipalResourceName, id.ObjectId) @@ -54,9 +55,9 @@ func servicePrincipalCertificateResourceCreate(ctx context.Context, d *schema.Re return tf.ErrorDiagPathF(err, "service_principal_id", "Listing certificate credentials for service principal with ID %q", objectId) } - newCreds, err := graph.KeyCredentialResultAdd(existingCreds, cred) + newCreds, err := aadgraph.KeyCredentialResultAdd(existingCreds, cred) if err != nil { - if _, ok := err.(*graph.AlreadyExistsError); ok { + if _, ok := err.(*aadgraph.AlreadyExistsError); ok { return tf.ImportAsExistsDiag("azuread_service_principal_certificate", id.String()) } return tf.ErrorDiagF(err, "Adding service principal certificate") @@ -66,7 +67,7 @@ func servicePrincipalCertificateResourceCreate(ctx context.Context, d *schema.Re return tf.ErrorDiagF(err, "Creating certificate credentials %q for service principal with object ID %q", id.KeyId, id.ObjectId) } - _, err = graph.WaitForKeyCredentialReplication(ctx, id.KeyId, d.Timeout(schema.TimeoutCreate), func() (graphrbac.KeyCredentialListResult, error) { + _, err = aadgraph.WaitForKeyCredentialReplication(ctx, id.KeyId, d.Timeout(schema.TimeoutCreate), func() (graphrbac.KeyCredentialListResult, error) { return client.ListKeyCredentials(ctx, id.ObjectId) }) if err != nil { @@ -79,9 +80,9 @@ func servicePrincipalCertificateResourceCreate(ctx context.Context, d *schema.Re } func servicePrincipalCertificateResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ServicePrincipalsClient + client := meta.(*clients.Client).ServicePrincipals.AadClient - id, err := graph.ParseCertificateId(d.Id()) + id, err := parse.CertificateID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing certificate credential with ID %q", d.Id()) } @@ -103,7 +104,7 @@ func servicePrincipalCertificateResourceRead(ctx context.Context, d *schema.Reso return tf.ErrorDiagPathF(err, "service_principal_id", "Listing certificate credentials for service principal with object ID %q", id.ObjectId) } - credential := graph.KeyCredentialResultFindByKeyId(credentials, id.KeyId) + credential := aadgraph.KeyCredentialResultFindByKeyId(credentials, id.KeyId) if credential == nil { log.Printf("[DEBUG] certificate credential %q (ID %q) was not found - removing from state!", id.KeyId, id.ObjectId) d.SetId("") @@ -146,9 +147,9 @@ func servicePrincipalCertificateResourceRead(ctx context.Context, d *schema.Reso } func servicePrincipalCertificateResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ServicePrincipalsClient + client := meta.(*clients.Client).ServicePrincipals.AadClient - id, err := graph.ParseCertificateId(d.Id()) + id, err := parse.CertificateID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing certificate credential with ID %q", d.Id()) } @@ -172,7 +173,7 @@ func servicePrincipalCertificateResourceDelete(ctx context.Context, d *schema.Re return tf.ErrorDiagF(err, "Listing certificate credentials for service principal with object ID %q", id.ObjectId) } - newCreds, err := graph.KeyCredentialResultRemoveByKeyId(existing, id.KeyId) + newCreds, err := aadgraph.KeyCredentialResultRemoveByKeyId(existing, id.KeyId) if err != nil { return tf.ErrorDiagF(err, "Removing certificate credential %q from service principal with object ID %q", id.KeyId, id.ObjectId) } diff --git a/internal/services/aadgraph/service_principal_certificate_resource_test.go b/internal/services/serviceprincipals/service_principal_certificate_resource_test.go similarity index 93% rename from internal/services/aadgraph/service_principal_certificate_resource_test.go rename to internal/services/serviceprincipals/service_principal_certificate_resource_test.go index 84f88dea7..4eb116cb7 100644 --- a/internal/services/aadgraph/service_principal_certificate_resource_test.go +++ b/internal/services/serviceprincipals/service_principal_certificate_resource_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package serviceprincipals_test import ( "context" @@ -12,7 +12,8 @@ import ( "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance" "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance/check" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/serviceprincipals/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" ) @@ -107,13 +108,13 @@ func TestAccServicePrincipalCertificate_requiresImport(t *testing.T) { }) } -func (r ServicePrincipalCertificateResource) Exists(ctx context.Context, clients *clients.AadClient, state *terraform.InstanceState) (*bool, error) { - id, err := graph.ParseCertificateId(state.ID) +func (r ServicePrincipalCertificateResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + id, err := parse.CertificateID(state.ID) if err != nil { return nil, fmt.Errorf("parsing Service Principal Certificate ID: %v", err) } - resp, err := clients.AadGraph.ServicePrincipalsClient.Get(ctx, id.ObjectId) + resp, err := clients.ServicePrincipals.AadClient.Get(ctx, id.ObjectId) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return nil, fmt.Errorf("Service Principal with object ID %q does not exist", id.ObjectId) @@ -121,12 +122,12 @@ func (r ServicePrincipalCertificateResource) Exists(ctx context.Context, clients return nil, fmt.Errorf("Service Principal with object ID %q does not exist", id.ObjectId) } - credentials, err := clients.AadGraph.ServicePrincipalsClient.ListKeyCredentials(ctx, id.ObjectId) + credentials, err := clients.ServicePrincipals.AadClient.ListKeyCredentials(ctx, id.ObjectId) if err != nil { return nil, fmt.Errorf("listing Key Credentials for Service Principal %q: %+v", id.ObjectId, err) } - cred := graph.KeyCredentialResultFindByKeyId(credentials, id.KeyId) + cred := aadgraph.KeyCredentialResultFindByKeyId(credentials, id.KeyId) if cred != nil { return utils.Bool(true), nil } diff --git a/internal/services/aadgraph/service_principal_data_source.go b/internal/services/serviceprincipals/service_principal_data_source.go similarity index 90% rename from internal/services/aadgraph/service_principal_data_source.go rename to internal/services/serviceprincipals/service_principal_data_source.go index 09df41973..cada0526c 100644 --- a/internal/services/aadgraph/service_principal_data_source.go +++ b/internal/services/serviceprincipals/service_principal_data_source.go @@ -1,4 +1,4 @@ -package aadgraph +package serviceprincipals import ( "context" @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" @@ -45,15 +45,15 @@ func servicePrincipalData() *schema.Resource { ConflictsWith: []string{"object_id", "display_name"}, }, - "app_roles": graph.SchemaAppRolesComputed(), + "app_roles": schemaAppRolesComputed(), - "oauth2_permissions": graph.SchemaOauth2PermissionsComputed(), + "oauth2_permissions": schemaOauth2PermissionsComputed(), }, } } func servicePrincipalDataRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ServicePrincipalsClient + client := meta.(*clients.Client).ServicePrincipals.AadClient var sp *graphrbac.ServicePrincipal @@ -138,11 +138,11 @@ func servicePrincipalDataRead(ctx context.Context, d *schema.ResourceData, meta return dg } - if dg := tf.Set(d, "app_roles", graph.FlattenAppRoles(sp.AppRoles)); dg != nil { + if dg := tf.Set(d, "app_roles", aadgraph.FlattenAppRoles(sp.AppRoles)); dg != nil { return dg } - if dg := tf.Set(d, "oauth2_permissions", graph.FlattenOauth2Permissions(sp.Oauth2Permissions)); dg != nil { + if dg := tf.Set(d, "oauth2_permissions", aadgraph.FlattenOauth2Permissions(sp.Oauth2Permissions)); dg != nil { return dg } diff --git a/internal/services/aadgraph/service_principal_data_source_test.go b/internal/services/serviceprincipals/service_principal_data_source_test.go similarity index 99% rename from internal/services/aadgraph/service_principal_data_source_test.go rename to internal/services/serviceprincipals/service_principal_data_source_test.go index ce961072e..218f7a0c8 100644 --- a/internal/services/aadgraph/service_principal_data_source_test.go +++ b/internal/services/serviceprincipals/service_principal_data_source_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package serviceprincipals_test import ( "fmt" diff --git a/internal/services/aadgraph/service_principal_password_resource.go b/internal/services/serviceprincipals/service_principal_password_resource.go similarity index 86% rename from internal/services/aadgraph/service_principal_password_resource.go rename to internal/services/serviceprincipals/service_principal_password_resource.go index 714da8153..04cd0c79a 100644 --- a/internal/services/aadgraph/service_principal_password_resource.go +++ b/internal/services/serviceprincipals/service_principal_password_resource.go @@ -1,4 +1,4 @@ -package aadgraph +package serviceprincipals import ( "context" @@ -12,7 +12,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/serviceprincipals/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" @@ -25,11 +26,11 @@ func servicePrincipalPasswordResource() *schema.Resource { DeleteContext: servicePrincipalPasswordResourceDelete, Importer: tf.ValidateResourceIDPriorToImport(func(id string) error { - _, err := graph.ParsePasswordId(id) + _, err := parse.PasswordID(id) return err }), - Schema: graph.PasswordResourceSchema("service_principal_id"), + Schema: aadgraph.PasswordResourceSchema("service_principal_id"), SchemaVersion: 1, StateUpgraders: []schema.StateUpgrader{ @@ -43,19 +44,19 @@ func servicePrincipalPasswordResource() *schema.Resource { } func servicePrincipalPasswordResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ServicePrincipalsClient + client := meta.(*clients.Client).ServicePrincipals.AadClient objectId := d.Get("service_principal_id").(string) - cred, err := graph.PasswordCredentialForResource(d) + cred, err := aadgraph.PasswordCredentialForResource(d) if err != nil { attr := "" - if kerr, ok := err.(graph.CredentialError); ok { + if kerr, ok := err.(aadgraph.CredentialError); ok { attr = kerr.Attr() } return tf.ErrorDiagPathF(err, attr, "Generating password credentials for service principal with object ID %q", objectId) } - id := graph.CredentialIdFrom(objectId, "password", *cred.KeyID) + id := parse.NewCredentialID(objectId, "password", *cred.KeyID) tf.LockByName(servicePrincipalResourceName, id.ObjectId) defer tf.UnlockByName(servicePrincipalResourceName, id.ObjectId) @@ -65,9 +66,9 @@ func servicePrincipalPasswordResourceCreate(ctx context.Context, d *schema.Resou return tf.ErrorDiagPathF(err, "service_principal_id", "Listing password credentials for service principal with ID %q", objectId) } - newCreds, err := graph.PasswordCredentialResultAdd(existingCreds, cred) + newCreds, err := aadgraph.PasswordCredentialResultAdd(existingCreds, cred) if err != nil { - if _, ok := err.(*graph.AlreadyExistsError); ok { + if _, ok := err.(*aadgraph.AlreadyExistsError); ok { return tf.ImportAsExistsDiag("azuread_service_principal_password", id.String()) } return tf.ErrorDiagF(err, "Adding service principal password") @@ -79,7 +80,7 @@ func servicePrincipalPasswordResourceCreate(ctx context.Context, d *schema.Resou d.SetId(id.String()) - _, err = graph.WaitForPasswordCredentialReplication(ctx, id.KeyId, d.Timeout(schema.TimeoutCreate), func() (graphrbac.PasswordCredentialListResult, error) { + _, err = aadgraph.WaitForPasswordCredentialReplication(ctx, id.KeyId, d.Timeout(schema.TimeoutCreate), func() (graphrbac.PasswordCredentialListResult, error) { return client.ListPasswordCredentials(ctx, id.ObjectId) }) if err != nil { @@ -90,9 +91,9 @@ func servicePrincipalPasswordResourceCreate(ctx context.Context, d *schema.Resou } func servicePrincipalPasswordResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ServicePrincipalsClient + client := meta.(*clients.Client).ServicePrincipals.AadClient - id, err := graph.ParsePasswordId(d.Id()) + id, err := parse.PasswordID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing password credential with ID %q", d.Id()) } @@ -114,7 +115,7 @@ func servicePrincipalPasswordResourceRead(ctx context.Context, d *schema.Resourc return tf.ErrorDiagPathF(err, "service_principal_id", "Listing password credentials for service principal with object ID %q", id.ObjectId) } - credential := graph.PasswordCredentialResultFindByKeyId(credentials, id.KeyId) + credential := aadgraph.PasswordCredentialResultFindByKeyId(credentials, id.KeyId) if credential == nil { log.Printf("[DEBUG] Service Principal %q (ID %q) was not found - removing from state!", id.KeyId, id.ObjectId) d.SetId("") @@ -157,9 +158,9 @@ func servicePrincipalPasswordResourceRead(ctx context.Context, d *schema.Resourc } func servicePrincipalPasswordResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ServicePrincipalsClient + client := meta.(*clients.Client).ServicePrincipals.AadClient - id, err := graph.ParsePasswordId(d.Id()) + id, err := parse.PasswordID(d.Id()) if err != nil { return tf.ErrorDiagPathF(err, "id", "Parsing password credential with ID %q", d.Id()) } @@ -183,7 +184,7 @@ func servicePrincipalPasswordResourceDelete(ctx context.Context, d *schema.Resou return tf.ErrorDiagPathF(err, "service_principal_id", "Listing password credentials for service principal with object ID %q", id.ObjectId) } - newCreds, err := graph.PasswordCredentialResultRemoveByKeyId(existing, id.KeyId) + newCreds, err := aadgraph.PasswordCredentialResultRemoveByKeyId(existing, id.KeyId) if err != nil { return tf.ErrorDiagF(err, "Removing password credential %q from service principal with object ID %q", id.KeyId, id.ObjectId) } @@ -258,7 +259,7 @@ func resourceServicePrincipalPasswordInstanceResourceV0() *schema.Resource { func resourceServicePrincipalPasswordInstanceStateUpgradeV0(_ context.Context, rawState map[string]interface{}, _ interface{}) (map[string]interface{}, error) { log.Println("[DEBUG] Migrating ID from v0 to v1 format") - newId, err := graph.ParseOldPasswordId(rawState["id"].(string)) + newId, err := parse.OldPasswordID(rawState["id"].(string)) if err != nil { return rawState, fmt.Errorf("generating new ID: %s", err) } diff --git a/internal/services/aadgraph/service_principal_password_resource_test.go b/internal/services/serviceprincipals/service_principal_password_resource_test.go similarity index 91% rename from internal/services/aadgraph/service_principal_password_resource_test.go rename to internal/services/serviceprincipals/service_principal_password_resource_test.go index cbd79af8c..592a924a3 100644 --- a/internal/services/aadgraph/service_principal_password_resource_test.go +++ b/internal/services/serviceprincipals/service_principal_password_resource_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package serviceprincipals_test import ( "context" @@ -12,7 +12,8 @@ import ( "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance" "github.com/terraform-providers/terraform-provider-azuread/internal/acceptance/check" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" + "github.com/terraform-providers/terraform-provider-azuread/internal/services/serviceprincipals/parse" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" ) @@ -85,13 +86,13 @@ func TestAccServicePrincipalPassword_requiresImport(t *testing.T) { }) } -func (r ServicePrincipalPasswordResource) Exists(ctx context.Context, clients *clients.AadClient, state *terraform.InstanceState) (*bool, error) { - id, err := graph.ParsePasswordId(state.ID) +func (r ServicePrincipalPasswordResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + id, err := parse.PasswordID(state.ID) if err != nil { return nil, fmt.Errorf("Service Principal Password ID: %v", err) } - resp, err := clients.AadGraph.ServicePrincipalsClient.Get(ctx, id.ObjectId) + resp, err := clients.ServicePrincipals.AadClient.Get(ctx, id.ObjectId) if err != nil { if utils.ResponseWasNotFound(resp.Response) { return nil, fmt.Errorf("Service Principal with object ID %q does not exist", id.ObjectId) @@ -99,12 +100,12 @@ func (r ServicePrincipalPasswordResource) Exists(ctx context.Context, clients *c return nil, fmt.Errorf("failed to retrieve Service Principal with object ID %q: %+v", id.ObjectId, err) } - credentials, err := clients.AadGraph.ServicePrincipalsClient.ListPasswordCredentials(ctx, id.ObjectId) + credentials, err := clients.ServicePrincipals.AadClient.ListPasswordCredentials(ctx, id.ObjectId) if err != nil { return nil, fmt.Errorf("listing Password Credentials for Service Principal %q: %+v", id.ObjectId, err) } - cred := graph.PasswordCredentialResultFindByKeyId(credentials, id.KeyId) + cred := aadgraph.PasswordCredentialResultFindByKeyId(credentials, id.KeyId) if cred != nil { return utils.Bool(true), nil } diff --git a/internal/services/aadgraph/service_principal_resource.go b/internal/services/serviceprincipals/service_principal_resource.go similarity index 86% rename from internal/services/aadgraph/service_principal_resource.go rename to internal/services/serviceprincipals/service_principal_resource.go index e206869f9..5bebf3c89 100644 --- a/internal/services/aadgraph/service_principal_resource.go +++ b/internal/services/serviceprincipals/service_principal_resource.go @@ -1,4 +1,4 @@ -package aadgraph +package serviceprincipals import ( "context" @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" @@ -58,9 +58,9 @@ func servicePrincipalResource() *schema.Resource { Computed: true, }, - "app_roles": graph.SchemaAppRolesComputed(), + "app_roles": schemaAppRolesComputed(), - "oauth2_permissions": graph.SchemaOauth2PermissionsComputed(), + "oauth2_permissions": schemaOauth2PermissionsComputed(), "tags": { Type: schema.TypeSet, @@ -75,7 +75,7 @@ func servicePrincipalResource() *schema.Resource { } func servicePrincipalResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ServicePrincipalsClient + client := meta.(*clients.Client).ServicePrincipals.AadClient applicationId := d.Get("application_id").(string) @@ -103,7 +103,7 @@ func servicePrincipalResourceCreate(ctx context.Context, d *schema.ResourceData, } d.SetId(*sp.ObjectID) - _, err = graph.WaitForCreationReplication(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { + _, err = aadgraph.WaitForCreationReplication(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { return client.Get(ctx, *sp.ObjectID) }) if err != nil { @@ -114,7 +114,7 @@ func servicePrincipalResourceCreate(ctx context.Context, d *schema.ResourceData, } func servicePrincipalResourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ServicePrincipalsClient + client := meta.(*clients.Client).ServicePrincipals.AadClient var properties graphrbac.ServicePrincipalUpdateParameters @@ -136,7 +136,7 @@ func servicePrincipalResourceUpdate(ctx context.Context, d *schema.ResourceData, } // Wait for replication delay after updating - _, err := graph.WaitForCreationReplication(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { + _, err := aadgraph.WaitForCreationReplication(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { return client.Get(ctx, d.Id()) }) if err != nil { @@ -147,7 +147,7 @@ func servicePrincipalResourceUpdate(ctx context.Context, d *schema.ResourceData, } func servicePrincipalResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ServicePrincipalsClient + client := meta.(*clients.Client).ServicePrincipals.AadClient objectId := d.Id() @@ -182,11 +182,11 @@ func servicePrincipalResourceRead(ctx context.Context, d *schema.ResourceData, m return dg } - if dg := tf.Set(d, "app_roles", graph.FlattenAppRoles(sp.AppRoles)); dg != nil { + if dg := tf.Set(d, "app_roles", aadgraph.FlattenAppRoles(sp.AppRoles)); dg != nil { return dg } - if dg := tf.Set(d, "oauth2_permissions", graph.FlattenOauth2Permissions(sp.Oauth2Permissions)); dg != nil { + if dg := tf.Set(d, "oauth2_permissions", aadgraph.FlattenOauth2Permissions(sp.Oauth2Permissions)); dg != nil { return dg } @@ -194,7 +194,7 @@ func servicePrincipalResourceRead(ctx context.Context, d *schema.ResourceData, m } func servicePrincipalResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.ServicePrincipalsClient + client := meta.(*clients.Client).ServicePrincipals.AadClient applicationId := d.Id() app, err := client.Delete(ctx, applicationId) diff --git a/internal/services/aadgraph/service_principal_resource_test.go b/internal/services/serviceprincipals/service_principal_resource_test.go similarity index 94% rename from internal/services/aadgraph/service_principal_resource_test.go rename to internal/services/serviceprincipals/service_principal_resource_test.go index b0058cc7b..0bf2c3869 100644 --- a/internal/services/aadgraph/service_principal_resource_test.go +++ b/internal/services/serviceprincipals/service_principal_resource_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package serviceprincipals_test import ( "context" @@ -75,8 +75,8 @@ func TestAccServicePrincipal_update(t *testing.T) { }) } -func (r ServicePrincipalResource) Exists(ctx context.Context, clients *clients.AadClient, state *terraform.InstanceState) (*bool, error) { - resp, err := clients.AadGraph.ServicePrincipalsClient.Get(ctx, state.ID) +func (r ServicePrincipalResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + resp, err := clients.ServicePrincipals.AadClient.Get(ctx, state.ID) if err != nil { if utils.ResponseWasNotFound(resp.Response) { diff --git a/internal/services/users/client/client.go b/internal/services/users/client/client.go new file mode 100644 index 000000000..aa0944813 --- /dev/null +++ b/internal/services/users/client/client.go @@ -0,0 +1,19 @@ +package client + +import ( + "github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac" + "github.com/terraform-providers/terraform-provider-azuread/internal/common" +) + +type Client struct { + AadClient *graphrbac.UsersClient +} + +func NewClient(o *common.ClientOptions) *Client { + aadClient := graphrbac.NewUsersClientWithBaseURI(o.AadGraphEndpoint, o.TenantID) + o.ConfigureClient(&aadClient.Client, o.AadGraphAuthorizer) + + return &Client{ + AadClient: &aadClient, + } +} diff --git a/internal/services/users/registration.go b/internal/services/users/registration.go new file mode 100644 index 000000000..402725ad2 --- /dev/null +++ b/internal/services/users/registration.go @@ -0,0 +1,34 @@ +package users + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type Registration struct{} + +// Name is the name of this Service +func (r Registration) Name() string { + return "Users" +} + +// WebsiteCategories returns a list of categories which can be used for the sidebar +func (r Registration) WebsiteCategories() []string { + return []string{ + "Users", + } +} + +// SupportedDataSources returns the supported Data Sources supported by this Service +func (r Registration) SupportedDataSources() map[string]*schema.Resource { + return map[string]*schema.Resource{ + "azuread_user": userData(), + "azuread_users": usersData(), + } +} + +// SupportedResources returns the supported Resources supported by this Service +func (r Registration) SupportedResources() map[string]*schema.Resource { + return map[string]*schema.Resource{ + "azuread_user": userResource(), + } +} diff --git a/internal/services/aadgraph/user_data_source.go b/internal/services/users/user_data_source.go similarity index 97% rename from internal/services/aadgraph/user_data_source.go rename to internal/services/users/user_data_source.go index 6b8f2aadb..3275bb93c 100644 --- a/internal/services/aadgraph/user_data_source.go +++ b/internal/services/users/user_data_source.go @@ -1,4 +1,4 @@ -package aadgraph +package users import ( "context" @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" @@ -161,7 +161,7 @@ func userData() *schema.Resource { } func userDataRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.UsersClient + client := meta.(*clients.Client).Users.AadClient var user graphrbac.User @@ -176,7 +176,7 @@ func userDataRead(ctx context.Context, d *schema.ResourceData, meta interface{}) } user = resp } else if objectId, ok := d.Get("object_id").(string); ok && objectId != "" { - u, err := graph.UserGetByObjectId(ctx, client, objectId) + u, err := aadgraph.UserGetByObjectId(ctx, client, objectId) if err != nil { return tf.ErrorDiagF(err, "Finding user with object ID: %q", objectId) } @@ -185,7 +185,7 @@ func userDataRead(ctx context.Context, d *schema.ResourceData, meta interface{}) } user = *u } else if mailNickname, ok := d.Get("mail_nickname").(string); ok && mailNickname != "" { - u, err := graph.UserGetByMailNickname(ctx, client, mailNickname) + u, err := aadgraph.UserGetByMailNickname(ctx, client, mailNickname) if err != nil { return tf.ErrorDiagPathF(err, "mail_nickname", "Finding user with email alias: %q", mailNickname) } diff --git a/internal/services/aadgraph/user_data_source_test.go b/internal/services/users/user_data_source_test.go similarity index 94% rename from internal/services/aadgraph/user_data_source_test.go rename to internal/services/users/user_data_source_test.go index bb78cb6bc..bc7e066a2 100644 --- a/internal/services/aadgraph/user_data_source_test.go +++ b/internal/services/users/user_data_source_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package users_test import ( "fmt" @@ -107,12 +107,14 @@ data "azuread_user" "test" { func (UserDataSource) byUserPrincipalNameNonexistent(data acceptance.TestData) string { return fmt.Sprintf(` -%[1]s +data "azuread_domains" "test" { + only_initial = true +} data "azuread_user" "test" { - user_principal_name = "not-a-real-user-%[2]d${data.azuread_domains.test.domains.0.domain_name}" + user_principal_name = "not-a-real-user-%[1]d${data.azuread_domains.test.domains.0.domain_name}" } -`, DomainsDataSource{}.onlyInitial(), data.RandomInteger) +`, data.RandomInteger) } func (UserDataSource) byObjectId(data acceptance.TestData) string { @@ -145,10 +147,12 @@ data "azuread_user" "test" { func (UserDataSource) byMailNicknameNonexistent(data acceptance.TestData) string { return fmt.Sprintf(` -%[1]s +data "azuread_domains" "test" { + only_initial = true +} data "azuread_user" "test" { - mail_nickname = "not-a-real-user-%[2]d${data.azuread_domains.test.domains.0.domain_name}" + mail_nickname = "not-a-real-user-%[1]d${data.azuread_domains.test.domains.0.domain_name}" } -`, DomainsDataSource{}.onlyInitial(), data.RandomInteger) +`, data.RandomInteger) } diff --git a/internal/services/aadgraph/user_resource.go b/internal/services/users/user_resource.go similarity index 97% rename from internal/services/aadgraph/user_resource.go rename to internal/services/users/user_resource.go index d6a318095..4e3c1e695 100644 --- a/internal/services/aadgraph/user_resource.go +++ b/internal/services/users/user_resource.go @@ -1,4 +1,4 @@ -package aadgraph +package users import ( "context" @@ -14,7 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" @@ -200,7 +200,7 @@ func userResource() *schema.Resource { } func userResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.UsersClient + client := meta.(*clients.Client).Users.AadClient upn := d.Get("user_principal_name").(string) mailNickName := d.Get("mail_nickname").(string) @@ -289,7 +289,7 @@ func userResourceCreate(ctx context.Context, d *schema.ResourceData, meta interf d.SetId(*user.ObjectID) - _, err = graph.WaitForCreationReplication(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { + _, err = aadgraph.WaitForCreationReplication(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { return client.Get(ctx, *user.ObjectID) }) @@ -301,7 +301,7 @@ func userResourceCreate(ctx context.Context, d *schema.ResourceData, meta interf } func userResourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.UsersClient + client := meta.(*clients.Client).Users.AadClient var userUpdateParameters graphrbac.UserUpdateParameters @@ -394,7 +394,7 @@ func userResourceUpdate(ctx context.Context, d *schema.ResourceData, meta interf } func userResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.UsersClient + client := meta.(*clients.Client).Users.AadClient objectId := d.Id() @@ -540,7 +540,7 @@ func userResourceRead(ctx context.Context, d *schema.ResourceData, meta interfac } func userResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.UsersClient + client := meta.(*clients.Client).Users.AadClient resp, err := client.Delete(ctx, d.Id()) if err != nil { diff --git a/internal/services/aadgraph/user_resource_test.go b/internal/services/users/user_resource_test.go similarity index 68% rename from internal/services/aadgraph/user_resource_test.go rename to internal/services/users/user_resource_test.go index 0b52468e9..a398263b9 100644 --- a/internal/services/aadgraph/user_resource_test.go +++ b/internal/services/users/user_resource_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package users_test import ( "context" @@ -96,8 +96,8 @@ func TestAccUser_threeUsersABC(t *testing.T) { }) } -func (r UserResource) Exists(ctx context.Context, clients *clients.AadClient, state *terraform.InstanceState) (*bool, error) { - resp, err := clients.AadGraph.UsersClient.Get(ctx, state.ID) +func (r UserResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + resp, err := clients.Users.AadClient.Get(ctx, state.ID) if err != nil { if utils.ResponseWasNotFound(resp.Response) { @@ -111,69 +111,75 @@ func (r UserResource) Exists(ctx context.Context, clients *clients.AadClient, st func (UserResource) basic(data acceptance.TestData) string { return fmt.Sprintf(` -%[1]s +data "azuread_domains" "test" { + only_initial = true +} resource "azuread_user" "test" { - user_principal_name = "acctestUser.%[2]d@${data.azuread_domains.test.domains.0.domain_name}" - display_name = "acctestUser-%[2]d" - password = "%[3]s" + user_principal_name = "acctestUser.%[1]d@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "acctestUser-%[1]d" + password = "%[2]s" } -`, DomainsDataSource{}.onlyInitial(), data.RandomInteger, data.RandomPassword) +`, data.RandomInteger, data.RandomPassword) } func (UserResource) complete(data acceptance.TestData) string { return fmt.Sprintf(` -%[1]s +data "azuread_domains" "test" { + only_initial = true +} resource "azuread_user" "test" { - user_principal_name = "acctestUser.%[2]d@${data.azuread_domains.test.domains.0.domain_name}" + user_principal_name = "acctestUser.%[1]d@${data.azuread_domains.test.domains.0.domain_name}" force_password_change = true - display_name = "acctestUser-%[2]d-DisplayName" - given_name = "acctestUser-%[2]d-GivenName" - surname = "acctestUser-%[2]d-Surname" - mail_nickname = "acctestUser-%[2]d-MailNickname" + display_name = "acctestUser-%[1]d-DisplayName" + given_name = "acctestUser-%[1]d-GivenName" + surname = "acctestUser-%[1]d-Surname" + mail_nickname = "acctestUser-%[1]d-MailNickname" account_enabled = false - password = "%[3]s" + password = "%[2]s" usage_location = "NO" - immutable_id = "%[2]d" - - job_title = "acctestUser-%[2]d-Job" - department = "acctestUser-%[2]d-Dept" - company_name = "acctestUser-%[2]d-Company" - street_address = "acctestUser-%[2]d-Street" - state = "acctestUser-%[2]d-State" - city = "acctestUser-%[2]d-City" - country = "acctestUser-%[2]d-Country" + immutable_id = "%[1]d" + + job_title = "acctestUser-%[1]d-Job" + department = "acctestUser-%[1]d-Dept" + company_name = "acctestUser-%[1]d-Company" + street_address = "acctestUser-%[1]d-Street" + state = "acctestUser-%[1]d-State" + city = "acctestUser-%[1]d-City" + country = "acctestUser-%[1]d-Country" postal_code = "111111" mobile = "(555) 555-5555" - physical_delivery_office_name = "acctestUser-%[2]d-PDON" + physical_delivery_office_name = "acctestUser-%[1]d-PDON" } -`, DomainsDataSource{}.onlyInitial(), data.RandomInteger, data.RandomPassword) +`, data.RandomInteger, data.RandomPassword) } func (UserResource) threeUsersABC(data acceptance.TestData) string { return fmt.Sprintf(` -%[1]s +data "azuread_domains" "test" { + only_initial = true +} resource "azuread_user" "testA" { - user_principal_name = "acctestUser.%[2]d.A@${data.azuread_domains.test.domains.0.domain_name}" - display_name = "acctestUser-%[2]d-A" - password = "%[3]s" + user_principal_name = "acctestUser.%[1]d.A@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "acctestUser-%[1]d-A" + password = "%[2]s" } resource "azuread_user" "testB" { - user_principal_name = "acctestUser.%[2]d.B@${data.azuread_domains.test.domains.0.domain_name}" - display_name = "acctestUser-%[2]d-B" - mail_nickname = "acctestUser-%[2]d-B" - password = "%[3]s" + user_principal_name = "acctestUser.%[1]d.B@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "acctestUser-%[1]d-B" + mail_nickname = "acctestUser-%[1]d-B" + password = "%[2]s" } resource "azuread_user" "testC" { - user_principal_name = "acctestUser.%[2]d.C@${data.azuread_domains.test.domains.0.domain_name}" - display_name = "acctestUser-%[2]d-C" - password = "%[3]s" + user_principal_name = "acctestUser.%[1]d.C@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "acctestUser-%[1]d-C" + password = "%[2]s" } -`, DomainsDataSource{}.onlyInitial(), data.RandomInteger, data.RandomPassword) +`, data.RandomInteger, data.RandomPassword) } diff --git a/internal/services/aadgraph/users_data_source.go b/internal/services/users/users_data_source.go similarity index 96% rename from internal/services/aadgraph/users_data_source.go rename to internal/services/users/users_data_source.go index a3240768c..e8bbdc603 100644 --- a/internal/services/aadgraph/users_data_source.go +++ b/internal/services/users/users_data_source.go @@ -1,4 +1,4 @@ -package aadgraph +package users import ( "context" @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/terraform-providers/terraform-provider-azuread/internal/clients" - "github.com/terraform-providers/terraform-provider-azuread/internal/services/aadgraph/graph" + "github.com/terraform-providers/terraform-provider-azuread/internal/helpers/aadgraph" "github.com/terraform-providers/terraform-provider-azuread/internal/tf" "github.com/terraform-providers/terraform-provider-azuread/internal/utils" "github.com/terraform-providers/terraform-provider-azuread/internal/validate" @@ -129,7 +129,7 @@ func usersData() *schema.Resource { } func usersDataRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*clients.AadClient).AadGraph.UsersClient + client := meta.(*clients.Client).Users.AadClient var users []*graphrbac.User expectedCount := 0 @@ -152,7 +152,7 @@ func usersDataRead(ctx context.Context, d *schema.ResourceData, meta interface{} if objectIds, ok := d.Get("object_ids").([]interface{}); ok && len(objectIds) > 0 { expectedCount = len(objectIds) for _, v := range objectIds { - u, err := graph.UserGetByObjectId(ctx, client, v.(string)) + u, err := aadgraph.UserGetByObjectId(ctx, client, v.(string)) if err != nil { return tf.ErrorDiagPathF(err, "object_ids", "Finding user with object ID: %q", v) } @@ -168,7 +168,7 @@ func usersDataRead(ctx context.Context, d *schema.ResourceData, meta interface{} } else if mailNicknames, ok := d.Get("mail_nicknames").([]interface{}); ok && len(mailNicknames) > 0 { expectedCount = len(mailNicknames) for _, v := range mailNicknames { - u, err := graph.UserGetByMailNickname(ctx, client, v.(string)) + u, err := aadgraph.UserGetByMailNickname(ctx, client, v.(string)) if err != nil { return tf.ErrorDiagPathF(err, "mail_nicknames", "Finding user with email alias: %q", v) } diff --git a/internal/services/aadgraph/users_data_source_test.go b/internal/services/users/users_data_source_test.go similarity index 99% rename from internal/services/aadgraph/users_data_source_test.go rename to internal/services/users/users_data_source_test.go index b824a47b2..5ea59a5fb 100644 --- a/internal/services/aadgraph/users_data_source_test.go +++ b/internal/services/users/users_data_source_test.go @@ -1,4 +1,4 @@ -package aadgraph_test +package users_test import ( "fmt" diff --git a/website/docs/d/application.html.markdown b/website/docs/d/application.html.markdown index ed9f62966..31d3405a6 100644 --- a/website/docs/d/application.html.markdown +++ b/website/docs/d/application.html.markdown @@ -16,7 +16,7 @@ Use this data source to access information about an existing Application within ```hcl data "azuread_application" "example" { - name = "My First AzureAD Application" + display_name = "My First AzureAD Application" } output "azure_ad_object_id" { @@ -30,9 +30,9 @@ output "azure_ad_object_id" { * `application_id` - (Optional) Specifies the Application ID of the Azure Active Directory Application. -* `name` - (Optional) Specifies the name of the Application within Azure Active Directory. +* `display_name` - (Optional) Specifies the display name of the Application within Azure Active Directory. -~> **NOTE:** One of `object_id`, `application_id` or `name` must be specified. +~> **NOTE:** One of `object_id`, `application_id` or `display_name` must be specified. ## Attributes Reference @@ -66,7 +66,7 @@ The following attributes are exported: * `app_roles` - A collection of `app_role` blocks as documented below. For more information https://docs.microsoft.com/en-us/azure/architecture/multitenant-identity/app-roles -* `public_client` - Is this Azure AD Application available publically? +* `public_client` - Is this Azure AD Application available publicly? --- diff --git a/website/docs/d/group.html.markdown b/website/docs/d/group.html.markdown index fb2d9ca75..fd741e9af 100644 --- a/website/docs/d/group.html.markdown +++ b/website/docs/d/group.html.markdown @@ -17,7 +17,7 @@ Gets information about an Azure Active Directory group. ```hcl data "azuread_group" "example" { - name = "A-AD-Group" + display_name = "A-AD-Group" } ``` @@ -25,11 +25,11 @@ data "azuread_group" "example" { The following arguments are supported: -* `name` - (Optional) The Name of the AD Group we want to lookup. +* `display_name` - (Optional) The splay name of the Group within Azure Active Directory. -* `object_id` - (Optional) Specifies the Object ID of the AD Group within Azure Active Directory. +* `object_id` - (Optional) Specifies the Object ID of the Group within Azure Active Directory. -~> **NOTE:** One of `name` or an `object_id` must be specified. +~> **NOTE:** One of `display_name` or `object_id` must be specified. ## Attributes Reference @@ -37,7 +37,7 @@ The following attributes are exported: * `id` - The Object ID of the Azure AD Group. * `description` - The description of the AD Group. -* `name` - The name of the Azure AD Group. +* `display_name` - The name of the Azure AD Group. * `owners` - The Object IDs of the Azure AD Group owners. * `members` - The Object IDs of the Azure AD Group members. diff --git a/website/docs/r/application.html.markdown b/website/docs/r/application.html.markdown index 8ac8407fc..2e76d8a1d 100644 --- a/website/docs/r/application.html.markdown +++ b/website/docs/r/application.html.markdown @@ -17,7 +17,7 @@ Manages an Application within Azure Active Directory. ```hcl resource "azuread_application" "example" { - name = "example" + display_name = "example" homepage = "https://homepage" identifier_uris = ["https://uri"] reply_urls = ["https://replyurl"] @@ -107,7 +107,7 @@ resource "azuread_application" "example" { The following arguments are supported: -* `name` - (Required) The display name for the application. +* `display_name` - (Required) The display name for the application. * `homepage` - (optional) The URL to the application's home page. @@ -133,11 +133,13 @@ The following arguments are supported: * `type` - (Optional) Type of an application: `webapp/api` or `native`. Defaults to `webapp/api`. For `native` apps type `identifier_uris` property can not not be set. +-> **Note:** The `type` attribute is deprecated and will be removed in version 2.0 of the provider, along with the associated constraints of this attribute's values. + * `app_role` - (Optional) A collection of `app_role` blocks as documented below. For more information https://docs.microsoft.com/en-us/azure/architecture/multitenant-identity/app-roles * `oauth2_permissions` - (Optional) A collection of OAuth 2.0 permission scopes that the web API (resource) app exposes to client apps. Each permission is covered by `oauth2_permissions` blocks as documented below. --> **Note on roles and scopes/permissions:** in Azure Active Directory, roles (`app_role`) and scopes/permissions (`oauth2_permissions`) exported by an Application share the same namespace and cannot contain duplicate values. Terraform will attempt to detect this at plan time. +-> **Note on roles and scopes/permissions:** In Azure Active Directory, roles (`app_role`) and scopes/permissions (`oauth2_permissions`) exported by an Application share the same namespace and cannot contain duplicate values. Terraform will attempt to detect this at plan time. * `prevent_duplicate_names` - (Optional) If `true`, will return an error when an existing Application is found with the same name. Defaults to `false`. diff --git a/website/docs/r/group.markdown b/website/docs/r/group.markdown index cc58836a7..75d1392c7 100644 --- a/website/docs/r/group.markdown +++ b/website/docs/r/group.markdown @@ -19,7 +19,7 @@ Manages a Group within Azure Active Directory. ```hcl resource "azuread_group" "example" { - name = "A-AD-Group" + display_name = "A-AD-Group" } ``` @@ -33,7 +33,7 @@ resource "azuread_user" "example" { } resource "azuread_group" "example" { - name = "MyGroup" + display_name = "MyGroup" members = [ azuread_user.example.object_id, /* more users */ @@ -45,7 +45,7 @@ resource "azuread_group" "example" { The following arguments are supported: -* `name` - (Required) The display name for the Group. Changing this forces a new resource to be created. +* `display_name` - (Required) The display name for the Group. Changing this forces a new resource to be created. * `description` - (Optional) The description for the Group. Changing this forces a new resource to be created. * `members` (Optional) A set of members who should be present in this Group. Supported Object types are Users, Groups or Service Principals. * `owners` (Optional) A set of owners who own this Group. Supported Object types are Users or Service Principals.