From ade0bf4e50ac1d39fd9515e68c01648d9f73ce81 Mon Sep 17 00:00:00 2001 From: thulasi Date: Fri, 21 Jun 2024 23:05:51 +0200 Subject: [PATCH 01/36] feat(): SocialProvider Resource --- helpers/cidaas/cidaas.go | 1 + helpers/cidaas/social_provider.go | 103 +++++ helpers/util/locals.go | 12 + internal/provider.go | 1 + .../resources/resource_social_provider.go | 359 ++++++++++++++++++ 5 files changed, 476 insertions(+) create mode 100644 helpers/cidaas/social_provider.go create mode 100644 internal/resources/resource_social_provider.go diff --git a/helpers/cidaas/cidaas.go b/helpers/cidaas/cidaas.go index 818332e..5193324 100644 --- a/helpers/cidaas/cidaas.go +++ b/helpers/cidaas/cidaas.go @@ -12,6 +12,7 @@ import ( type Client struct { Role RoleService CustomProvider CustomProvideService + SocialProvider SocialProviderService Scope ScopeService ScopeGroup ScopeGroupService GroupType GroupTypeService diff --git a/helpers/cidaas/social_provider.go b/helpers/cidaas/social_provider.go new file mode 100644 index 0000000..d39ca2e --- /dev/null +++ b/helpers/cidaas/social_provider.go @@ -0,0 +1,103 @@ +package cidaas + +import ( + "encoding/json" + "fmt" + "github.com/Cidaas/terraform-provider-cidaas/helpers/util" + "net/http" +) + +type SocialProviderModel struct { + ID string `json:"id,omitempty"` + ClientID string `json:"client_id,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + Name string `json:"name,omitempty"` + ProviderName string `json:"provider_name,omitempty"` + Claims ClaimsModel `json:"claims,omitempty"` + EnabledForAdminPortal bool `json:"enabled_for_admin_portal"` + Enabled bool `json:"enabled"` + SPScopes []string `json:"scopes"` + SPUserInfoFields []string `json:"userinfo_fields"` +} + +type ClaimsModel struct { + RequiredClaims RequiredClaimsModel `json:"required_claims,omitempty"` + OptionalClaims OptionalClaimsModel `json:"optional_claims,omitempty"` +} + +type RequiredClaimsModel struct { + UserInfo []string `json:"user_info,omitempty"` + IdToken []string `json:"id_token,omitempty"` +} + +type OptionalClaimsModel struct { + UserInfo []string `json:"user_info,omitempty"` + IdToken []string `json:"id_token,omitempty"` +} + +type SocialProviderResponse struct { + Success bool `json:"success,omitempty"` + Status int `json:"status,omitempty"` + Data SocialProviderModel +} + +type SocialProvider struct { + HTTPClient util.HTTPClientInterface +} + +type SocialProviderService interface { + UpsertSocialProvider(cp *SocialProviderModel) (*SocialProviderResponse, error) + + GetSocialProvider(providerName, providerId string) (*SocialProviderResponse, error) + DeleteSocialProvider(providerName, providerId string) error + //ConfigureCustomProvider(cp CustomProviderConfigPayload) (*CustomProviderConfigureResponse, error) + //UpdateCustomProvider(cp *CustomProviderModel) error + //https://kube-nightlybuild-dev.cidaas.de/providers-srv/multi/providers/google/bc34e3db-aeaf-4a41-9b75-c12b825ea987 +} + +func NewSocialProvider(httpClient util.HTTPClientInterface) SocialProviderService { + return &SocialProvider{HTTPClient: httpClient} +} + +func (c *SocialProvider) UpsertSocialProvider(cp *SocialProviderModel) (*SocialProviderResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers")) + c.HTTPClient.SetMethod(http.MethodPost) + res, err := c.HTTPClient.MakeRequest(cp) + if err != nil { + return nil, err + } + defer res.Body.Close() + var response SocialProviderResponse + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + } + return &response, nil +} + +func (c *SocialProvider) GetSocialProvider(providerName, providerId string) (*SocialProviderResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s%s&provider_id=%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers?provider_name=", providerName, providerId)) + c.HTTPClient.SetMethod(http.MethodGet) + res, err := c.HTTPClient.MakeRequest(nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + var response SocialProviderResponse + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + } + return &response, nil +} + +func (c *SocialProvider) DeleteSocialProvider(providerName, providerId string) error { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s/%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers", providerName, providerId)) + c.HTTPClient.SetMethod(http.MethodDelete) + res, err := c.HTTPClient.MakeRequest(nil) + if err != nil { + return err + } + defer res.Body.Close() + return nil +} diff --git a/helpers/util/locals.go b/helpers/util/locals.go index b46a984..c95ec5d 100644 --- a/helpers/util/locals.go +++ b/helpers/util/locals.go @@ -159,3 +159,15 @@ func GetLanguageForLocale(locale string) string { } return "en" } + +var AllowedClaims = []string{ + "name", "given_name", "family_name", "nickname", "preferred_username", "profile", + "picture", "website", "email", "email_verified", "gender", "middle_name", "birthdate", "zoneinfo", "locale", + "phone_number", "phone_number_verified", "formatted", "street_address", "locality", "region", "postal_code", "country", +} + +var AllowedProviders = []string{ + "google", "facebook", "linkedin", "amazon", "foursquare", "github", + "instagram", "yammer", "wordpress", "microsoft", "yahoo", "officeenterprise", "salesforce", "paypal_sandbox", "paypal", + "apple", "twitter", "netid", "netid_qa", +} diff --git a/internal/provider.go b/internal/provider.go index 1fafa01..66295a7 100644 --- a/internal/provider.go +++ b/internal/provider.go @@ -58,6 +58,7 @@ func (p *cidaasProvider) Resources(_ context.Context) []func() resource.Resource return []func() resource.Resource{ cidaasResource.NewRoleResource, cidaasResource.NewCustomProvider, + cidaasResource.NewSocialProvider, cidaasResource.NewScopeResource, cidaasResource.NewScopeGroupResource, cidaasResource.NewGroupTypeResource, diff --git a/internal/resources/resource_social_provider.go b/internal/resources/resource_social_provider.go new file mode 100644 index 0000000..694d3ed --- /dev/null +++ b/internal/resources/resource_social_provider.go @@ -0,0 +1,359 @@ +package resources + +import ( + "context" + "fmt" + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + "github.com/Cidaas/terraform-provider-cidaas/helpers/util" + "github.com/Cidaas/terraform-provider-cidaas/internal/validators" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +type SocialProvider struct { + cidaasClient *cidaas.Client +} + +type SocialProviderConfig struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + ProviderName types.String `tfsdk:"provider_name"` + Enabled types.Bool `tfsdk:"enabled"` + ClientID types.String `tfsdk:"client_id"` + ClientSecret types.String `tfsdk:"client_secret"` + ClaimsConfig types.Object `tfsdk:"claims"` + EnabledForAdminPortal types.Bool `tfsdk:"enabled_for_admin_portal"` + //UserInfoFieldsConfig types.List `tfsdk:"userinfo_fields"` + //CreatedAt types.String `tfsdk:"created_at"` + //UpdatedAt types.String `tfsdk:"updated_at"` + claimsConfig *ClaimsConfig +} + +type ClaimsConfig struct { + RequiredClaimsConfig types.Object `tfsdk:"required_claims"` + OptionalClaimsConfig types.Object `tfsdk:"optional_claims"` + requiredClaimsConfig *RequiredClaimsConfig + optionalClaimsConfig *OptionalClaimsConfig +} + +type RequiredClaimsConfig struct { + UserInfo types.Set `tfsdk:"user_info"` + IdToken types.Set `tfsdk:"id_token"` +} + +type OptionalClaimsConfig struct { + UserInfo types.Set `tfsdk:"user_info"` + IdToken types.Set `tfsdk:"id_token"` +} + +type UserInfoFieldsConfig struct { + InnerKey types.String `tfsdk:"inner_key"` + ExternalKey types.String `tfsdk:"external_key"` + IsCustomField types.Bool `tfsdk:"is_custom_field"` + IsSystemField types.Bool `tfsdk:"is_system_field"` +} + +func NewSocialProvider() resource.Resource { + return &SocialProvider{} +} + +func (r *SocialProvider) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_social_provider" +} + +func (r *SocialProvider) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*cidaas.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected cidaas.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.cidaasClient = client +} + +func (r *SocialProvider) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + &validators.UniqueIdentifier{}, + }, + }, + "provider_name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + &validators.UniqueIdentifier{}, + }, + Validators: []validator.String{ + stringvalidator.OneOf( + func() []string { + var provider = make([]string, len(util.AllowedProviders)) //nolint:gofumpt + for i, cType := range util.AllowedProviders { + provider[i] = cType + } + return provider + }()...), + }, + }, + "enabled": schema.BoolAttribute{ + Required: true, + }, + "client_id": schema.StringAttribute{ + Required: true, + }, + "client_secret": schema.StringAttribute{ + Required: true, + Sensitive: true, + }, + "claims": schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "required_claims": schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "user_info": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "id_token": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + }, + }, + "optional_claims": schema.SingleNestedAttribute{ + Optional: true, + Computed: true, + Attributes: map[string]schema.Attribute{ + "user_info": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + "id_token": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, + }, + }, + }, + }, + "enabled_for_admin_portal": schema.BoolAttribute{ + Required: true, + }, + }, + } +} + +func (r *SocialProvider) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan SocialProviderConfig + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(plan.extract(ctx)...) + resp.Diagnostics.AddWarning("ClaimsConfig", fmt.Sprintf("SP: %+v ", plan.ClaimsConfig)) + model, diag := prepareSocialProviderModel(ctx, plan) + if diag.HasError() { + resp.Diagnostics.AddWarning("error preparing social provider payload ", fmt.Sprintf("Error: %+v ", diag.Errors())) + return + } + //resp.Diagnostics.AddWarning("SocialProviderModel ", fmt.Sprintf("SP: %+v ", model)) + res, err := r.cidaasClient.SocialProvider.UpsertSocialProvider(model) + if err != nil { + resp.Diagnostics.AddError("failed to create social provider", fmt.Sprintf("Error: %s", err.Error())) + return + } + resp.Diagnostics.AddWarning("SocialProviderModel Response ", fmt.Sprintf("SP: %+v ", res.Data)) + + plan.ID = types.StringValue(res.Data.ID) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *SocialProvider) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state SocialProviderConfig + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + res, err := r.cidaasClient.SocialProvider.GetSocialProvider(state.ProviderName.ValueString(), state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed to read custom provider", fmt.Sprintf("Error: %s", err.Error())) + return + } + state.ClientID = types.StringValue(res.Data.ClientID) + state.ClientSecret = types.StringValue(res.Data.ClientSecret) + state.Enabled = types.BoolValue(res.Data.Enabled) + state.EnabledForAdminPortal = types.BoolValue(res.Data.EnabledForAdminPortal) + ReadClaimsInfo(state, res.Data, ctx, resp) + resp.Diagnostics.AddWarning("State After reading.", fmt.Sprintf("state: %+v", state)) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + /* + Setting + */ + + /*claimObjectType := types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "required_claims": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "user_info": types.SetValue(types.StringType, res.Data.Claims.RequiredClaims.UserInfo), + "id_token": types.SetValue(types.StringType, res.Data.Claims.RequiredClaims.IdToken), + }, + }, + "optional_claims": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + + }, + }, + }, + }*/ + + /*objValue := types.ObjectValueMust(claimObjectType.AttrTypes, map[string]attr.Value{ + "scope_name": types.StringValue(res.Data.Claims.RequiredClaims.UserInfo), + "required": types.BoolValue(sc.Required), + "recommended": types.BoolValue(sc.Recommended), + })*/ + + /*if len(res.Data.Domains) > 0 { + state.Domains, d = types.SetValueFrom(ctx, types.StringType, res.Data.Domains) + resp.Diagnostics.Append(d...) + if resp.Diagnostics.HasError() { + return + } + }*/ +} + +func (r *SocialProvider) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state SocialProviderConfig + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(plan.extract(ctx)...) + spModel, d := prepareSocialProviderModel(ctx, plan) + resp.Diagnostics.Append(d...) + if resp.Diagnostics.HasError() { + return + } + spModel.ID = state.ID.ValueString() + _, err := r.cidaasClient.SocialProvider.UpsertSocialProvider(spModel) + if err != nil { + resp.Diagnostics.AddError("failed to update custom provider", fmt.Sprintf("Error: %s", err.Error())) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *SocialProvider) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state SocialProviderConfig + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + err := r.cidaasClient.SocialProvider.DeleteSocialProvider(state.ProviderName.ValueString(), state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed to delete social provider", fmt.Sprintf("Error: %s", err.Error())) + return + } +} + +func (sp *SocialProviderConfig) extract(ctx context.Context) diag.Diagnostics { + var diags diag.Diagnostics + if !sp.ClaimsConfig.IsNull() { + sp.claimsConfig = &ClaimsConfig{} + diags = sp.ClaimsConfig.As(ctx, sp.claimsConfig, basetypes.ObjectAsOptions{}) + sp.claimsConfig.optionalClaimsConfig = &OptionalClaimsConfig{} + diags = sp.claimsConfig.OptionalClaimsConfig.As(ctx, sp.claimsConfig.optionalClaimsConfig, basetypes.ObjectAsOptions{}) + sp.claimsConfig.requiredClaimsConfig = &RequiredClaimsConfig{} + diags = sp.claimsConfig.RequiredClaimsConfig.As(ctx, sp.claimsConfig.requiredClaimsConfig, basetypes.ObjectAsOptions{}) + //sp.claimsConfig.requiredClaimsConfig.UserInfo = make([]*string, 0, len(sp.claimsConfig.requiredClaimsConfig.UserInfo.Elements())) + /*if !sp.claimsConfig.RequiredClaimsConfig.IsNull() { + diags = sp.claimsConfig.requiredClaimsConfig.UserInfo.ElementsAs(ctx, &sp.claimsConfig.requiredClaimsConfig.UserInfo, false) + diags = sp.claimsConfig.requiredClaimsConfig.IdToken.ElementsAs(ctx, &sp.claimsConfig.requiredClaimsConfig.IdToken, false) + } + if !sp.claimsConfig.OptionalClaimsConfig.IsNull() { + diags = sp.claimsConfig.optionalClaimsConfig.UserInfo.ElementsAs(ctx, &sp.claimsConfig.optionalClaimsConfig.UserInfo, false) + diags = sp.claimsConfig.optionalClaimsConfig.IdToken.ElementsAs(ctx, &sp.claimsConfig.optionalClaimsConfig.IdToken, false) + }*/ + + } + /*if !sp.ClaimsConfig.IsNull() && sp.claimsConfig.{ + pc.scopes = make([]*CpScope, 0, len(pc.Scopes.Elements())) + diags = pc.Scopes.ElementsAs(ctx, &pc.scopes, false) + }*/ + + return diags +} + +func prepareSocialProviderModel(ctx context.Context, plan SocialProviderConfig) (*cidaas.SocialProviderModel, diag.Diagnostics) { + var sp cidaas.SocialProviderModel + sp.Name = plan.Name.ValueString() + sp.ProviderName = plan.ProviderName.ValueString() + sp.ClientID = plan.ClientID.ValueString() + sp.ClientSecret = plan.ClientSecret.ValueString() + sp.EnabledForAdminPortal = plan.EnabledForAdminPortal.ValueBool() + sp.Enabled = plan.Enabled.ValueBool() + if !plan.ClaimsConfig.IsNull() { + diag := plan.claimsConfig.requiredClaimsConfig.UserInfo.ElementsAs(ctx, &sp.Claims.RequiredClaims.UserInfo, false) + diag = plan.claimsConfig.requiredClaimsConfig.IdToken.ElementsAs(ctx, &sp.Claims.RequiredClaims.IdToken, false) + diag = plan.claimsConfig.optionalClaimsConfig.IdToken.ElementsAs(ctx, &sp.Claims.OptionalClaims.IdToken, false) + diag = plan.claimsConfig.optionalClaimsConfig.UserInfo.ElementsAs(ctx, &sp.Claims.OptionalClaims.UserInfo, false) + if diag.HasError() { + return nil, diag + } + } + return &sp, nil +} + +func ReadClaimsInfo(spc SocialProviderConfig, model cidaas.SocialProviderModel, ctx context.Context, resp *resource.ReadResponse) { + var setIdTokenHolder basetypes.SetValue + var setUserInfoHolder basetypes.SetValue + var diag diag.Diagnostics + if len(model.Claims.RequiredClaims.IdToken) > 0 { + setIdTokenHolder, diag = types.SetValueFrom(ctx, types.StringType, model.Claims.RequiredClaims.IdToken) + resp.Diagnostics.Append(diag...) + } + if len(model.Claims.RequiredClaims.UserInfo) > 0 { + setUserInfoHolder, diag = types.SetValueFrom(ctx, types.StringType, model.Claims.RequiredClaims.UserInfo) + resp.Diagnostics.Append(diag...) + } + /*reqClaimsObjectType := types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "user_info": types.SetType{ElemType: types.StringType}, + "id_token": types.SetType{ElemType: types.StringType}, + }, + } + reqClaimsObjectValue := types.ObjectValueMust( + reqClaimsObjectType.AttrTypes, map[string]attr.Value{ + "user_info": setUserInfoHolder, + "id_token": setIdTokenHolder, + }, + )*/ + if len(model.Claims.OptionalClaims.IdToken) > 0 { + setIdTokenHolder, diag = types.SetValueFrom(ctx, types.StringType, model.Claims.OptionalClaims.IdToken) + resp.Diagnostics.Append(diag...) + } + if len(model.Claims.OptionalClaims.UserInfo) > 0 { + setUserInfoHolder, diag = types.SetValueFrom(ctx, types.StringType, model.Claims.OptionalClaims.UserInfo) + resp.Diagnostics.Append(diag...) + } + fmt.Println("sss", setUserInfoHolder, setIdTokenHolder) + /*optionalClaimsObjectValue := types.ObjectValueMust( + reqClaimsObjectType.AttrTypes, map[string]attr.Value{ + "user_info": setUserInfoHolder, + "id_token": setIdTokenHolder, + }, + )*/ +} From 31aa2baa7c515617854b1781e792cd51d4e87416 Mon Sep 17 00:00:00 2001 From: thulasi Date: Fri, 21 Jun 2024 23:59:59 +0200 Subject: [PATCH 02/36] feat(): SocialProvider Resource CRUD --- helpers/cidaas/social_provider.go | 27 +++++---- .../resources/resource_social_provider.go | 57 ++++++++++++++++++- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/helpers/cidaas/social_provider.go b/helpers/cidaas/social_provider.go index d39ca2e..7525708 100644 --- a/helpers/cidaas/social_provider.go +++ b/helpers/cidaas/social_provider.go @@ -8,16 +8,16 @@ import ( ) type SocialProviderModel struct { - ID string `json:"id,omitempty"` - ClientID string `json:"client_id,omitempty"` - ClientSecret string `json:"client_secret,omitempty"` - Name string `json:"name,omitempty"` - ProviderName string `json:"provider_name,omitempty"` - Claims ClaimsModel `json:"claims,omitempty"` - EnabledForAdminPortal bool `json:"enabled_for_admin_portal"` - Enabled bool `json:"enabled"` - SPScopes []string `json:"scopes"` - SPUserInfoFields []string `json:"userinfo_fields"` + ID string `json:"id,omitempty"` + ClientID string `json:"client_id,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + Name string `json:"name,omitempty"` + ProviderName string `json:"provider_name,omitempty"` + Claims ClaimsModel `json:"claims,omitempty"` + EnabledForAdminPortal bool `json:"enabled_for_admin_portal"` + Enabled bool `json:"enabled"` + Scopes []string `json:"scopes"` + UserInfoFields []UserInfoFieldsModel `json:"userinfo_fields"` } type ClaimsModel struct { @@ -35,6 +35,13 @@ type OptionalClaimsModel struct { IdToken []string `json:"id_token,omitempty"` } +type UserInfoFieldsModel struct { + InnerKey string `json:"inner_key,omitempty"` + ExternalKey string `json:"external_key,omitempty"` + IsCustomField bool `json:"is_custom_field,omitempty"` + IsSystemField bool `json:"is_system_field,omitempty"` +} + type SocialProviderResponse struct { Success bool `json:"success,omitempty"` Status int `json:"status,omitempty"` diff --git a/internal/resources/resource_social_provider.go b/internal/resources/resource_social_provider.go index 694d3ed..9b488eb 100644 --- a/internal/resources/resource_social_provider.go +++ b/internal/resources/resource_social_provider.go @@ -30,7 +30,9 @@ type SocialProviderConfig struct { ClientSecret types.String `tfsdk:"client_secret"` ClaimsConfig types.Object `tfsdk:"claims"` EnabledForAdminPortal types.Bool `tfsdk:"enabled_for_admin_portal"` - //UserInfoFieldsConfig types.List `tfsdk:"userinfo_fields"` + UserInfoFieldsConfigs types.List `tfsdk:"userinfo_fields"` + userInfoFieldsConfigs []*UserInfoFieldsConfig + Scopes types.Set `tfsdk:"scopes"` //CreatedAt types.String `tfsdk:"created_at"` //UpdatedAt types.String `tfsdk:"updated_at"` claimsConfig *ClaimsConfig @@ -124,6 +126,10 @@ func (r *SocialProvider) Schema(_ context.Context, _ resource.SchemaRequest, res Required: true, Sensitive: true, }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + }, "claims": schema.SingleNestedAttribute{ Optional: true, Computed: true, @@ -158,6 +164,26 @@ func (r *SocialProvider) Schema(_ context.Context, _ resource.SchemaRequest, res }, }, }, + "userinfo_fields": schema.ListNestedAttribute{ + Computed: true, + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "inner_key": schema.StringAttribute{ + Required: true, + }, + "external_key": schema.StringAttribute{ + Required: true, + }, + "is_custom_field": schema.BoolAttribute{ + Required: true, + }, + "is_system_field": schema.BoolAttribute{ + Required: true, + }, + }, + }, + }, "enabled_for_admin_portal": schema.BoolAttribute{ Required: true, }, @@ -199,6 +225,15 @@ func (r *SocialProvider) Read(ctx context.Context, req resource.ReadRequest, res state.ClientSecret = types.StringValue(res.Data.ClientSecret) state.Enabled = types.BoolValue(res.Data.Enabled) state.EnabledForAdminPortal = types.BoolValue(res.Data.EnabledForAdminPortal) + + var d diag.Diagnostics + if len(res.Data.Scopes) > 0 { + state.Scopes, d = types.SetValueFrom(ctx, types.StringType, res.Data.Scopes) + resp.Diagnostics.Append(d...) + if resp.Diagnostics.HasError() { + return + } + } ReadClaimsInfo(state, res.Data, ctx, resp) resp.Diagnostics.AddWarning("State After reading.", fmt.Sprintf("state: %+v", state)) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) @@ -287,6 +322,10 @@ func (sp *SocialProviderConfig) extract(ctx context.Context) diag.Diagnostics { diags = sp.claimsConfig.optionalClaimsConfig.UserInfo.ElementsAs(ctx, &sp.claimsConfig.optionalClaimsConfig.UserInfo, false) diags = sp.claimsConfig.optionalClaimsConfig.IdToken.ElementsAs(ctx, &sp.claimsConfig.optionalClaimsConfig.IdToken, false) }*/ + if !sp.UserInfoFieldsConfigs.IsNull() { + sp.userInfoFieldsConfigs = make([]*UserInfoFieldsConfig, 0, len(sp.UserInfoFieldsConfigs.Elements())) + diags = sp.UserInfoFieldsConfigs.ElementsAs(ctx, &sp.userInfoFieldsConfigs, false) + } } /*if !sp.ClaimsConfig.IsNull() && sp.claimsConfig.{ @@ -314,6 +353,22 @@ func prepareSocialProviderModel(ctx context.Context, plan SocialProviderConfig) return nil, diag } } + if !plan.UserInfoFieldsConfigs.IsNull() { + var userInfoModels []cidaas.UserInfoFieldsModel + for _, v := range plan.userInfoFieldsConfigs { + userInfoModels = append(userInfoModels, cidaas.UserInfoFieldsModel{ + IsCustomField: v.IsCustomField.ValueBool(), + IsSystemField: v.IsSystemField.ValueBool(), + InnerKey: v.InnerKey.ValueString(), + ExternalKey: v.ExternalKey.ValueString(), + }) + } + sp.UserInfoFields = userInfoModels + } + diag := plan.Scopes.ElementsAs(ctx, &sp.Scopes, false) + if diag.HasError() { + return nil, diag + } return &sp, nil } From 2dd33db0cc02286ef686a1042d9105a6325491fe Mon Sep 17 00:00:00 2001 From: thulasi Date: Sat, 22 Jun 2024 00:13:24 +0200 Subject: [PATCH 03/36] feat(): Updated Get URL --- helpers/cidaas/social_provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/cidaas/social_provider.go b/helpers/cidaas/social_provider.go index 7525708..e7a1a1c 100644 --- a/helpers/cidaas/social_provider.go +++ b/helpers/cidaas/social_provider.go @@ -83,7 +83,7 @@ func (c *SocialProvider) UpsertSocialProvider(cp *SocialProviderModel) (*SocialP } func (c *SocialProvider) GetSocialProvider(providerName, providerId string) (*SocialProviderResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s%s&provider_id=%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers?provider_name=", providerName, providerId)) + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s?provider_name=%s&provider_id=%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers", providerName, providerId)) c.HTTPClient.SetMethod(http.MethodGet) res, err := c.HTTPClient.MakeRequest(nil) if err != nil { From 2c80a228d68b4ec4a2d5e1eb946e534312133bca Mon Sep 17 00:00:00 2001 From: thulasi Date: Sat, 22 Jun 2024 00:22:55 +0200 Subject: [PATCH 04/36] feat(): Import state --- helpers/cidaas/social_provider.go | 4 ---- internal/resources/resource_social_provider.go | 6 ++++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/helpers/cidaas/social_provider.go b/helpers/cidaas/social_provider.go index e7a1a1c..96be8b3 100644 --- a/helpers/cidaas/social_provider.go +++ b/helpers/cidaas/social_provider.go @@ -54,12 +54,8 @@ type SocialProvider struct { type SocialProviderService interface { UpsertSocialProvider(cp *SocialProviderModel) (*SocialProviderResponse, error) - GetSocialProvider(providerName, providerId string) (*SocialProviderResponse, error) DeleteSocialProvider(providerName, providerId string) error - //ConfigureCustomProvider(cp CustomProviderConfigPayload) (*CustomProviderConfigureResponse, error) - //UpdateCustomProvider(cp *CustomProviderModel) error - //https://kube-nightlybuild-dev.cidaas.de/providers-srv/multi/providers/google/bc34e3db-aeaf-4a41-9b75-c12b825ea987 } func NewSocialProvider(httpClient util.HTTPClientInterface) SocialProviderService { diff --git a/internal/resources/resource_social_provider.go b/internal/resources/resource_social_provider.go index 9b488eb..5cced81 100644 --- a/internal/resources/resource_social_provider.go +++ b/internal/resources/resource_social_provider.go @@ -8,6 +8,7 @@ import ( "github.com/Cidaas/terraform-provider-cidaas/internal/validators" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -304,6 +305,11 @@ func (r *SocialProvider) Delete(ctx context.Context, req resource.DeleteRequest, } } +func (r *SocialProvider) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + //resource. +} + func (sp *SocialProviderConfig) extract(ctx context.Context) diag.Diagnostics { var diags diag.Diagnostics if !sp.ClaimsConfig.IsNull() { From 032d3c440c1e92fb1805b3ec4b7bd084dc20b5c8 Mon Sep 17 00:00:00 2001 From: thulasi Date: Mon, 24 Jun 2024 10:43:17 +0200 Subject: [PATCH 05/36] fix(): fixed import state by concatenating two params into one( ProviderName + ID) --- .../resources/resource_social_provider.go | 74 +++++++++---------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/internal/resources/resource_social_provider.go b/internal/resources/resource_social_provider.go index 5cced81..ddf1e51 100644 --- a/internal/resources/resource_social_provider.go +++ b/internal/resources/resource_social_provider.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "strings" ) type SocialProvider struct { @@ -210,22 +211,32 @@ func (r *SocialProvider) Create(ctx context.Context, req resource.CreateRequest, } resp.Diagnostics.AddWarning("SocialProviderModel Response ", fmt.Sprintf("SP: %+v ", res.Data)) - plan.ID = types.StringValue(res.Data.ID) + //plan.ID = types.StringValue(res.Data.ID) + plan.ID = types.StringValue(res.Data.ProviderName + "_" + res.Data.ID) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } func (r *SocialProvider) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state SocialProviderConfig resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - res, err := r.cidaasClient.SocialProvider.GetSocialProvider(state.ProviderName.ValueString(), state.ID.ValueString()) + params := strings.Split(state.ID.ValueString(), "_") + if len(params) != 2 { + resp.Diagnostics.AddError("invalid id. Valid Format: providerName_ID ", fmt.Sprintf("ID: %s", state.ID.ValueString())) + return + } + //res, err := r.cidaasClient.SocialProvider.GetSocialProvider(state.ProviderName.ValueString(), state.ID.ValueString()) + res, err := r.cidaasClient.SocialProvider.GetSocialProvider(params[0], params[1]) if err != nil { - resp.Diagnostics.AddError("failed to read custom provider", fmt.Sprintf("Error: %s", err.Error())) + resp.Diagnostics.AddError("failed to read social provider", fmt.Sprintf("Error: %s", err.Error())) return } state.ClientID = types.StringValue(res.Data.ClientID) state.ClientSecret = types.StringValue(res.Data.ClientSecret) state.Enabled = types.BoolValue(res.Data.Enabled) state.EnabledForAdminPortal = types.BoolValue(res.Data.EnabledForAdminPortal) + // + //state.ID = types.StringValue(res.Data.ID) + //state.ProviderName = types.StringValue(res.Data.ID) var d diag.Diagnostics if len(res.Data.Scopes) > 0 { @@ -236,41 +247,8 @@ func (r *SocialProvider) Read(ctx context.Context, req resource.ReadRequest, res } } ReadClaimsInfo(state, res.Data, ctx, resp) - resp.Diagnostics.AddWarning("State After reading.", fmt.Sprintf("state: %+v", state)) + //resp.Diagnostics.AddWarning("State After reading.", fmt.Sprintf("state: %+v", state)) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) - /* - Setting - */ - - /*claimObjectType := types.ObjectType{ - AttrTypes: map[string]attr.Type{ - "required_claims": types.ObjectType{ - AttrTypes: map[string]attr.Type{ - "user_info": types.SetValue(types.StringType, res.Data.Claims.RequiredClaims.UserInfo), - "id_token": types.SetValue(types.StringType, res.Data.Claims.RequiredClaims.IdToken), - }, - }, - "optional_claims": types.ObjectType{ - AttrTypes: map[string]attr.Type{ - - }, - }, - }, - }*/ - - /*objValue := types.ObjectValueMust(claimObjectType.AttrTypes, map[string]attr.Value{ - "scope_name": types.StringValue(res.Data.Claims.RequiredClaims.UserInfo), - "required": types.BoolValue(sc.Required), - "recommended": types.BoolValue(sc.Recommended), - })*/ - - /*if len(res.Data.Domains) > 0 { - state.Domains, d = types.SetValueFrom(ctx, types.StringType, res.Data.Domains) - resp.Diagnostics.Append(d...) - if resp.Diagnostics.HasError() { - return - } - }*/ } func (r *SocialProvider) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { @@ -278,17 +256,28 @@ func (r *SocialProvider) Update(ctx context.Context, req resource.UpdateRequest, resp.Diagnostics.Append(req.State.Get(ctx, &state)...) resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) resp.Diagnostics.Append(plan.extract(ctx)...) + params := strings.Split(plan.ID.ValueString(), "_") + if len(params) != 2 { + resp.Diagnostics.AddError("invalid id. Valid Format: providerName_ID ", fmt.Sprintf("ID: %s", state.ID.ValueString())) + return + } + plan.ID = types.StringValue(params[1]) + state.ID = types.StringValue(params[1]) + plan.ProviderName = types.StringValue(params[0]) spModel, d := prepareSocialProviderModel(ctx, plan) + //spModel.ID = params[1] resp.Diagnostics.Append(d...) if resp.Diagnostics.HasError() { return } spModel.ID = state.ID.ValueString() - _, err := r.cidaasClient.SocialProvider.UpsertSocialProvider(spModel) + + res, err := r.cidaasClient.SocialProvider.UpsertSocialProvider(spModel) if err != nil { - resp.Diagnostics.AddError("failed to update custom provider", fmt.Sprintf("Error: %s", err.Error())) + resp.Diagnostics.AddError("failed to update social provider", fmt.Sprintf("Error: %s", err.Error())) return } + plan.ID = types.StringValue(res.Data.ProviderName + "_" + res.Data.ID) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } @@ -298,7 +287,12 @@ func (r *SocialProvider) Delete(ctx context.Context, req resource.DeleteRequest, if resp.Diagnostics.HasError() { return } - err := r.cidaasClient.SocialProvider.DeleteSocialProvider(state.ProviderName.ValueString(), state.ID.ValueString()) + params := strings.Split(state.ID.ValueString(), "_") + if len(params) != 2 { + resp.Diagnostics.AddError("invalid id. Valid Format: providerName_ID ", fmt.Sprintf("ID: %s", state.ID.ValueString())) + } + //err := r.cidaasClient.SocialProvider.DeleteSocialProvider(state.ProviderName.ValueString(), state.ID.ValueString()) + err := r.cidaasClient.SocialProvider.DeleteSocialProvider(params[0], params[1]) if err != nil { resp.Diagnostics.AddError("failed to delete social provider", fmt.Sprintf("Error: %s", err.Error())) return From 5326f4ad68c7848526e00f8dfebf472eb9f730db Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Mon, 15 Jul 2024 13:37:51 +0530 Subject: [PATCH 06/36] social provider implementation --- README.md | 116 ++++ docs/resources/social_provider.md | 124 +++++ .../cidaas_social_provider/import.sh | 4 + .../cidaas_social_provider/resource.tf | 33 ++ helpers/cidaas/social_provider.go | 25 +- helpers/util/locals.go | 12 - .../resources/resource_social_provider.go | 503 +++++++++++------- templates/resources/social_provider.md.tmpl | 25 + 8 files changed, 635 insertions(+), 207 deletions(-) create mode 100644 docs/resources/social_provider.md create mode 100644 examples/resources/cidaas_social_provider/import.sh create mode 100644 examples/resources/cidaas_social_provider/resource.tf create mode 100644 templates/resources/social_provider.md.tmpl diff --git a/README.md b/README.md index 76def93..cf3fd9a 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ Explore the following resources to understand their attributes, functionalities - [cidaas_role](#cidaas_role-resource) - [cidaas_scope_group](#cidaas_scope_group-resource) - [cidaas_scope](#cidaas_scope-resource) +- [cidaas_social_provider](#cidaas_social_provider-resource) - [cidaas_template_group](#cidaas_template_group-resource) - [cidaas_template](#cidaas_template-resource) - [cidaas_user_groups](#cidaas_user_groups-resource) @@ -1576,6 +1577,121 @@ Import is supported using the following syntax: terraform import cidaas_scope.resource_name scope_key ``` +# cidaas_social_provider (Resource) + +The `cidaas_social_provider` resource allows you to configure and manage social login providers within Cidaas. + Social login providers enable users to authenticate using their existing accounts from popular social platforms such as Google, Facebook, LinkedIn and others. + + Ensure that the below scopes are assigned to the client: +- cidaas:providers_read +- cidaas:providers_write + +## Example Usage + +```terraform +resource "cidaas_social_provider" "sample" { + name = "Sample Social Provider" + provider_name = "google" + enabled = true + client_id = "8d789b3d-b312" + client_secret = "96ae-ea2e8d8e6708" + scopes = ["profile", "email"] + enabled_for_admin_portal = true + claims = { + required_claims = { + user_info = ["name"] + id_token = ["phone_number"] + } + optional_claims = { + user_info = ["website"] + id_token = ["street_address"] + } + } + userinfo_fields = [ + { + inner_key = "sample_custom_field" + external_key = "external_sample_cf" + is_custom_field = true + is_system_field = false + }, + { + inner_key = "sample_system_field" + external_key = "external_sample_sf" + is_custom_field = false + is_system_field = true + } + ] +} +``` + + +## Schema + +### Required + +- `client_id` (String) The client ID provided by the social provider. This is used to authenticate your application with the social provider. +- `client_secret` (String, Sensitive) The client secret provided by the social provider. This is used alongside the client ID to authenticate your application with the social provider. +- `name` (String) The name of the social provider configuration. This should be unique within your Cidaas environment. +- `provider_name` (String) The name of the social provider. Supported values include `google`, `facebook`, `linkedin` etc. + +### Optional + +- `claims` (Attributes) A map defining required and optional claims to be requested from the social provider. (see [below for nested schema](#nestedatt--claims)) +- `enabled` (Boolean) A flag to enable or disable the social provider configuration. Set to `true` to enable and `false` to disable. +- `enabled_for_admin_portal` (Boolean) A flag to enable or disable the social provider for the admin portal. Set to `true` to enable and `false` to disable. +- `scopes` (Set of String) A list of scopes of the social provider. +- `userinfo_fields` (Attributes List) A list of user info fields to be mapped between the social provider and Cidaas. (see [below for nested schema](#nestedatt--userinfo_fields)) + +### Read-Only + +- `id` (String) The unique identifier of the social provider + + +### Nested Schema for `claims` + +Optional: + +- `optional_claims` (Attributes) Defines the claims that are optional from the social provider. (see [below for nested schema](#nestedatt--claims--optional_claims)) +- `required_claims` (Attributes) Defines the claims that are required from the social provider. (see [below for nested schema](#nestedatt--claims--required_claims)) + + +### Nested Schema for `claims.optional_claims` + +Optional: + +- `id_token` (Set of String) A list of ID token claims that are optional. +- `user_info` (Set of String) A list of user information claims that are optional. + + + +### Nested Schema for `claims.required_claims` + +Optional: + +- `id_token` (Set of String) A list of ID token claims that are required. +- `user_info` (Set of String) A list of user information claims that are required. + + + + +### Nested Schema for `userinfo_fields` + +Required: + +- `external_key` (String) The external key used by the social provider. +- `inner_key` (String) The internal key used by Cidaas. +- `is_custom_field` (Boolean) A flag indicating whether the field is a custom field. Set to `true` if it is a custom field. +- `is_system_field` (Boolean) A flag indicating whether the field is a system field. Set to `true` if it is a system field. + +## Import + +The import identifier of resource social provider is a combination of **provider_name** and **provider_id** joined by the special character ":". +For example, if the resource name is `sample` with provider_name `google` and provider_id `8d789b3d-b312-4251`, the import statement would be: + +```shell +terraform import cidaas_social_provider.sample google:8d789b3d-b312-4251 +``` + # cidaas_template_group (Resource) The cidaas_template_group resource in the provider is used to define and manage templates groups within the Cidaas system. Template Groups categorize your communication templates allowing you to map preferred templates to specific clients effectively. diff --git a/docs/resources/social_provider.md b/docs/resources/social_provider.md new file mode 100644 index 0000000..af02d32 --- /dev/null +++ b/docs/resources/social_provider.md @@ -0,0 +1,124 @@ +--- +page_title: "cidaas_social_provider Resource - cidaas" +subcategory: "" +description: |- + The cidaas_social_provider resource allows you to configure and manage social login providers within Cidaas. + Social login providers enable users to authenticate using their existing accounts from popular social platforms such as Google, Facebook, LinkedIn and others. + Ensure that the below scopes are assigned to the client: + cidaas:providers_readcidaas:providers_write +--- + +# cidaas_social_provider (Resource) + +The `cidaas_social_provider` resource allows you to configure and manage social login providers within Cidaas. + Social login providers enable users to authenticate using their existing accounts from popular social platforms such as Google, Facebook, LinkedIn and others. + + Ensure that the below scopes are assigned to the client: +- cidaas:providers_read +- cidaas:providers_write + +## Example Usage + +```terraform +resource "cidaas_social_provider" "sample" { + name = "Sample Social Provider" + provider_name = "google" + enabled = true + client_id = "8d789b3d-b312" + client_secret = "96ae-ea2e8d8e6708" + scopes = ["profile", "email"] + enabled_for_admin_portal = true + claims = { + required_claims = { + user_info = ["name"] + id_token = ["phone_number"] + } + optional_claims = { + user_info = ["website"] + id_token = ["street_address"] + } + } + userinfo_fields = [ + { + inner_key = "sample_custom_field" + external_key = "external_sample_cf" + is_custom_field = true + is_system_field = false + }, + { + inner_key = "sample_system_field" + external_key = "external_sample_sf" + is_custom_field = false + is_system_field = true + } + ] +} +``` + + +## Schema + +### Required + +- `client_id` (String) The client ID provided by the social provider. This is used to authenticate your application with the social provider. +- `client_secret` (String, Sensitive) The client secret provided by the social provider. This is used alongside the client ID to authenticate your application with the social provider. +- `name` (String) The name of the social provider configuration. This should be unique within your Cidaas environment. +- `provider_name` (String) The name of the social provider. Supported values include `google`, `facebook`, `linkedin` etc. + +### Optional + +- `claims` (Attributes) A map defining required and optional claims to be requested from the social provider. (see [below for nested schema](#nestedatt--claims)) +- `enabled` (Boolean) A flag to enable or disable the social provider configuration. Set to `true` to enable and `false` to disable. +- `enabled_for_admin_portal` (Boolean) A flag to enable or disable the social provider for the admin portal. Set to `true` to enable and `false` to disable. +- `scopes` (Set of String) A list of scopes of the social provider. +- `userinfo_fields` (Attributes List) A list of user info fields to be mapped between the social provider and Cidaas. (see [below for nested schema](#nestedatt--userinfo_fields)) + +### Read-Only + +- `id` (String) The unique identifier of the social provider + + +### Nested Schema for `claims` + +Optional: + +- `optional_claims` (Attributes) Defines the claims that are optional from the social provider. (see [below for nested schema](#nestedatt--claims--optional_claims)) +- `required_claims` (Attributes) Defines the claims that are required from the social provider. (see [below for nested schema](#nestedatt--claims--required_claims)) + + +### Nested Schema for `claims.optional_claims` + +Optional: + +- `id_token` (Set of String) A list of ID token claims that are optional. +- `user_info` (Set of String) A list of user information claims that are optional. + + + +### Nested Schema for `claims.required_claims` + +Optional: + +- `id_token` (Set of String) A list of ID token claims that are required. +- `user_info` (Set of String) A list of user information claims that are required. + + + + +### Nested Schema for `userinfo_fields` + +Required: + +- `external_key` (String) The external key used by the social provider. +- `inner_key` (String) The internal key used by Cidaas. +- `is_custom_field` (Boolean) A flag indicating whether the field is a custom field. Set to `true` if it is a custom field. +- `is_system_field` (Boolean) A flag indicating whether the field is a system field. Set to `true` if it is a system field. + +## Import + +The import identifier of resource social provider is a combination of **provider_name** and **provider_id** joined by the special character ":". +For example, if the resource name is `sample` with provider_name `google` and provider_id `8d789b3d-b312-4251`, the import statement would be: + +```shell +terraform import cidaas_social_provider.sample google:8d789b3d-b312-4251 +``` \ No newline at end of file diff --git a/examples/resources/cidaas_social_provider/import.sh b/examples/resources/cidaas_social_provider/import.sh new file mode 100644 index 0000000..b1d847b --- /dev/null +++ b/examples/resources/cidaas_social_provider/import.sh @@ -0,0 +1,4 @@ +# Below is the command to import a social provider +# Here, import identifier is a combination of provider_name and provider_id joined by the special character ":". +# For example, if the resource name is "sample" with provider_name "google" and provider_id "8d789b3d-b312-4251", the import statement would be: +terraform import cidaas_social_provider.sample google:8d789b3d-b312-4251 \ No newline at end of file diff --git a/examples/resources/cidaas_social_provider/resource.tf b/examples/resources/cidaas_social_provider/resource.tf new file mode 100644 index 0000000..85318b4 --- /dev/null +++ b/examples/resources/cidaas_social_provider/resource.tf @@ -0,0 +1,33 @@ +resource "cidaas_social_provider" "sample" { + name = "Sample Social Provider" + provider_name = "google" + enabled = true + client_id = "8d789b3d-b312" + client_secret = "96ae-ea2e8d8e6708" + scopes = ["profile", "email"] + enabled_for_admin_portal = true + claims = { + required_claims = { + user_info = ["name"] + id_token = ["phone_number"] + } + optional_claims = { + user_info = ["website"] + id_token = ["street_address"] + } + } + userinfo_fields = [ + { + inner_key = "sample_custom_field" + external_key = "external_sample_cf" + is_custom_field = true + is_system_field = false + }, + { + inner_key = "sample_system_field" + external_key = "external_sample_sf" + is_custom_field = false + is_system_field = true + } + ] +} diff --git a/helpers/cidaas/social_provider.go b/helpers/cidaas/social_provider.go index 96be8b3..1eae0fb 100644 --- a/helpers/cidaas/social_provider.go +++ b/helpers/cidaas/social_provider.go @@ -3,8 +3,9 @@ package cidaas import ( "encoding/json" "fmt" - "github.com/Cidaas/terraform-provider-cidaas/helpers/util" "net/http" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/util" ) type SocialProviderModel struct { @@ -13,7 +14,7 @@ type SocialProviderModel struct { ClientSecret string `json:"client_secret,omitempty"` Name string `json:"name,omitempty"` ProviderName string `json:"provider_name,omitempty"` - Claims ClaimsModel `json:"claims,omitempty"` + Claims *ClaimsModel `json:"claims,omitempty"` EnabledForAdminPortal bool `json:"enabled_for_admin_portal"` Enabled bool `json:"enabled"` Scopes []string `json:"scopes"` @@ -27,12 +28,12 @@ type ClaimsModel struct { type RequiredClaimsModel struct { UserInfo []string `json:"user_info,omitempty"` - IdToken []string `json:"id_token,omitempty"` + IDToken []string `json:"id_token,omitempty"` } type OptionalClaimsModel struct { UserInfo []string `json:"user_info,omitempty"` - IdToken []string `json:"id_token,omitempty"` + IDToken []string `json:"id_token,omitempty"` } type UserInfoFieldsModel struct { @@ -53,16 +54,16 @@ type SocialProvider struct { } type SocialProviderService interface { - UpsertSocialProvider(cp *SocialProviderModel) (*SocialProviderResponse, error) - GetSocialProvider(providerName, providerId string) (*SocialProviderResponse, error) - DeleteSocialProvider(providerName, providerId string) error + Upsert(cp *SocialProviderModel) (*SocialProviderResponse, error) + Get(providerName, providerID string) (*SocialProviderResponse, error) + Delete(providerName, providerID string) error } func NewSocialProvider(httpClient util.HTTPClientInterface) SocialProviderService { return &SocialProvider{HTTPClient: httpClient} } -func (c *SocialProvider) UpsertSocialProvider(cp *SocialProviderModel) (*SocialProviderResponse, error) { +func (c *SocialProvider) Upsert(cp *SocialProviderModel) (*SocialProviderResponse, error) { c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers")) c.HTTPClient.SetMethod(http.MethodPost) res, err := c.HTTPClient.MakeRequest(cp) @@ -78,8 +79,8 @@ func (c *SocialProvider) UpsertSocialProvider(cp *SocialProviderModel) (*SocialP return &response, nil } -func (c *SocialProvider) GetSocialProvider(providerName, providerId string) (*SocialProviderResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s?provider_name=%s&provider_id=%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers", providerName, providerId)) +func (c *SocialProvider) Get(providerName, providerID string) (*SocialProviderResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s?provider_name=%s&provider_id=%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers", providerName, providerID)) c.HTTPClient.SetMethod(http.MethodGet) res, err := c.HTTPClient.MakeRequest(nil) if err != nil { @@ -94,8 +95,8 @@ func (c *SocialProvider) GetSocialProvider(providerName, providerId string) (*So return &response, nil } -func (c *SocialProvider) DeleteSocialProvider(providerName, providerId string) error { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s/%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers", providerName, providerId)) +func (c *SocialProvider) Delete(providerName, providerID string) error { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s/%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers", providerName, providerID)) c.HTTPClient.SetMethod(http.MethodDelete) res, err := c.HTTPClient.MakeRequest(nil) if err != nil { diff --git a/helpers/util/locals.go b/helpers/util/locals.go index c95ec5d..b46a984 100644 --- a/helpers/util/locals.go +++ b/helpers/util/locals.go @@ -159,15 +159,3 @@ func GetLanguageForLocale(locale string) string { } return "en" } - -var AllowedClaims = []string{ - "name", "given_name", "family_name", "nickname", "preferred_username", "profile", - "picture", "website", "email", "email_verified", "gender", "middle_name", "birthdate", "zoneinfo", "locale", - "phone_number", "phone_number_verified", "formatted", "street_address", "locality", "region", "postal_code", "country", -} - -var AllowedProviders = []string{ - "google", "facebook", "linkedin", "amazon", "foursquare", "github", - "instagram", "yammer", "wordpress", "microsoft", "yahoo", "officeenterprise", "salesforce", "paypal_sandbox", "paypal", - "apple", "twitter", "netid", "netid_qa", -} diff --git a/internal/resources/resource_social_provider.go b/internal/resources/resource_social_provider.go index ddf1e51..bb4082a 100644 --- a/internal/resources/resource_social_provider.go +++ b/internal/resources/resource_social_provider.go @@ -3,22 +3,38 @@ package resources import ( "context" "fmt" + "strings" + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" "github.com/Cidaas/terraform-provider-cidaas/helpers/util" "github.com/Cidaas/terraform-provider-cidaas/internal/validators" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - "strings" ) +// var allowedClaims = []string{ +// "name", "given_name", "family_name", "nickname", "preferred_username", "profile", +// "picture", "website", "email", "email_verified", "gender", "middle_name", "birthdate", "zoneinfo", "locale", +// "phone_number", "phone_number_verified", "formatted", "street_address", "locality", "region", "postal_code", "country", +// } + +var allowedProviders = []string{ + "google", "facebook", "linkedin", "amazon", "foursquare", "github", + "instagram", "yammer", "wordpress", "microsoft", "yahoo", "officeenterprise", "salesforce", "paypal_sandbox", "paypal", + "apple", "twitter", "netid", "netid_qa", +} + type SocialProvider struct { cidaasClient *cidaas.Client } @@ -30,34 +46,28 @@ type SocialProviderConfig struct { Enabled types.Bool `tfsdk:"enabled"` ClientID types.String `tfsdk:"client_id"` ClientSecret types.String `tfsdk:"client_secret"` - ClaimsConfig types.Object `tfsdk:"claims"` + Claims types.Object `tfsdk:"claims"` EnabledForAdminPortal types.Bool `tfsdk:"enabled_for_admin_portal"` - UserInfoFieldsConfigs types.List `tfsdk:"userinfo_fields"` - userInfoFieldsConfigs []*UserInfoFieldsConfig - Scopes types.Set `tfsdk:"scopes"` - //CreatedAt types.String `tfsdk:"created_at"` - //UpdatedAt types.String `tfsdk:"updated_at"` - claimsConfig *ClaimsConfig -} + UserInfoFields types.List `tfsdk:"userinfo_fields"` + Scopes types.Set `tfsdk:"scopes"` -type ClaimsConfig struct { - RequiredClaimsConfig types.Object `tfsdk:"required_claims"` - OptionalClaimsConfig types.Object `tfsdk:"optional_claims"` - requiredClaimsConfig *RequiredClaimsConfig - optionalClaimsConfig *OptionalClaimsConfig + claims *Claims + userInfoFields []*UserInfoFields } -type RequiredClaimsConfig struct { - UserInfo types.Set `tfsdk:"user_info"` - IdToken types.Set `tfsdk:"id_token"` +type Claims struct { + RequiredClaims types.Object `tfsdk:"required_claims"` + OptionalClaims types.Object `tfsdk:"optional_claims"` + requiredClaims *ClaimConfigs + optionalClaims *ClaimConfigs } -type OptionalClaimsConfig struct { +type ClaimConfigs struct { UserInfo types.Set `tfsdk:"user_info"` - IdToken types.Set `tfsdk:"id_token"` + IDToken types.Set `tfsdk:"id_token"` } -type UserInfoFieldsConfig struct { +type UserInfoFields struct { InnerKey types.String `tfsdk:"inner_key"` ExternalKey types.String `tfsdk:"external_key"` IsCustomField types.Bool `tfsdk:"is_custom_field"` @@ -68,6 +78,42 @@ func NewSocialProvider() resource.Resource { return &SocialProvider{} } +func (r *SocialProvider) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, res *resource.ValidateConfigResponse) { + var config SocialProviderConfig + res.Diagnostics.Append(req.Config.Get(ctx, &config)...) + res.Diagnostics.Append(config.extract(ctx)...) + if !config.UserInfoFields.IsNull() { + innerKeysCustom := make(map[string]bool) + innerKeysSystem := make(map[string]bool) + for _, v := range config.userInfoFields { + if v.IsCustomField.Equal(v.IsSystemField) { + res.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("For inner_key \033[1m%s\033[0m the fields is_custom_field and is_system_field cannot have the same value.", v.InnerKey.ValueString()), + ) + } + if v.IsCustomField.ValueBool() { + if innerKeysCustom[v.InnerKey.ValueString()] { + res.Diagnostics.AddError( + "Duplicate Custom Field Key", + fmt.Sprintf("The custom field with the inner_key \033[1m%s\033[0m is repeated. Each key must be unique.", v.InnerKey.ValueString()), + ) + } + innerKeysCustom[v.InnerKey.ValueString()] = true + } + if v.IsSystemField.ValueBool() { + if innerKeysSystem[v.InnerKey.ValueString()] { + res.Diagnostics.AddError( + "Duplicate System Field Key", + fmt.Sprintf("The system field with the inner_key \033[1m%s\033[0m is repeated. Each key must be unique.", v.InnerKey.ValueString()), + ) + } + innerKeysSystem[v.InnerKey.ValueString()] = true + } + } + } +} + func (r *SocialProvider) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_social_provider" } @@ -89,18 +135,25 @@ func (r *SocialProvider) Configure(_ context.Context, req resource.ConfigureRequ func (r *SocialProvider) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ + MarkdownDescription: "The `cidaas_social_provider` resource allows you to configure and manage social login providers within Cidaas." + + "\n Social login providers enable users to authenticate using their existing accounts from popular social platforms such as Google, Facebook, LinkedIn and others." + + "\n\n Ensure that the below scopes are assigned to the client:" + + "\n- cidaas:providers_read" + + "\n- cidaas:providers_write", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, + MarkdownDescription: "The unique identifier of the social provider", }, "name": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ &validators.UniqueIdentifier{}, }, + MarkdownDescription: "The name of the social provider configuration. This should be unique within your Cidaas environment.", }, "provider_name": schema.StringAttribute{ Required: true, @@ -108,86 +161,133 @@ func (r *SocialProvider) Schema(_ context.Context, _ resource.SchemaRequest, res &validators.UniqueIdentifier{}, }, Validators: []validator.String{ - stringvalidator.OneOf( - func() []string { - var provider = make([]string, len(util.AllowedProviders)) //nolint:gofumpt - for i, cType := range util.AllowedProviders { - provider[i] = cType - } - return provider - }()...), + stringvalidator.OneOf(allowedProviders...), }, + MarkdownDescription: "The name of the social provider. Supported values include `google`, `facebook`, `linkedin` etc.", }, "enabled": schema.BoolAttribute{ - Required: true, + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + MarkdownDescription: "A flag to enable or disable the social provider configuration. Set to `true` to enable and `false` to disable.", }, "client_id": schema.StringAttribute{ - Required: true, + Required: true, + MarkdownDescription: "The client ID provided by the social provider. This is used to authenticate your application with the social provider.", }, "client_secret": schema.StringAttribute{ - Required: true, - Sensitive: true, + Required: true, + Sensitive: true, + MarkdownDescription: "The client secret provided by the social provider. This is used alongside the client ID to authenticate your application with the social provider.", }, "scopes": schema.SetAttribute{ - ElementType: types.StringType, - Optional: true, + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "A list of scopes of the social provider.", }, "claims": schema.SingleNestedAttribute{ - Optional: true, - Computed: true, + Optional: true, + Computed: true, + MarkdownDescription: "A map defining required and optional claims to be requested from the social provider.", Attributes: map[string]schema.Attribute{ "required_claims": schema.SingleNestedAttribute{ - Optional: true, - Computed: true, + Optional: true, + MarkdownDescription: "Defines the claims that are required from the social provider.", Attributes: map[string]schema.Attribute{ "user_info": schema.SetAttribute{ - ElementType: types.StringType, - Optional: true, + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "A list of user information claims that are required.", }, "id_token": schema.SetAttribute{ - ElementType: types.StringType, - Optional: true, + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "A list of ID token claims that are required.", }, }, }, "optional_claims": schema.SingleNestedAttribute{ - Optional: true, - Computed: true, + Optional: true, + MarkdownDescription: "Defines the claims that are optional from the social provider.", Attributes: map[string]schema.Attribute{ "user_info": schema.SetAttribute{ - ElementType: types.StringType, - Optional: true, + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "A list of user information claims that are optional.", }, "id_token": schema.SetAttribute{ - ElementType: types.StringType, - Optional: true, + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "A list of ID token claims that are optional.", }, }, }, }, + Default: objectdefault.StaticValue( + types.ObjectValueMust( + map[string]attr.Type{ + "required_claims": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "user_info": types.SetType{ElemType: types.StringType}, + "id_token": types.SetType{ElemType: types.StringType}, + }, + }, + "optional_claims": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "user_info": types.SetType{ElemType: types.StringType}, + "id_token": types.SetType{ElemType: types.StringType}, + }, + }, + }, + map[string]attr.Value{ + "required_claims": types.ObjectValueMust(map[string]attr.Type{ + "user_info": types.SetType{ElemType: types.StringType}, + "id_token": types.SetType{ElemType: types.StringType}, + }, + map[string]attr.Value{ + "user_info": types.SetNull(types.StringType), + "id_token": types.SetNull(types.StringType), + }), + "optional_claims": types.ObjectValueMust(map[string]attr.Type{ + "user_info": types.SetType{ElemType: types.StringType}, + "id_token": types.SetType{ElemType: types.StringType}, + }, + map[string]attr.Value{ + "user_info": types.SetNull(types.StringType), + "id_token": types.SetNull(types.StringType), + }), + }), + ), }, "userinfo_fields": schema.ListNestedAttribute{ - Computed: true, - Optional: true, + Optional: true, + MarkdownDescription: "A list of user info fields to be mapped between the social provider and Cidaas.", NestedObject: schema.NestedAttributeObject{ Attributes: map[string]schema.Attribute{ "inner_key": schema.StringAttribute{ - Required: true, + Required: true, + MarkdownDescription: "The internal key used by Cidaas.", }, "external_key": schema.StringAttribute{ - Required: true, + Required: true, + MarkdownDescription: "The external key used by the social provider.", }, "is_custom_field": schema.BoolAttribute{ - Required: true, + Required: true, + MarkdownDescription: "A flag indicating whether the field is a custom field. Set to `true` if it is a custom field.", }, "is_system_field": schema.BoolAttribute{ - Required: true, + Required: true, + MarkdownDescription: "A flag indicating whether the field is a system field. Set to `true` if it is a system field.", }, }, }, }, "enabled_for_admin_portal": schema.BoolAttribute{ - Required: true, + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + MarkdownDescription: "A flag to enable or disable the social provider for the admin portal. Set to `true` to enable and `false` to disable.", }, }, } @@ -197,57 +297,45 @@ func (r *SocialProvider) Create(ctx context.Context, req resource.CreateRequest, var plan SocialProviderConfig resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) resp.Diagnostics.Append(plan.extract(ctx)...) - resp.Diagnostics.AddWarning("ClaimsConfig", fmt.Sprintf("SP: %+v ", plan.ClaimsConfig)) model, diag := prepareSocialProviderModel(ctx, plan) if diag.HasError() { - resp.Diagnostics.AddWarning("error preparing social provider payload ", fmt.Sprintf("Error: %+v ", diag.Errors())) + resp.Diagnostics.AddError("error preparing social provider payload ", fmt.Sprintf("Error: %+v ", diag.Errors())) return } - //resp.Diagnostics.AddWarning("SocialProviderModel ", fmt.Sprintf("SP: %+v ", model)) - res, err := r.cidaasClient.SocialProvider.UpsertSocialProvider(model) + res, err := r.cidaasClient.SocialProvider.Upsert(model) if err != nil { resp.Diagnostics.AddError("failed to create social provider", fmt.Sprintf("Error: %s", err.Error())) return } - resp.Diagnostics.AddWarning("SocialProviderModel Response ", fmt.Sprintf("SP: %+v ", res.Data)) - - //plan.ID = types.StringValue(res.Data.ID) - plan.ID = types.StringValue(res.Data.ProviderName + "_" + res.Data.ID) + plan.ID = util.StringValueOrNull(&res.Data.ID) + resp.Diagnostics.Append(setClaimsInfo(&plan, res.Data.Claims)...) + resp.Diagnostics.Append(setUserInfoFields(&plan, res.Data.UserInfoFields)...) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } func (r *SocialProvider) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state SocialProviderConfig resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - params := strings.Split(state.ID.ValueString(), "_") - if len(params) != 2 { - resp.Diagnostics.AddError("invalid id. Valid Format: providerName_ID ", fmt.Sprintf("ID: %s", state.ID.ValueString())) + if resp.Diagnostics.HasError() { return } - //res, err := r.cidaasClient.SocialProvider.GetSocialProvider(state.ProviderName.ValueString(), state.ID.ValueString()) - res, err := r.cidaasClient.SocialProvider.GetSocialProvider(params[0], params[1]) + res, err := r.cidaasClient.SocialProvider.Get(state.ProviderName.ValueString(), state.ID.ValueString()) if err != nil { resp.Diagnostics.AddError("failed to read social provider", fmt.Sprintf("Error: %s", err.Error())) return } - state.ClientID = types.StringValue(res.Data.ClientID) - state.ClientSecret = types.StringValue(res.Data.ClientSecret) - state.Enabled = types.BoolValue(res.Data.Enabled) - state.EnabledForAdminPortal = types.BoolValue(res.Data.EnabledForAdminPortal) - // - //state.ID = types.StringValue(res.Data.ID) - //state.ProviderName = types.StringValue(res.Data.ID) - - var d diag.Diagnostics - if len(res.Data.Scopes) > 0 { - state.Scopes, d = types.SetValueFrom(ctx, types.StringType, res.Data.Scopes) - resp.Diagnostics.Append(d...) - if resp.Diagnostics.HasError() { - return - } - } - ReadClaimsInfo(state, res.Data, ctx, resp) - //resp.Diagnostics.AddWarning("State After reading.", fmt.Sprintf("state: %+v", state)) + + state.ID = util.StringValueOrNull(&res.Data.ID) + state.Name = util.StringValueOrNull(&res.Data.Name) + state.ProviderName = util.StringValueOrNull(&res.Data.ProviderName) + state.ClientID = util.StringValueOrNull(&res.Data.ClientID) + state.ClientSecret = util.StringValueOrNull(&res.Data.ClientSecret) + state.Enabled = util.BoolValueOrNull(&res.Data.Enabled) + state.EnabledForAdminPortal = util.BoolValueOrNull(&res.Data.EnabledForAdminPortal) + state.Scopes = util.SetValueOrNull(res.Data.Scopes) + + resp.Diagnostics.Append(setClaimsInfo(&state, res.Data.Claims)...) + resp.Diagnostics.Append(setUserInfoFields(&state, res.Data.UserInfoFields)...) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } @@ -256,28 +344,17 @@ func (r *SocialProvider) Update(ctx context.Context, req resource.UpdateRequest, resp.Diagnostics.Append(req.State.Get(ctx, &state)...) resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) resp.Diagnostics.Append(plan.extract(ctx)...) - params := strings.Split(plan.ID.ValueString(), "_") - if len(params) != 2 { - resp.Diagnostics.AddError("invalid id. Valid Format: providerName_ID ", fmt.Sprintf("ID: %s", state.ID.ValueString())) - return - } - plan.ID = types.StringValue(params[1]) - state.ID = types.StringValue(params[1]) - plan.ProviderName = types.StringValue(params[0]) - spModel, d := prepareSocialProviderModel(ctx, plan) - //spModel.ID = params[1] - resp.Diagnostics.Append(d...) - if resp.Diagnostics.HasError() { + model, diag := prepareSocialProviderModel(ctx, plan) + if diag.HasError() { + resp.Diagnostics.AddError("error preparing social provider payload ", fmt.Sprintf("Error: %+v ", diag.Errors())) return } - spModel.ID = state.ID.ValueString() - - res, err := r.cidaasClient.SocialProvider.UpsertSocialProvider(spModel) + model.ID = state.ID.ValueString() + _, err := r.cidaasClient.SocialProvider.Upsert(model) if err != nil { resp.Diagnostics.AddError("failed to update social provider", fmt.Sprintf("Error: %s", err.Error())) return } - plan.ID = types.StringValue(res.Data.ProviderName + "_" + res.Data.ID) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } @@ -287,12 +364,7 @@ func (r *SocialProvider) Delete(ctx context.Context, req resource.DeleteRequest, if resp.Diagnostics.HasError() { return } - params := strings.Split(state.ID.ValueString(), "_") - if len(params) != 2 { - resp.Diagnostics.AddError("invalid id. Valid Format: providerName_ID ", fmt.Sprintf("ID: %s", state.ID.ValueString())) - } - //err := r.cidaasClient.SocialProvider.DeleteSocialProvider(state.ProviderName.ValueString(), state.ID.ValueString()) - err := r.cidaasClient.SocialProvider.DeleteSocialProvider(params[0], params[1]) + err := r.cidaasClient.SocialProvider.Delete(state.ProviderName.ValueString(), state.ID.ValueString()) if err != nil { resp.Diagnostics.AddError("failed to delete social provider", fmt.Sprintf("Error: %s", err.Error())) return @@ -300,62 +372,102 @@ func (r *SocialProvider) Delete(ctx context.Context, req resource.DeleteRequest, } func (r *SocialProvider) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) - //resource. + id := req.ID + parts := strings.Split(id, ":") + if len(parts) != 2 { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: 'provider_name:provider_id', got: %s", id), + ) + return + } + providerName := parts[0] + providerID := parts[1] + if !util.StringInSlice(providerName, allowedProviders) { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Invalid provider_name provided in import identifier. Valid provider_names %+v, got: %s", allowedProviders, providerName), + ) + return + } + resp.State.SetAttribute(ctx, path.Root("provider_name"), providerName) + resp.State.SetAttribute(ctx, path.Root("id"), providerID) } func (sp *SocialProviderConfig) extract(ctx context.Context) diag.Diagnostics { var diags diag.Diagnostics - if !sp.ClaimsConfig.IsNull() { - sp.claimsConfig = &ClaimsConfig{} - diags = sp.ClaimsConfig.As(ctx, sp.claimsConfig, basetypes.ObjectAsOptions{}) - sp.claimsConfig.optionalClaimsConfig = &OptionalClaimsConfig{} - diags = sp.claimsConfig.OptionalClaimsConfig.As(ctx, sp.claimsConfig.optionalClaimsConfig, basetypes.ObjectAsOptions{}) - sp.claimsConfig.requiredClaimsConfig = &RequiredClaimsConfig{} - diags = sp.claimsConfig.RequiredClaimsConfig.As(ctx, sp.claimsConfig.requiredClaimsConfig, basetypes.ObjectAsOptions{}) - //sp.claimsConfig.requiredClaimsConfig.UserInfo = make([]*string, 0, len(sp.claimsConfig.requiredClaimsConfig.UserInfo.Elements())) - /*if !sp.claimsConfig.RequiredClaimsConfig.IsNull() { - diags = sp.claimsConfig.requiredClaimsConfig.UserInfo.ElementsAs(ctx, &sp.claimsConfig.requiredClaimsConfig.UserInfo, false) - diags = sp.claimsConfig.requiredClaimsConfig.IdToken.ElementsAs(ctx, &sp.claimsConfig.requiredClaimsConfig.IdToken, false) + if !sp.Claims.IsNull() { + sp.claims = &Claims{} + diags = sp.Claims.As(ctx, sp.claims, basetypes.ObjectAsOptions{}) + if !sp.claims.OptionalClaims.IsNull() { + sp.claims.optionalClaims = &ClaimConfigs{} + diags = sp.claims.OptionalClaims.As(ctx, &sp.claims.optionalClaims, basetypes.ObjectAsOptions{}) } - if !sp.claimsConfig.OptionalClaimsConfig.IsNull() { - diags = sp.claimsConfig.optionalClaimsConfig.UserInfo.ElementsAs(ctx, &sp.claimsConfig.optionalClaimsConfig.UserInfo, false) - diags = sp.claimsConfig.optionalClaimsConfig.IdToken.ElementsAs(ctx, &sp.claimsConfig.optionalClaimsConfig.IdToken, false) - }*/ - if !sp.UserInfoFieldsConfigs.IsNull() { - sp.userInfoFieldsConfigs = make([]*UserInfoFieldsConfig, 0, len(sp.UserInfoFieldsConfigs.Elements())) - diags = sp.UserInfoFieldsConfigs.ElementsAs(ctx, &sp.userInfoFieldsConfigs, false) + if !sp.claims.RequiredClaims.IsNull() { + sp.claims.requiredClaims = &ClaimConfigs{} + diags = sp.claims.RequiredClaims.As(ctx, &sp.claims.requiredClaims, basetypes.ObjectAsOptions{}) } - } - /*if !sp.ClaimsConfig.IsNull() && sp.claimsConfig.{ - pc.scopes = make([]*CpScope, 0, len(pc.Scopes.Elements())) - diags = pc.Scopes.ElementsAs(ctx, &pc.scopes, false) - }*/ - + if !sp.UserInfoFields.IsNull() { + sp.userInfoFields = make([]*UserInfoFields, 0, len(sp.UserInfoFields.Elements())) + diags = sp.UserInfoFields.ElementsAs(ctx, &sp.userInfoFields, false) + } return diags } func prepareSocialProviderModel(ctx context.Context, plan SocialProviderConfig) (*cidaas.SocialProviderModel, diag.Diagnostics) { var sp cidaas.SocialProviderModel + sp.Name = plan.Name.ValueString() sp.ProviderName = plan.ProviderName.ValueString() sp.ClientID = plan.ClientID.ValueString() sp.ClientSecret = plan.ClientSecret.ValueString() - sp.EnabledForAdminPortal = plan.EnabledForAdminPortal.ValueBool() sp.Enabled = plan.Enabled.ValueBool() - if !plan.ClaimsConfig.IsNull() { - diag := plan.claimsConfig.requiredClaimsConfig.UserInfo.ElementsAs(ctx, &sp.Claims.RequiredClaims.UserInfo, false) - diag = plan.claimsConfig.requiredClaimsConfig.IdToken.ElementsAs(ctx, &sp.Claims.RequiredClaims.IdToken, false) - diag = plan.claimsConfig.optionalClaimsConfig.IdToken.ElementsAs(ctx, &sp.Claims.OptionalClaims.IdToken, false) - diag = plan.claimsConfig.optionalClaimsConfig.UserInfo.ElementsAs(ctx, &sp.Claims.OptionalClaims.UserInfo, false) - if diag.HasError() { - return nil, diag + sp.EnabledForAdminPortal = plan.EnabledForAdminPortal.ValueBool() + + sp.Claims = &cidaas.ClaimsModel{} + sp.Claims.RequiredClaims = cidaas.RequiredClaimsModel{} + sp.Claims.OptionalClaims = cidaas.OptionalClaimsModel{} + + var diags diag.Diagnostics + if !plan.Claims.IsNull() && !plan.claims.RequiredClaims.IsNull() { + if !plan.claims.requiredClaims.UserInfo.IsNull() { + diags = plan.claims.requiredClaims.UserInfo.ElementsAs(ctx, &sp.Claims.RequiredClaims.UserInfo, false) + if diags.HasError() { + return nil, diags + } + } + if !plan.claims.requiredClaims.IDToken.IsNull() { + diags = plan.claims.requiredClaims.IDToken.ElementsAs(ctx, &sp.Claims.RequiredClaims.IDToken, false) + if diags.HasError() { + return nil, diags + } + } + } + + if !plan.Claims.IsNull() && !plan.claims.OptionalClaims.IsNull() { + if !plan.claims.optionalClaims.UserInfo.IsNull() { + diags = plan.claims.optionalClaims.UserInfo.ElementsAs(ctx, &sp.Claims.OptionalClaims.UserInfo, false) + if diags.HasError() { + return nil, diags + } } + if !plan.claims.optionalClaims.IDToken.IsNull() { + diags = plan.claims.optionalClaims.IDToken.ElementsAs(ctx, &sp.Claims.OptionalClaims.IDToken, false) + if diags.HasError() { + return nil, diags + } + } + } + + diags = plan.Scopes.ElementsAs(ctx, &sp.Scopes, false) + if diags.HasError() { + return nil, diags } - if !plan.UserInfoFieldsConfigs.IsNull() { + + if !plan.UserInfoFields.IsNull() { var userInfoModels []cidaas.UserInfoFieldsModel - for _, v := range plan.userInfoFieldsConfigs { + for _, v := range plan.userInfoFields { userInfoModels = append(userInfoModels, cidaas.UserInfoFieldsModel{ IsCustomField: v.IsCustomField.ValueBool(), IsSystemField: v.IsSystemField.ValueBool(), @@ -365,50 +477,75 @@ func prepareSocialProviderModel(ctx context.Context, plan SocialProviderConfig) } sp.UserInfoFields = userInfoModels } - diag := plan.Scopes.ElementsAs(ctx, &sp.Scopes, false) - if diag.HasError() { - return nil, diag - } - return &sp, nil + return &sp, diags } -func ReadClaimsInfo(spc SocialProviderConfig, model cidaas.SocialProviderModel, ctx context.Context, resp *resource.ReadResponse) { - var setIdTokenHolder basetypes.SetValue - var setUserInfoHolder basetypes.SetValue - var diag diag.Diagnostics - if len(model.Claims.RequiredClaims.IdToken) > 0 { - setIdTokenHolder, diag = types.SetValueFrom(ctx, types.StringType, model.Claims.RequiredClaims.IdToken) - resp.Diagnostics.Append(diag...) - } - if len(model.Claims.RequiredClaims.UserInfo) > 0 { - setUserInfoHolder, diag = types.SetValueFrom(ctx, types.StringType, model.Claims.RequiredClaims.UserInfo) - resp.Diagnostics.Append(diag...) +func setClaimsInfo(state *SocialProviderConfig, claims *cidaas.ClaimsModel) diag.Diagnostics { + var diags diag.Diagnostics + if claims != nil { + state.Claims, diags = types.ObjectValue( + map[string]attr.Type{ + "required_claims": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "user_info": types.SetType{ElemType: types.StringType}, + "id_token": types.SetType{ElemType: types.StringType}, + }, + }, + "optional_claims": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "user_info": types.SetType{ElemType: types.StringType}, + "id_token": types.SetType{ElemType: types.StringType}, + }, + }, + }, + map[string]attr.Value{ + "required_claims": types.ObjectValueMust(map[string]attr.Type{ + "user_info": types.SetType{ElemType: types.StringType}, + "id_token": types.SetType{ElemType: types.StringType}, + }, + map[string]attr.Value{ + "user_info": util.SetValueOrNull(claims.RequiredClaims.UserInfo), + "id_token": util.SetValueOrNull(claims.RequiredClaims.IDToken), + }), + "optional_claims": types.ObjectValueMust(map[string]attr.Type{ + "user_info": types.SetType{ElemType: types.StringType}, + "id_token": types.SetType{ElemType: types.StringType}, + }, + map[string]attr.Value{ + "user_info": util.SetValueOrNull(claims.OptionalClaims.UserInfo), + "id_token": util.SetValueOrNull(claims.OptionalClaims.IDToken), + }), + }) } - /*reqClaimsObjectType := types.ObjectType{ + return diags +} + +func setUserInfoFields(state *SocialProviderConfig, ufs []cidaas.UserInfoFieldsModel) diag.Diagnostics { + var diags diag.Diagnostics + ufObjectType := types.ObjectType{ AttrTypes: map[string]attr.Type{ - "user_info": types.SetType{ElemType: types.StringType}, - "id_token": types.SetType{ElemType: types.StringType}, + "inner_key": types.StringType, + "external_key": types.StringType, + "is_custom_field": types.BoolType, + "is_system_field": types.BoolType, }, } - reqClaimsObjectValue := types.ObjectValueMust( - reqClaimsObjectType.AttrTypes, map[string]attr.Value{ - "user_info": setUserInfoHolder, - "id_token": setIdTokenHolder, - }, - )*/ - if len(model.Claims.OptionalClaims.IdToken) > 0 { - setIdTokenHolder, diag = types.SetValueFrom(ctx, types.StringType, model.Claims.OptionalClaims.IdToken) - resp.Diagnostics.Append(diag...) - } - if len(model.Claims.OptionalClaims.UserInfo) > 0 { - setUserInfoHolder, diag = types.SetValueFrom(ctx, types.StringType, model.Claims.OptionalClaims.UserInfo) - resp.Diagnostics.Append(diag...) + if len(ufs) < 1 { + state.UserInfoFields = types.ListNull(ufObjectType) + } else { + var ufObjectValues []attr.Value + for _, uf := range ufs { + objValue := types.ObjectValueMust( + ufObjectType.AttrTypes, + map[string]attr.Value{ + "inner_key": util.StringValueOrNull(&uf.InnerKey), + "external_key": util.StringValueOrNull(&uf.ExternalKey), + "is_custom_field": util.BoolValueOrNull(&uf.IsCustomField), + "is_system_field": util.BoolValueOrNull(&uf.IsSystemField), + }) + ufObjectValues = append(ufObjectValues, objValue) + } + state.UserInfoFields, diags = types.ListValue(ufObjectType, ufObjectValues) } - fmt.Println("sss", setUserInfoHolder, setIdTokenHolder) - /*optionalClaimsObjectValue := types.ObjectValueMust( - reqClaimsObjectType.AttrTypes, map[string]attr.Value{ - "user_info": setUserInfoHolder, - "id_token": setIdTokenHolder, - }, - )*/ + return diags } diff --git a/templates/resources/social_provider.md.tmpl b/templates/resources/social_provider.md.tmpl new file mode 100644 index 0000000..90dc033 --- /dev/null +++ b/templates/resources/social_provider.md.tmpl @@ -0,0 +1,25 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/resources/cidaas_social_provider/resource.tf" }} + +{{ .SchemaMarkdown | trimspace }} + +## Import + +The import identifier of resource social provider is a combination of **provider_name** and **provider_id** joined by the special character ":". +For example, if the resource name is `sample` with provider_name `google` and provider_id `8d789b3d-b312-4251`, the import statement would be: + +```shell +terraform import cidaas_social_provider.sample google:8d789b3d-b312-4251 +``` \ No newline at end of file From 0f270cee4860f1429ebd6f5404648cac02dce47f Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Mon, 15 Jul 2024 15:13:17 +0530 Subject: [PATCH 07/36] add providers_delete scope in doc --- README.md | 2 ++ docs/resources/custom_provider.md | 3 ++- docs/resources/social_provider.md | 3 ++- internal/resources/resource_custom_provider.go | 3 ++- internal/resources/resource_social_provider.go | 3 ++- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cf3fd9a..d54c7bf 100644 --- a/README.md +++ b/README.md @@ -856,6 +856,7 @@ This example demonstrates the configuration of a custom provider resource for in Ensure that the below scopes are assigned to the client with the specified `client_id`: - cidaas:providers_read - cidaas:providers_write +- cidaas:providers_delete ### V2 to V3 Migration: @@ -1585,6 +1586,7 @@ The `cidaas_social_provider` resource allows you to configure and manage social Ensure that the below scopes are assigned to the client: - cidaas:providers_read - cidaas:providers_write +- cidaas:providers_delete ## Example Usage diff --git a/docs/resources/custom_provider.md b/docs/resources/custom_provider.md index e37fcf0..70bd2cf 100644 --- a/docs/resources/custom_provider.md +++ b/docs/resources/custom_provider.md @@ -4,7 +4,7 @@ subcategory: "" description: |- This example demonstrates the configuration of a custom provider resource for interacting with Cidaas. Ensure that the below scopes are assigned to the client with the specified client_id: - cidaas:providers_readcidaas:providers_write + cidaas:providers_readcidaas:providers_writecidaas:providers_delete --- # cidaas_custom_provider (Resource) @@ -14,6 +14,7 @@ This example demonstrates the configuration of a custom provider resource for in Ensure that the below scopes are assigned to the client with the specified `client_id`: - cidaas:providers_read - cidaas:providers_write +- cidaas:providers_delete ### V2 to V3 Migration: diff --git a/docs/resources/social_provider.md b/docs/resources/social_provider.md index af02d32..d968335 100644 --- a/docs/resources/social_provider.md +++ b/docs/resources/social_provider.md @@ -5,7 +5,7 @@ description: |- The cidaas_social_provider resource allows you to configure and manage social login providers within Cidaas. Social login providers enable users to authenticate using their existing accounts from popular social platforms such as Google, Facebook, LinkedIn and others. Ensure that the below scopes are assigned to the client: - cidaas:providers_readcidaas:providers_write + cidaas:providers_readcidaas:providers_writecidaas:providers_delete --- # cidaas_social_provider (Resource) @@ -16,6 +16,7 @@ The `cidaas_social_provider` resource allows you to configure and manage social Ensure that the below scopes are assigned to the client: - cidaas:providers_read - cidaas:providers_write +- cidaas:providers_delete ## Example Usage diff --git a/internal/resources/resource_custom_provider.go b/internal/resources/resource_custom_provider.go index f89019f..98ec70e 100644 --- a/internal/resources/resource_custom_provider.go +++ b/internal/resources/resource_custom_provider.go @@ -119,7 +119,8 @@ func (r *CustomProvider) Schema(_ context.Context, _ resource.SchemaRequest, res MarkdownDescription: "This example demonstrates the configuration of a custom provider resource for interacting with Cidaas." + "\n\n Ensure that the below scopes are assigned to the client with the specified `client_id`:" + "\n- cidaas:providers_read" + - "\n- cidaas:providers_write", + "\n- cidaas:providers_write" + + "\n- cidaas:providers_delete", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, diff --git a/internal/resources/resource_social_provider.go b/internal/resources/resource_social_provider.go index bb4082a..1133a61 100644 --- a/internal/resources/resource_social_provider.go +++ b/internal/resources/resource_social_provider.go @@ -139,7 +139,8 @@ func (r *SocialProvider) Schema(_ context.Context, _ resource.SchemaRequest, res "\n Social login providers enable users to authenticate using their existing accounts from popular social platforms such as Google, Facebook, LinkedIn and others." + "\n\n Ensure that the below scopes are assigned to the client:" + "\n- cidaas:providers_read" + - "\n- cidaas:providers_write", + "\n- cidaas:providers_write" + + "\n- cidaas:providers_delete", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, From 8c6e641225676ee6072201e2f3ac8853d265f66c Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Tue, 16 Jul 2024 17:14:06 +0530 Subject: [PATCH 08/36] lint fix in resource_social_provider file --- internal/resources/resource_social_provider.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/resources/resource_social_provider.go b/internal/resources/resource_social_provider.go index 1133a61..a716119 100644 --- a/internal/resources/resource_social_provider.go +++ b/internal/resources/resource_social_provider.go @@ -536,13 +536,17 @@ func setUserInfoFields(state *SocialProviderConfig, ufs []cidaas.UserInfoFieldsM } else { var ufObjectValues []attr.Value for _, uf := range ufs { + innerKey := uf.InnerKey + externalKey := uf.ExternalKey + isCustomField := uf.IsCustomField + isSystemField := uf.IsSystemField objValue := types.ObjectValueMust( ufObjectType.AttrTypes, map[string]attr.Value{ - "inner_key": util.StringValueOrNull(&uf.InnerKey), - "external_key": util.StringValueOrNull(&uf.ExternalKey), - "is_custom_field": util.BoolValueOrNull(&uf.IsCustomField), - "is_system_field": util.BoolValueOrNull(&uf.IsSystemField), + "inner_key": util.StringValueOrNull(&innerKey), + "external_key": util.StringValueOrNull(&externalKey), + "is_custom_field": util.BoolValueOrNull(&isCustomField), + "is_system_field": util.BoolValueOrNull(&isSystemField), }) ufObjectValues = append(ufObjectValues, objValue) } From 3e5d95cd5f395755292c5c1079953cf41e039c22 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Wed, 31 Jul 2024 11:17:02 +0530 Subject: [PATCH 09/36] unknown check & document update --- README.md | 17 +++++++++++++++++ docs/resources/social_provider.md | 17 +++++++++++++++++ internal/resources/resource_social_provider.go | 8 ++++---- templates/resources/social_provider.md.tmpl | 17 +++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d54c7bf..9c31dee 100644 --- a/README.md +++ b/README.md @@ -1626,6 +1626,23 @@ resource "cidaas_social_provider" "sample" { } ``` +### Configuring a Social Provider to a Client +To configure a social provider for a client in your Terraform configuration, you need to update the `cidaas_app` resources with the details from the `cidaas_social_provider` resource. Below is an example demonstrating how you can configure a social provider for a client: + +```terraform +resource "cidaas_app" "app_sample" { + ... + social_providers = [ + { + provider_name = cidaas_social_provider.sample.provider_name + social_id = cidaas_social_provider.sample.id + display_name = "google" + } + ] +... +} +``` + ## Schema diff --git a/docs/resources/social_provider.md b/docs/resources/social_provider.md index d968335..1787d4a 100644 --- a/docs/resources/social_provider.md +++ b/docs/resources/social_provider.md @@ -56,6 +56,23 @@ resource "cidaas_social_provider" "sample" { } ``` +### Configuring a Social Provider to a Client +To configure a social provider for a client in your Terraform configuration, you need to update the `cidaas_app` resources with the details from the `cidaas_social_provider` resource. Below is an example demonstrating how you can configure a social provider for a client: + +```terraform +resource "cidaas_app" "app_sample" { + ... + social_providers = [ + { + provider_name = cidaas_social_provider.sample.provider_name + social_id = cidaas_social_provider.sample.id + display_name = "google" + } + ] +... +} +``` + ## Schema diff --git a/internal/resources/resource_social_provider.go b/internal/resources/resource_social_provider.go index a716119..62da515 100644 --- a/internal/resources/resource_social_provider.go +++ b/internal/resources/resource_social_provider.go @@ -397,19 +397,19 @@ func (r *SocialProvider) ImportState(ctx context.Context, req resource.ImportSta func (sp *SocialProviderConfig) extract(ctx context.Context) diag.Diagnostics { var diags diag.Diagnostics - if !sp.Claims.IsNull() { + if !sp.Claims.IsNull() && !sp.Claims.IsUnknown() { sp.claims = &Claims{} diags = sp.Claims.As(ctx, sp.claims, basetypes.ObjectAsOptions{}) - if !sp.claims.OptionalClaims.IsNull() { + if !sp.claims.OptionalClaims.IsNull() && !sp.claims.OptionalClaims.IsUnknown() { sp.claims.optionalClaims = &ClaimConfigs{} diags = sp.claims.OptionalClaims.As(ctx, &sp.claims.optionalClaims, basetypes.ObjectAsOptions{}) } - if !sp.claims.RequiredClaims.IsNull() { + if !sp.claims.RequiredClaims.IsNull() && !sp.claims.RequiredClaims.IsUnknown() { sp.claims.requiredClaims = &ClaimConfigs{} diags = sp.claims.RequiredClaims.As(ctx, &sp.claims.requiredClaims, basetypes.ObjectAsOptions{}) } } - if !sp.UserInfoFields.IsNull() { + if !sp.UserInfoFields.IsNull() && !sp.UserInfoFields.IsUnknown() { sp.userInfoFields = make([]*UserInfoFields, 0, len(sp.UserInfoFields.Elements())) diags = sp.UserInfoFields.ElementsAs(ctx, &sp.userInfoFields, false) } diff --git a/templates/resources/social_provider.md.tmpl b/templates/resources/social_provider.md.tmpl index 90dc033..3cc8252 100644 --- a/templates/resources/social_provider.md.tmpl +++ b/templates/resources/social_provider.md.tmpl @@ -13,6 +13,23 @@ description: |- {{ tffile "examples/resources/cidaas_social_provider/resource.tf" }} +### Configuring a Social Provider to a Client +To configure a social provider for a client in your Terraform configuration, you need to update the `cidaas_app` resources with the details from the `cidaas_social_provider` resource. Below is an example demonstrating how you can configure a social provider for a client: + +```terraform +resource "cidaas_app" "app_sample" { + ... + social_providers = [ + { + provider_name = cidaas_social_provider.sample.provider_name + social_id = cidaas_social_provider.sample.id + display_name = "google" + } + ] +... +} +``` + {{ .SchemaMarkdown | trimspace }} ## Import From 7e7b4bff4f1c01205a12305400dbeed0682fb5dc Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Tue, 6 Aug 2024 10:22:19 +0530 Subject: [PATCH 10/36] resource password policy --- helpers/cidaas/cidaas.go | 1 + helpers/cidaas/password_policy.go | 65 +++++++ internal/provider.go | 1 + .../resources/resource_password_policy.go | 178 ++++++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 helpers/cidaas/password_policy.go create mode 100644 internal/resources/resource_password_policy.go diff --git a/helpers/cidaas/cidaas.go b/helpers/cidaas/cidaas.go index 5193324..99a3e44 100644 --- a/helpers/cidaas/cidaas.go +++ b/helpers/cidaas/cidaas.go @@ -23,6 +23,7 @@ type Client struct { RegField RegFieldService TemplateGroup TemplateGroupService Template TemplateService + PasswordPolicy PasswordPolicyService } type ClientConfig struct { diff --git a/helpers/cidaas/password_policy.go b/helpers/cidaas/password_policy.go new file mode 100644 index 0000000..7180dc0 --- /dev/null +++ b/helpers/cidaas/password_policy.go @@ -0,0 +1,65 @@ +package cidaas + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/util" +) + +type PasswordPolicyModel struct { + MaximumLength int64 `json:"maximumLength"` + MinimumLength int64 `json:"minimumLength"` + NoOfSpecialChars int64 `json:"noOfSpecialChars"` + NoOfDigits int64 `json:"noOfDigits"` + LowerAndUppercase bool `json:"lowerAndUpperCase"` + ReuseLimit int64 `json:"reuseLimit"` + ExpirationInDays int64 `json:"expirationInDays"` + NoOfDaysToRemind int64 `json:"noOfDaysToRemind"` +} + +type PasswordPolicyResponse struct { + Success bool `json:"success"` + Status int `json:"status"` + Data PasswordPolicyModel `json:"data,omitempty"` +} + +type PasswordPolicy struct { + HTTPClient util.HTTPClientInterface +} +type PasswordPolicyService interface { + Get() (*PasswordPolicyResponse, error) + Update(cp PasswordPolicyModel) error +} + +func NewPasswordPolicy(httpClient util.HTTPClientInterface) PasswordPolicyService { + return &PasswordPolicy{HTTPClient: httpClient} +} + +func (c *PasswordPolicy) Get() (*PasswordPolicyResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "password-policy-srv/policy")) + c.HTTPClient.SetMethod(http.MethodGet) + res, err := c.HTTPClient.MakeRequest(nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + var response PasswordPolicyResponse + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + } + return &response, nil +} + +func (c *PasswordPolicy) Update(payload PasswordPolicyModel) error { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "password-policy-srv/policy")) + c.HTTPClient.SetMethod(http.MethodPut) + res, err := c.HTTPClient.MakeRequest(payload) + if err != nil { + return err + } + defer res.Body.Close() + return nil +} diff --git a/internal/provider.go b/internal/provider.go index 66295a7..1b2256d 100644 --- a/internal/provider.go +++ b/internal/provider.go @@ -69,6 +69,7 @@ func (p *cidaasProvider) Resources(_ context.Context) []func() resource.Resource cidaasResource.NewRegFieldResource, cidaasResource.NewTemplateGroupResource, cidaasResource.NewTemplateResource, + cidaasResource.NewPasswordPolicy, } } diff --git a/internal/resources/resource_password_policy.go b/internal/resources/resource_password_policy.go new file mode 100644 index 0000000..d9ff3a5 --- /dev/null +++ b/internal/resources/resource_password_policy.go @@ -0,0 +1,178 @@ +package resources + +import ( + "context" + "fmt" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type PasswordPolicy struct { + cidaasClient *cidaas.Client +} + +type PasswordPolicyConfig struct { + ID types.String `tfsdk:"id"` + MaximumLength types.Int64 `tfsdk:"maximum_length"` + MinimumLength types.Int64 `tfsdk:"minimum_length"` + NoOfSpecialChars types.Int64 `tfsdk:"no_of_special_chars"` + NoOfDigits types.Int64 `tfsdk:"no_of_digits"` + LowerAndUppercase types.Bool `tfsdk:"lower_and_uppercase"` + ReuseLimit types.Int64 `tfsdk:"reuse_limit"` + ExpirationInDays types.Int64 `tfsdk:"expiration_in_days"` + NoOfDaysToRemind types.Int64 `tfsdk:"no_of_days_to_remind"` +} + +func NewPasswordPolicy() resource.Resource { + return &PasswordPolicy{} +} + +func (r *PasswordPolicy) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_password_policy" +} + +func (r *PasswordPolicy) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*cidaas.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected cidaas.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.cidaasClient = client +} + +func (r *PasswordPolicy) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { //nolint:gocognit + if req.Plan.Raw.IsNull() { + return + } + + var plan PasswordPolicyConfig + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + if plan.ID.IsUnknown() { + resp.Diagnostics.AddError( + "Resource Creation Not Allowed", + "Creating this resource using 'terraform apply' is not allowed. You must first import the existing resource using 'terraform import'. After the import, you can perform updates as needed.", + ) + } +} +func (r *PasswordPolicy) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + // The id is used to determine the operation type. + // If id is unknown, it indicates a create operation. Otherwise, it indicates an update operation. + // Note: Creation is not allowed, only updates are permitted after importing the resource. + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "maximum_length": schema.Int64Attribute{ + Required: true, + }, + "minimum_length": schema.Int64Attribute{ + Required: true, + }, + "no_of_special_chars": schema.Int64Attribute{ + Required: true, + }, + "no_of_digits": schema.Int64Attribute{ + Required: true, + }, + "lower_and_uppercase": schema.BoolAttribute{ + Required: true, + }, + "reuse_limit": schema.Int64Attribute{ + Required: true, + Validators: []validator.Int64{ + int64validator.AtMost(5), + }, + }, + "expiration_in_days": schema.Int64Attribute{ + Required: true, + }, + "no_of_days_to_remind": schema.Int64Attribute{ + Required: true, + }, + }, + } +} + +func (r *PasswordPolicy) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + resp.Diagnostics.AddError( + "Resource Creation Not Allowed", + "Creating this resource using 'terraform apply' is not allowed. You must first import the existing resource using 'terraform import'. After the import, you can perform updates as needed.", + ) +} + +func (r *PasswordPolicy) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state PasswordPolicyConfig + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + res, err := r.cidaasClient.PasswordPolicy.Get() + if err != nil { + resp.Diagnostics.AddError("failed to read password policy", fmt.Sprintf("Error: %s", err.Error())) + return + } + + state.MaximumLength = types.Int64Value(res.Data.MaximumLength) + state.MinimumLength = types.Int64Value(res.Data.MinimumLength) + state.NoOfSpecialChars = types.Int64Value(res.Data.NoOfSpecialChars) + state.NoOfDigits = types.Int64Value(res.Data.NoOfDigits) + state.LowerAndUppercase = types.BoolValue(res.Data.LowerAndUppercase) + state.ReuseLimit = types.Int64Value(res.Data.ReuseLimit) + state.ExpirationInDays = types.Int64Value(res.Data.ExpirationInDays) + state.NoOfDaysToRemind = types.Int64Value(res.Data.NoOfDaysToRemind) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *PasswordPolicy) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state PasswordPolicyConfig + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + payload := cidaas.PasswordPolicyModel{ + MaximumLength: plan.MaximumLength.ValueInt64(), + MinimumLength: plan.MinimumLength.ValueInt64(), + NoOfSpecialChars: plan.NoOfSpecialChars.ValueInt64(), + NoOfDigits: plan.NoOfDigits.ValueInt64(), + LowerAndUppercase: plan.LowerAndUppercase.ValueBool(), + ReuseLimit: plan.ReuseLimit.ValueInt64(), + ExpirationInDays: plan.ExpirationInDays.ValueInt64(), + NoOfDaysToRemind: plan.NoOfDaysToRemind.ValueInt64(), + } + + err := r.cidaasClient.PasswordPolicy.Update(payload) + if err != nil { + resp.Diagnostics.AddError("failed to update password policy", fmt.Sprintf("Error: %s", err.Error())) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *PasswordPolicy) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +func (r *PasswordPolicy) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} From ed24e41d5be705d2dfbdd04cc2b859e558c7bf0d Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Tue, 6 Aug 2024 13:45:53 +0530 Subject: [PATCH 11/36] password policy documentation --- README.md | 56 ++++++++++++++++ docs/resources/password_policy.md | 64 +++++++++++++++++++ .../cidaas_password_policy/import.sh | 4 ++ .../cidaas_password_policy/resource.tf | 10 +++ .../resources/resource_password_policy.go | 41 ++++++++---- 5 files changed, 163 insertions(+), 12 deletions(-) create mode 100644 docs/resources/password_policy.md create mode 100644 examples/resources/cidaas_password_policy/import.sh create mode 100644 examples/resources/cidaas_password_policy/resource.tf diff --git a/README.md b/README.md index 9c31dee..469714c 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ Explore the following resources to understand their attributes, functionalities - [cidaas_custom_provider](#cidaas_custom_provider-resource) - [cidaas_group_type](#cidaas_group_type-resource-previously-cidaas_user_group_category) - [cidaas_hosted_page](#cidaas_hosted_page-resource) +- [cidaas_password_policy](#cidaas_password_policy-resource) - [cidaas_registration_field](#cidaas_registration_field-resource) - [cidaas_role](#cidaas_role-resource) - [cidaas_scope_group](#cidaas_scope_group-resource) @@ -1143,6 +1144,61 @@ Import is supported using the following syntax: ```shell terraform import cidaas_hosted_page.resource_name hosted_page_id ``` + +# cidaas_password_policy (Resource) + +The Password Policy resource in the provider allows you to manage the password policy within the Cidaas. +Note that resource creation is not allowed, only updates are permitted after the resource has been imported. + + Ensure that the below scopes are assigned to the client with the specified `client_id`: +- cidaas:password_policy_read +- cidaas:password_policy_write + +## Example Usage + +```terraform +resource "cidaas_password_policy" "sample" { + minimum_length = 8 + lower_and_uppercase = true + no_of_digits = 1 + expiration_in_days = 30 + no_of_special_chars = 1 + no_of_days_to_remind = 1 + reuse_limit = 1 + maximum_length = 20 +} +``` + + +## Schema + +### Required + +- `expiration_in_days` (Number) The number of days after which the password expires. +- `lower_and_uppercase` (Boolean) Specifies whether the password must contain both lowercase and uppercase letters. +- `maximum_length` (Number) The maximum length allowed for the password. The `maximum_length` must be greater than `minimum_length` +- `minimum_length` (Number) The minimum length required for the password. The `minimum_length` must be greater than or equal to the sum of `no_of_special_chars`, `no_of_digits`, and `lowercase/uppercase` characters. +- `no_of_days_to_remind` (Number) The number of days before the password expiration to remind the user to change their password. +- `no_of_digits` (Number) The required number of digits in the password. +- `no_of_special_chars` (Number) The required number of special characters in the password. +- `reuse_limit` (Number) The number of previous passwords that cannot be reused. This number cannot exceed 5. + +### Read-Only + +- `id` (String) Unique identifier of the password policy. This will be set to the same value as the import identifier. +While the cidaas API does not require an identifier to import password policy, Terraform's import command does. Therefore, you can provide any arbitrary string as the identifier. + +## Import + +Import is supported using the following syntax: + +```shell +# The cidaas API does not require an identifier to import password policy but Terraform's import command does. +# Therefore, you can provide any arbitrary string as the identifier. It will be set to the `id` attribute in the schema. + +terraform import cidaas_password_policy.resource_name cidaas +``` + # cidaas_registration_field (Resource) The `cidaas_registration_page_field` in the provider allows management of registration fields in the Cidaas system. This resource enables you to configure and customize the fields displayed during user registration. diff --git a/docs/resources/password_policy.md b/docs/resources/password_policy.md new file mode 100644 index 0000000..01c0f4e --- /dev/null +++ b/docs/resources/password_policy.md @@ -0,0 +1,64 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cidaas_password_policy Resource - cidaas" +subcategory: "" +description: |- + The Password Policy resource in the provider allows you to manage the password policy within the Cidaas. + Note that resource creation is not allowed, only updates are permitted after the resource has been imported. + Ensure that the below scopes are assigned to the client with the specified client_id: + cidaas:password_policy_readcidaas:password_policy_write +--- + +# cidaas_password_policy (Resource) + +The Password Policy resource in the provider allows you to manage the password policy within the Cidaas. +Note that resource creation is not allowed, only updates are permitted after the resource has been imported. + + Ensure that the below scopes are assigned to the client with the specified `client_id`: +- cidaas:password_policy_read +- cidaas:password_policy_write + +## Example Usage + +```terraform +resource "cidaas_password_policy" "sample" { + minimum_length = 8 + lower_and_uppercase = true + no_of_digits = 1 + expiration_in_days = 30 + no_of_special_chars = 1 + no_of_days_to_remind = 1 + reuse_limit = 1 + maximum_length = 20 +} +``` + + +## Schema + +### Required + +- `expiration_in_days` (Number) The number of days after which the password expires. +- `lower_and_uppercase` (Boolean) Specifies whether the password must contain both lowercase and uppercase letters. +- `maximum_length` (Number) The maximum length allowed for the password. The `maximum_length` must be greater than `minimum_length` +- `minimum_length` (Number) The minimum length required for the password. The `minimum_length` must be greater than or equal to the sum of `no_of_special_chars`, `no_of_digits`, and `lowercase/uppercase` characters. +- `no_of_days_to_remind` (Number) The number of days before the password expiration to remind the user to change their password. +- `no_of_digits` (Number) The required number of digits in the password. +- `no_of_special_chars` (Number) The required number of special characters in the password. +- `reuse_limit` (Number) The number of previous passwords that cannot be reused. This number cannot exceed 5. + +### Read-Only + +- `id` (String) Unique identifier of the password policy. This will be set to the same value as the import identifier. +While the cidaas API does not require an identifier to import password policy, Terraform's import command does. Therefore, you can provide any arbitrary string as the identifier. + +## Import + +Import is supported using the following syntax: + +```shell +# The cidaas API does not require an identifier to import password policy but Terraform's import command does. +# Therefore, you can provide any arbitrary string as the identifier. It will be set to the `id` attribute in the schema. + +terraform import cidaas_password_policy.resource_name cidaas +``` diff --git a/examples/resources/cidaas_password_policy/import.sh b/examples/resources/cidaas_password_policy/import.sh new file mode 100644 index 0000000..57c0371 --- /dev/null +++ b/examples/resources/cidaas_password_policy/import.sh @@ -0,0 +1,4 @@ +# The cidaas API does not require an identifier to import password policy but Terraform's import command does. +# Therefore, you can provide any arbitrary string as the identifier. It will be set to the `id` attribute in the schema. + +terraform import cidaas_password_policy.resource_name cidaas diff --git a/examples/resources/cidaas_password_policy/resource.tf b/examples/resources/cidaas_password_policy/resource.tf new file mode 100644 index 0000000..b444df3 --- /dev/null +++ b/examples/resources/cidaas_password_policy/resource.tf @@ -0,0 +1,10 @@ +resource "cidaas_password_policy" "sample" { + minimum_length = 8 + lower_and_uppercase = true + no_of_digits = 1 + expiration_in_days = 30 + no_of_special_chars = 1 + no_of_days_to_remind = 1 + reuse_limit = 1 + maximum_length = 20 +} diff --git a/internal/resources/resource_password_policy.go b/internal/resources/resource_password_policy.go index d9ff3a5..8e6a994 100644 --- a/internal/resources/resource_password_policy.go +++ b/internal/resources/resource_password_policy.go @@ -58,13 +58,11 @@ func (r *PasswordPolicy) ModifyPlan(ctx context.Context, req resource.ModifyPlan if req.Plan.Raw.IsNull() { return } - var plan PasswordPolicyConfig resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } - if plan.ID.IsUnknown() { resp.Diagnostics.AddError( "Resource Creation Not Allowed", @@ -72,50 +70,69 @@ func (r *PasswordPolicy) ModifyPlan(ctx context.Context, req resource.ModifyPlan ) } } + func (r *PasswordPolicy) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ + MarkdownDescription: "The Password Policy resource in the provider allows you to manage the password policy within the Cidaas." + + "\nNote that resource creation is not allowed, only updates are permitted after the resource has been imported." + + "\n\n Ensure that the below scopes are assigned to the client with the specified `client_id`:" + + "\n- cidaas:password_policy_read" + + "\n- cidaas:password_policy_write", Attributes: map[string]schema.Attribute{ // The id is used to determine the operation type. // If id is unknown, it indicates a create operation. Otherwise, it indicates an update operation. // Note: Creation is not allowed, only updates are permitted after importing the resource. "id": schema.StringAttribute{ Computed: true, + MarkdownDescription: "Unique identifier of the password policy. This will be set to the same value as the import identifier." + + "\nWhile the cidaas API does not require an identifier to import password policy, Terraform's import command does. Therefore, you can provide any arbitrary string as the identifier.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "maximum_length": schema.Int64Attribute{ - Required: true, + Required: true, + MarkdownDescription: "The maximum length allowed for the password. The `maximum_length` must be greater than `minimum_length`", + Validators: []validator.Int64{ + int64validator.AtLeastSumOf(path.MatchRoot("minimum_length")), + }, }, "minimum_length": schema.Int64Attribute{ - Required: true, + Required: true, + MarkdownDescription: "The minimum length required for the password. The `minimum_length` must be greater than or equal to the sum of `no_of_special_chars`, `no_of_digits`, and `lowercase/uppercase` characters.", }, "no_of_special_chars": schema.Int64Attribute{ - Required: true, + Required: true, + MarkdownDescription: "The required number of special characters in the password.", }, "no_of_digits": schema.Int64Attribute{ - Required: true, + Required: true, + MarkdownDescription: "The required number of digits in the password.", }, "lower_and_uppercase": schema.BoolAttribute{ - Required: true, + Required: true, + MarkdownDescription: "Specifies whether the password must contain both lowercase and uppercase letters.", }, "reuse_limit": schema.Int64Attribute{ - Required: true, + Required: true, + MarkdownDescription: "The number of previous passwords that cannot be reused. This number cannot exceed 5.", Validators: []validator.Int64{ int64validator.AtMost(5), }, }, "expiration_in_days": schema.Int64Attribute{ - Required: true, + Required: true, + MarkdownDescription: "The number of days after which the password expires.", }, "no_of_days_to_remind": schema.Int64Attribute{ - Required: true, + Required: true, + MarkdownDescription: "The number of days before the password expiration to remind the user to change their password.", }, }, } } -func (r *PasswordPolicy) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { +func (r *PasswordPolicy) Create(_ context.Context, _ resource.CreateRequest, resp *resource.CreateResponse) { resp.Diagnostics.AddError( "Resource Creation Not Allowed", "Creating this resource using 'terraform apply' is not allowed. You must first import the existing resource using 'terraform import'. After the import, you can perform updates as needed.", @@ -170,7 +187,7 @@ func (r *PasswordPolicy) Update(ctx context.Context, req resource.UpdateRequest, resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } -func (r *PasswordPolicy) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +func (r *PasswordPolicy) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) { } func (r *PasswordPolicy) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { From 79846a93d69caaf447d677bf79ecab3ee9406087 Mon Sep 17 00:00:00 2001 From: thulasi Date: Fri, 14 Jun 2024 12:44:13 +0200 Subject: [PATCH 12/36] fix(): Added consents feat(): Added consent version as list in consent feat(): Added consent locale feat(): Added Validation for consent_type and locale feat(): Added Validation for URL feat(): Added list of consent locales feat(): destroy/delete feat(): Rollback in case of error in other API Calls.. Consent changed to ConsentClient with duplicate available in reg field consent_group refactor & markdown description added removed consent, consent_local & consent_version removed consent, consent_local & consent_version from helper documntation & lint fix --- README.md | 45 +++++ docs/resources/consent_group.md | 54 ++++++ .../resources/cidaas_consent_group/import.sh | 1 + .../cidaas_consent_group/resource.tf | 4 + helpers/cidaas/cidaas.go | 1 + helpers/cidaas/consent_group.go | 79 ++++++++ internal/provider.go | 1 + internal/resources/resource_consent_group.go | 170 ++++++++++++++++++ internal/resources/resource_scope_group.go | 2 +- 9 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 docs/resources/consent_group.md create mode 100644 examples/resources/cidaas_consent_group/import.sh create mode 100644 examples/resources/cidaas_consent_group/resource.tf create mode 100644 helpers/cidaas/consent_group.go create mode 100644 internal/resources/resource_consent_group.go diff --git a/README.md b/README.md index 469714c..fe60a5b 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ The Terraform provider for Cidaas supports a variety of resources that enables y Explore the following resources to understand their attributes, functionalities and how to use them in your Terraform configurations: - [cidaas_app](#cidaas_app-resource) +- [cidaas_consent_group](#cidaas_consent_group-resource) - [cidaas_custom_provider](#cidaas_custom_provider-resource) - [cidaas_group_type](#cidaas_group_type-resource-previously-cidaas_user_group_category) - [cidaas_hosted_page](#cidaas_hosted_page-resource) @@ -850,6 +851,50 @@ Import is supported using the following syntax: terraform import cidaas_app.sample client_id ``` +# cidaas_consent_group (Resource) + +The Consent Group resource in the provider allows you to define and manage consent groups in Cidaas. + Consent Groups are useful to organize and manage consents by grouping related consent items together. + + Ensure that the below scopes are assigned to the client with the specified `client_id`: +- cidaas:tenant_consent_read +- cidaas:tenant_consent_write +- cidaas:tenant_consent_delete + +## Example Usage + +```terraform +resource "cidaas_consent_group" "sample" { + group_name = "sample_consent_group" + description = "sample description" +} +``` + + +## Schema + +### Required + +- `group_name` (String) The name of the consent group. + +### Optional + +- `description` (String) Description of the consent group. + +### Read-Only + +- `created_at` (String) The timestamp when the consent group was created. +- `id` (String) The unique identifier of the consent group. +- `updated_at` (String) The timestamp when the consent group was last updated. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import cidaas_consent_group.sample id +``` + # cidaas_custom_provider (Resource) This example demonstrates the configuration of a custom provider resource for interacting with Cidaas. diff --git a/docs/resources/consent_group.md b/docs/resources/consent_group.md new file mode 100644 index 0000000..e9cdcce --- /dev/null +++ b/docs/resources/consent_group.md @@ -0,0 +1,54 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cidaas_consent_group Resource - cidaas" +subcategory: "" +description: |- + The Consent Group resource in the provider allows you to define and manage consent groups in Cidaas. + Consent Groups are useful to organize and manage consents by grouping related consent items together. + Ensure that the below scopes are assigned to the client with the specified client_id: + cidaas:tenant_consent_readcidaas:tenant_consent_writecidaas:tenant_consent_delete +--- + +# cidaas_consent_group (Resource) + +The Consent Group resource in the provider allows you to define and manage consent groups in Cidaas. + Consent Groups are useful to organize and manage consents by grouping related consent items together. + + Ensure that the below scopes are assigned to the client with the specified `client_id`: +- cidaas:tenant_consent_read +- cidaas:tenant_consent_write +- cidaas:tenant_consent_delete + +## Example Usage + +```terraform +resource "cidaas_consent_group" "sample" { + group_name = "sample_consent_group" + description = "sample description" +} +``` + + +## Schema + +### Required + +- `group_name` (String) The name of the consent group. + +### Optional + +- `description` (String) Description of the consent group. + +### Read-Only + +- `created_at` (String) The timestamp when the consent group was created. +- `id` (String) The unique identifier of the consent group. +- `updated_at` (String) The timestamp when the consent group was last updated. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import cidaas_consent_group.sample id +``` diff --git a/examples/resources/cidaas_consent_group/import.sh b/examples/resources/cidaas_consent_group/import.sh new file mode 100644 index 0000000..c5c1e32 --- /dev/null +++ b/examples/resources/cidaas_consent_group/import.sh @@ -0,0 +1 @@ +terraform import cidaas_consent_group.sample id \ No newline at end of file diff --git a/examples/resources/cidaas_consent_group/resource.tf b/examples/resources/cidaas_consent_group/resource.tf new file mode 100644 index 0000000..bed1d1f --- /dev/null +++ b/examples/resources/cidaas_consent_group/resource.tf @@ -0,0 +1,4 @@ +resource "cidaas_consent_group" "sample" { + group_name = "sample_consent_group" + description = "sample description" +} diff --git a/helpers/cidaas/cidaas.go b/helpers/cidaas/cidaas.go index 99a3e44..f1d453f 100644 --- a/helpers/cidaas/cidaas.go +++ b/helpers/cidaas/cidaas.go @@ -15,6 +15,7 @@ type Client struct { SocialProvider SocialProviderService Scope ScopeService ScopeGroup ScopeGroupService + ConsentGroup ConsentGroupService GroupType GroupTypeService UserGroup UserGroupService HostedPage HostedPageService diff --git a/helpers/cidaas/consent_group.go b/helpers/cidaas/consent_group.go new file mode 100644 index 0000000..c91c853 --- /dev/null +++ b/helpers/cidaas/consent_group.go @@ -0,0 +1,79 @@ +package cidaas + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/util" +) + +type ConsentGroupConfig struct { + ID string `json:"_id,omitempty"` + GroupName string `json:"group_name,omitempty"` + Description string `json:"description"` // description needs to set to empty string, so omitempty is removed here + CreatedTime string `json:"createdTime,omitempty"` + UpdatedTime string `json:"updatedTime,omitempty"` +} + +type ConsentGroupResponse struct { + Success bool `json:"success,omitempty"` + Status int `json:"status,omitempty"` + Data ConsentGroupConfig `json:"data,omitempty"` +} + +type ConsentGroup struct { + HTTPClient util.HTTPClientInterface +} +type ConsentGroupService interface { + Upsert(cg ConsentGroupConfig) (*ConsentGroupResponse, error) + Get(consentGroupID string) (*ConsentGroupResponse, error) + Delete(consentGroupID string) error +} + +func NewConsentGroup(httpClient util.HTTPClientInterface) ConsentGroupService { + return &ConsentGroup{HTTPClient: httpClient} +} + +func (c *ConsentGroup) Upsert(cg ConsentGroupConfig) (*ConsentGroupResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/groups")) + c.HTTPClient.SetMethod(http.MethodPost) + res, err := c.HTTPClient.MakeRequest(cg) + if err != nil { + return nil, err + } + defer res.Body.Close() + var response ConsentGroupResponse + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + } + return &response, nil +} + +func (c *ConsentGroup) Get(consentGroupID string) (*ConsentGroupResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/groups", consentGroupID)) + c.HTTPClient.SetMethod(http.MethodGet) + res, err := c.HTTPClient.MakeRequest(nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + var response ConsentGroupResponse + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json body, %w, %s", err, consentGroupID) + } + return &response, nil +} + +func (c *ConsentGroup) Delete(consentGroupID string) error { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/groups", consentGroupID)) + c.HTTPClient.SetMethod(http.MethodDelete) + res, err := c.HTTPClient.MakeRequest(nil) + if err != nil { + return err + } + defer res.Body.Close() + return nil +} diff --git a/internal/provider.go b/internal/provider.go index 1b2256d..1668349 100644 --- a/internal/provider.go +++ b/internal/provider.go @@ -61,6 +61,7 @@ func (p *cidaasProvider) Resources(_ context.Context) []func() resource.Resource cidaasResource.NewSocialProvider, cidaasResource.NewScopeResource, cidaasResource.NewScopeGroupResource, + cidaasResource.NewConsentGroupResource, cidaasResource.NewGroupTypeResource, cidaasResource.NewUserGroupResource, cidaasResource.NewHostedPageResource, diff --git a/internal/resources/resource_consent_group.go b/internal/resources/resource_consent_group.go new file mode 100644 index 0000000..751a23c --- /dev/null +++ b/internal/resources/resource_consent_group.go @@ -0,0 +1,170 @@ +package resources + +import ( + "context" + "fmt" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + "github.com/Cidaas/terraform-provider-cidaas/helpers/util" + "github.com/Cidaas/terraform-provider-cidaas/internal/validators" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type ConsentGroupResource struct { + cidaasClient *cidaas.Client +} + +type ConsentGroupConfig struct { + ID types.String `tfsdk:"id"` + GroupName types.String `tfsdk:"group_name"` + Description types.String `tfsdk:"description"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` +} + +func NewConsentGroupResource() resource.Resource { + return &ConsentGroupResource{} +} + +func (r *ConsentGroupResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_consent_group" +} + +func (r *ConsentGroupResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*cidaas.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected cidaas.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.cidaasClient = client +} + +func (r *ConsentGroupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "The Consent Group resource in the provider allows you to define and manage consent groups in Cidaas." + + "\n Consent Groups are useful to organize and manage consents by grouping related consent items together." + + "\n\n Ensure that the below scopes are assigned to the client with the specified `client_id`:" + + "\n- cidaas:tenant_consent_read" + + "\n- cidaas:tenant_consent_write" + + "\n- cidaas:tenant_consent_delete", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The unique identifier of the consent group.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "group_name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "The name of the consent group.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + &validators.UniqueIdentifier{}, + }, + }, + "description": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Description of the consent group.", + }, + "created_at": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The timestamp when the consent group was created.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "updated_at": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The timestamp when the consent group was last updated.", + }, + }, + } +} + +func (r *ConsentGroupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan ScopeGroupConfig + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + consentGroup := cidaas.ConsentGroupConfig{ + GroupName: plan.GroupName.ValueString(), + Description: plan.Description.ValueString(), + } + res, err := r.cidaasClient.ConsentGroup.Upsert(consentGroup) + if err != nil { + resp.Diagnostics.AddError("failed to create consent group", fmt.Sprintf("Error: %s", err.Error())) + return + } + plan.ID = util.StringValueOrNull(&res.Data.ID) + plan.GroupName = util.StringValueOrNull(&res.Data.GroupName) + plan.CreatedAt = util.StringValueOrNull(&res.Data.CreatedTime) + plan.UpdatedAt = util.StringValueOrNull(&res.Data.UpdatedTime) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *ConsentGroupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { //nolint:dupl + var state ConsentGroupConfig + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + res, err := r.cidaasClient.ConsentGroup.Get(state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed to read consent group", fmt.Sprintf("Error: %s ", err.Error())) + return + } + state.ID = util.StringValueOrNull(&res.Data.ID) + state.GroupName = util.StringValueOrNull(&res.Data.GroupName) + state.CreatedAt = util.StringValueOrNull(&res.Data.CreatedTime) + state.UpdatedAt = util.StringValueOrNull(&res.Data.UpdatedTime) + state.Description = util.StringValueOrNull(&res.Data.Description) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *ConsentGroupResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan ConsentGroupConfig + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + consentGroup := cidaas.ConsentGroupConfig{ + GroupName: plan.GroupName.ValueString(), + Description: plan.Description.ValueString(), + } + res, err := r.cidaasClient.ConsentGroup.Upsert(consentGroup) + if err != nil { + resp.Diagnostics.AddError("failed to update consent group", fmt.Sprintf("Error: %s", err.Error())) + return + } + plan.UpdatedAt = util.StringValueOrNull(&res.Data.UpdatedTime) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *ConsentGroupResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state ConsentGroupConfig + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + err := r.cidaasClient.ConsentGroup.Delete(state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed to delete consent group", fmt.Sprintf("Error: %s", err.Error())) + return + } +} + +func (r *ConsentGroupResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/resources/resource_scope_group.go b/internal/resources/resource_scope_group.go index 82336a6..202f660 100644 --- a/internal/resources/resource_scope_group.go +++ b/internal/resources/resource_scope_group.go @@ -117,7 +117,7 @@ func (r *ScopeGroupResource) Create(ctx context.Context, req resource.CreateRequ resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } -func (r *ScopeGroupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +func (r *ScopeGroupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { //nolint:dupl var state ScopeGroupConfig resp.Diagnostics.Append(req.State.Get(ctx, &state)...) res, err := r.cidaasClient.ScopeGroup.Get(state.GroupName.ValueString()) From 73b1cdb2bb55cde0315ac7915699676e792f018b Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Mon, 19 Aug 2024 20:24:07 +0530 Subject: [PATCH 13/36] httpclient update in all resources & error msg fix --- helpers/cidaas/cidaas.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/helpers/cidaas/cidaas.go b/helpers/cidaas/cidaas.go index f1d453f..381c74f 100644 --- a/helpers/cidaas/cidaas.go +++ b/helpers/cidaas/cidaas.go @@ -74,6 +74,9 @@ func NewClient(config ClientConfig) (*Client, error) { RegField: NewRegField(config), TemplateGroup: NewTemplateGroup(config), Template: NewTemplate(config), + SocialProvider: NewSocialProvider(config), + PasswordPolicy: NewPasswordPolicy(config), + ConsentGroup: NewConsentGroup(config), } return client, nil } From 0ee7e96b440893e5fcab6f42badf23a804ce81c4 Mon Sep 17 00:00:00 2001 From: thulasi Date: Fri, 21 Jun 2024 23:05:51 +0200 Subject: [PATCH 14/36] feat(): SocialProvider Resource --- helpers/cidaas/social_provider.go | 56 ++++++++++++++++--------------- helpers/util/locals.go | 12 +++++++ 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/helpers/cidaas/social_provider.go b/helpers/cidaas/social_provider.go index 1eae0fb..50ce977 100644 --- a/helpers/cidaas/social_provider.go +++ b/helpers/cidaas/social_provider.go @@ -1,7 +1,6 @@ package cidaas import ( - "encoding/json" "fmt" "net/http" @@ -50,7 +49,7 @@ type SocialProviderResponse struct { } type SocialProvider struct { - HTTPClient util.HTTPClientInterface + ClientConfig } type SocialProviderService interface { @@ -59,47 +58,50 @@ type SocialProviderService interface { Delete(providerName, providerID string) error } -func NewSocialProvider(httpClient util.HTTPClientInterface) SocialProviderService { - return &SocialProvider{HTTPClient: httpClient} +func NewSocialProvider(clientConfig ClientConfig) SocialProviderService { + return &SocialProvider{clientConfig} } -func (c *SocialProvider) Upsert(cp *SocialProviderModel) (*SocialProviderResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers")) - c.HTTPClient.SetMethod(http.MethodPost) - res, err := c.HTTPClient.MakeRequest(cp) - if err != nil { +func (s *SocialProvider) Upsert(sp *SocialProviderModel) (*SocialProviderResponse, error) { + var response SocialProviderResponse + url := fmt.Sprintf("%s/%s", s.BaseURL, "providers-srv/multi/providers") + httpClient := util.NewHTTPClient(url, http.MethodPost, s.AccessToken) + + res, err := httpClient.MakeRequest(sp) + if err = util.HandleResponseError(res, err); err != nil { return nil, err } defer res.Body.Close() - var response SocialProviderResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + + if err = util.ProcessResponse(res, &response); err != nil { + return nil, err } return &response, nil } -func (c *SocialProvider) Get(providerName, providerID string) (*SocialProviderResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s?provider_name=%s&provider_id=%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers", providerName, providerID)) - c.HTTPClient.SetMethod(http.MethodGet) - res, err := c.HTTPClient.MakeRequest(nil) - if err != nil { +func (s *SocialProvider) Get(providerName, providerID string) (*SocialProviderResponse, error) { + var response SocialProviderResponse + url := fmt.Sprintf("%s/%s?provider_name=%s&provider_id=%s", s.BaseURL, "providers-srv/multi/providers", providerName, providerID) + httpClient := util.NewHTTPClient(url, http.MethodGet, s.AccessToken) + + res, err := httpClient.MakeRequest(nil) + if err = util.HandleResponseError(res, err); err != nil { return nil, err } defer res.Body.Close() - var response SocialProviderResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + + if err = util.ProcessResponse(res, &response); err != nil { + return nil, err } return &response, nil } -func (c *SocialProvider) Delete(providerName, providerID string) error { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s/%s", c.HTTPClient.GetHost(), "providers-srv/multi/providers", providerName, providerID)) - c.HTTPClient.SetMethod(http.MethodDelete) - res, err := c.HTTPClient.MakeRequest(nil) - if err != nil { +func (s *SocialProvider) Delete(providerName, providerID string) error { + url := fmt.Sprintf("%s/%s/%s/%s", s.BaseURL, "providers-srv/multi/providers", providerName, providerID) + httpClient := util.NewHTTPClient(url, http.MethodDelete, s.AccessToken) + + res, err := httpClient.MakeRequest(nil) + if err = util.HandleResponseError(res, err); err != nil { return err } defer res.Body.Close() diff --git a/helpers/util/locals.go b/helpers/util/locals.go index b46a984..c95ec5d 100644 --- a/helpers/util/locals.go +++ b/helpers/util/locals.go @@ -159,3 +159,15 @@ func GetLanguageForLocale(locale string) string { } return "en" } + +var AllowedClaims = []string{ + "name", "given_name", "family_name", "nickname", "preferred_username", "profile", + "picture", "website", "email", "email_verified", "gender", "middle_name", "birthdate", "zoneinfo", "locale", + "phone_number", "phone_number_verified", "formatted", "street_address", "locality", "region", "postal_code", "country", +} + +var AllowedProviders = []string{ + "google", "facebook", "linkedin", "amazon", "foursquare", "github", + "instagram", "yammer", "wordpress", "microsoft", "yahoo", "officeenterprise", "salesforce", "paypal_sandbox", "paypal", + "apple", "twitter", "netid", "netid_qa", +} From 46199089e517e2aa60a122743bf1659539eee50d Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Tue, 20 Aug 2024 10:41:03 +0530 Subject: [PATCH 15/36] httpclient update in consent_group, password_policy and social_provider --- go.mod | 31 +++++++-------- go.sum | 64 +++++++++++++++---------------- helpers/cidaas/consent_group.go | 50 ++++++++++++------------ helpers/cidaas/password_policy.go | 37 +++++++++--------- 4 files changed, 93 insertions(+), 89 deletions(-) diff --git a/go.mod b/go.mod index 6fd7c6a..3a9a999 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.21.0 require ( github.com/hashicorp/terraform-plugin-docs v0.19.4 - github.com/hashicorp/terraform-plugin-framework v1.7.0 - github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 - github.com/hashicorp/terraform-plugin-go v0.22.1 + github.com/hashicorp/terraform-plugin-framework v1.11.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 + github.com/hashicorp/terraform-plugin-go v0.23.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.7.0 ) @@ -25,7 +25,7 @@ require ( github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -34,9 +34,9 @@ require ( github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect - github.com/hashicorp/go-hclog v1.6.2 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/go-plugin v1.6.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hc-install v0.7.0 // indirect @@ -58,7 +58,7 @@ require ( github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/oklog/run v1.0.0 // indirect + github.com/oklog/run v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/posener/complete v1.2.3 // indirect github.com/shopspring/decimal v1.3.1 // indirect @@ -71,17 +71,18 @@ require ( github.com/yuin/goldmark-meta v1.1.0 // indirect github.com/zclconf/go-cty v1.14.4 // indirect go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect - golang.org/x/crypto v0.23.0 // indirect + golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect - google.golang.org/grpc v1.62.1 // indirect - google.golang.org/protobuf v1.34.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 03fdafc..10f76bb 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= @@ -76,13 +76,13 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= -github.com/hashicorp/go-hclog v1.6.2 h1:NOtoftovWkDheyUM/8JW3QMiXyxJK3uHRK7wV04nD2I= -github.com/hashicorp/go-hclog v1.6.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= -github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= +github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= +github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -100,12 +100,12 @@ github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7 github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c= github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= -github.com/hashicorp/terraform-plugin-framework v1.7.0 h1:wOULbVmfONnJo9iq7/q+iBOBJul5vRovaYJIu2cY/Pw= -github.com/hashicorp/terraform-plugin-framework v1.7.0/go.mod h1:jY9Id+3KbZ17OMpulgnWLSfwxNVYSoYBQFTgsx044CI= -github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc= -github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg= -github.com/hashicorp/terraform-plugin-go v0.22.1 h1:iTS7WHNVrn7uhe3cojtvWWn83cm2Z6ryIUDTRO0EV7w= -github.com/hashicorp/terraform-plugin-go v0.22.1/go.mod h1:qrjnqRghvQ6KnDbB12XeZ4FluclYwptntoWCr9QaXTI= +github.com/hashicorp/terraform-plugin-framework v1.11.0 h1:M7+9zBArexHFXDx/pKTxjE6n/2UCXY6b8FIq9ZYhwfE= +github.com/hashicorp/terraform-plugin-framework v1.11.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM= +github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 h1:bxZfGo9DIUoLLtHMElsu+zwqI4IsMZQBRRy4iLzZJ8E= +github.com/hashicorp/terraform-plugin-framework-validators v0.13.0/go.mod h1:wGeI02gEhj9nPANU62F2jCaHjXulejm/X+af4PdZaNo= +github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= +github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 h1:qHprzXy/As0rxedphECBEQAh3R4yp6pKksKHcqZx5G8= @@ -161,8 +161,8 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -210,8 +210,8 @@ go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -222,13 +222,13 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -242,8 +242,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -253,26 +253,26 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/helpers/cidaas/consent_group.go b/helpers/cidaas/consent_group.go index c91c853..6e17a6a 100644 --- a/helpers/cidaas/consent_group.go +++ b/helpers/cidaas/consent_group.go @@ -1,7 +1,6 @@ package cidaas import ( - "encoding/json" "fmt" "net/http" @@ -23,7 +22,7 @@ type ConsentGroupResponse struct { } type ConsentGroup struct { - HTTPClient util.HTTPClientInterface + ClientConfig } type ConsentGroupService interface { Upsert(cg ConsentGroupConfig) (*ConsentGroupResponse, error) @@ -31,47 +30,50 @@ type ConsentGroupService interface { Delete(consentGroupID string) error } -func NewConsentGroup(httpClient util.HTTPClientInterface) ConsentGroupService { - return &ConsentGroup{HTTPClient: httpClient} +func NewConsentGroup(clientConfig ClientConfig) ConsentGroupService { + return &ConsentGroup{clientConfig} } func (c *ConsentGroup) Upsert(cg ConsentGroupConfig) (*ConsentGroupResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/groups")) - c.HTTPClient.SetMethod(http.MethodPost) - res, err := c.HTTPClient.MakeRequest(cg) - if err != nil { + var response ConsentGroupResponse + url := fmt.Sprintf("%s/%s", c.BaseURL, "consent-management-srv/v2/groups") + httpClient := util.NewHTTPClient(url, http.MethodPost, c.AccessToken) + + res, err := httpClient.MakeRequest(cg) + if err = util.HandleResponseError(res, err); err != nil { return nil, err } defer res.Body.Close() - var response ConsentGroupResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + + if err = util.ProcessResponse(res, &response); err != nil { + return nil, err } return &response, nil } func (c *ConsentGroup) Get(consentGroupID string) (*ConsentGroupResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/groups", consentGroupID)) - c.HTTPClient.SetMethod(http.MethodGet) - res, err := c.HTTPClient.MakeRequest(nil) - if err != nil { + var response ConsentGroupResponse + url := fmt.Sprintf("%s/%s/%s", c.BaseURL, "consent-management-srv/v2/groups", consentGroupID) + httpClient := util.NewHTTPClient(url, http.MethodGet, c.AccessToken) + + res, err := httpClient.MakeRequest(nil) + if err = util.HandleResponseError(res, err); err != nil { return nil, err } defer res.Body.Close() - var response ConsentGroupResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json body, %w, %s", err, consentGroupID) + + if err = util.ProcessResponse(res, &response); err != nil { + return nil, err } return &response, nil } func (c *ConsentGroup) Delete(consentGroupID string) error { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/groups", consentGroupID)) - c.HTTPClient.SetMethod(http.MethodDelete) - res, err := c.HTTPClient.MakeRequest(nil) - if err != nil { + url := fmt.Sprintf("%s/%s/%s", c.BaseURL, "consent-management-srv/v2/groups", consentGroupID) + httpClient := util.NewHTTPClient(url, http.MethodDelete, c.AccessToken) + + res, err := httpClient.MakeRequest(nil) + if err = util.HandleResponseError(res, err); err != nil { return err } defer res.Body.Close() diff --git a/helpers/cidaas/password_policy.go b/helpers/cidaas/password_policy.go index 7180dc0..0a2486a 100644 --- a/helpers/cidaas/password_policy.go +++ b/helpers/cidaas/password_policy.go @@ -1,7 +1,6 @@ package cidaas import ( - "encoding/json" "fmt" "net/http" @@ -26,38 +25,40 @@ type PasswordPolicyResponse struct { } type PasswordPolicy struct { - HTTPClient util.HTTPClientInterface + ClientConfig } type PasswordPolicyService interface { Get() (*PasswordPolicyResponse, error) Update(cp PasswordPolicyModel) error } -func NewPasswordPolicy(httpClient util.HTTPClientInterface) PasswordPolicyService { - return &PasswordPolicy{HTTPClient: httpClient} +func NewPasswordPolicy(clientConfig ClientConfig) PasswordPolicyService { + return &PasswordPolicy{clientConfig} } -func (c *PasswordPolicy) Get() (*PasswordPolicyResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "password-policy-srv/policy")) - c.HTTPClient.SetMethod(http.MethodGet) - res, err := c.HTTPClient.MakeRequest(nil) - if err != nil { +func (p *PasswordPolicy) Get() (*PasswordPolicyResponse, error) { + var response PasswordPolicyResponse + url := fmt.Sprintf("%s/%s", p.BaseURL, "password-policy-srv/policy") + httpClient := util.NewHTTPClient(url, http.MethodGet, p.AccessToken) + + res, err := httpClient.MakeRequest(nil) + if err = util.HandleResponseError(res, err); err != nil { return nil, err } defer res.Body.Close() - var response PasswordPolicyResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + + if err = util.ProcessResponse(res, &response); err != nil { + return nil, err } return &response, nil } -func (c *PasswordPolicy) Update(payload PasswordPolicyModel) error { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "password-policy-srv/policy")) - c.HTTPClient.SetMethod(http.MethodPut) - res, err := c.HTTPClient.MakeRequest(payload) - if err != nil { +func (p *PasswordPolicy) Update(payload PasswordPolicyModel) error { + url := fmt.Sprintf("%s/%s", p.BaseURL, "password-policy-srv/policy") + httpClient := util.NewHTTPClient(url, http.MethodPut, p.AccessToken) + + res, err := httpClient.MakeRequest(payload) + if err = util.HandleResponseError(res, err); err != nil { return err } defer res.Body.Close() From 3b03da9b11907919970cbb16380a253962422ce1 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Wed, 7 Aug 2024 16:27:03 +0530 Subject: [PATCH 16/36] resource consent --- helpers/cidaas/cidaas.go | 2 + helpers/cidaas/consent.go | 92 ++++++++++++ internal/provider.go | 1 + internal/resources/resource_consent.go | 200 +++++++++++++++++++++++++ 4 files changed, 295 insertions(+) create mode 100644 helpers/cidaas/consent.go create mode 100644 internal/resources/resource_consent.go diff --git a/helpers/cidaas/cidaas.go b/helpers/cidaas/cidaas.go index 381c74f..87dcd1c 100644 --- a/helpers/cidaas/cidaas.go +++ b/helpers/cidaas/cidaas.go @@ -25,6 +25,7 @@ type Client struct { TemplateGroup TemplateGroupService Template TemplateService PasswordPolicy PasswordPolicyService + Consent ConsentService } type ClientConfig struct { @@ -77,6 +78,7 @@ func NewClient(config ClientConfig) (*Client, error) { SocialProvider: NewSocialProvider(config), PasswordPolicy: NewPasswordPolicy(config), ConsentGroup: NewConsentGroup(config), + Consent: NewConsent(config), } return client, nil } diff --git a/helpers/cidaas/consent.go b/helpers/cidaas/consent.go new file mode 100644 index 0000000..18a4678 --- /dev/null +++ b/helpers/cidaas/consent.go @@ -0,0 +1,92 @@ +package cidaas + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/util" +) + +type ConsentModel struct { + ID string `json:"_id,omitempty"` + ConsentGroupID string `json:"consent_group_id,omitempty"` + ConsentName string `json:"consent_name,omitempty"` + Enabled bool `json:"enabled"` + CreatedTime string `json:"createdTime,omitempty"` + UpdatedTime string `json:"updatedTime,omitempty"` +} + +type ConsentResponse struct { + Success bool `json:"success,omitempty"` + Status int `json:"status,omitempty"` + Data ConsentModel `json:"data,omitempty"` +} + +type ConsentInstanceResponse struct { + Success bool `json:"success,omitempty"` + Status int `json:"status,omitempty"` + Data []ConsentModel `json:"data,omitempty"` +} + +type ConsentClient struct { + HTTPClient util.HTTPClientInterface +} +type ConsentService interface { + Upsert(consent ConsentModel) (*ConsentResponse, error) + GetConsentInstances(consentGroupID string) (*ConsentInstanceResponse, error) + Delete(consentID string) error +} + +func NewConsent(httpClient util.HTTPClientInterface) ConsentService { + return &ConsentClient{HTTPClient: httpClient} +} + +func (c *ConsentClient) Upsert(consentConfig ConsentModel) (*ConsentResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/instance")) + c.HTTPClient.SetMethod(http.MethodPost) + res, err := c.HTTPClient.MakeRequest(consentConfig) + if err != nil { + return nil, err + } + defer res.Body.Close() + var response ConsentResponse + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + } + return &response, nil +} + +func (c *ConsentClient) GetConsentInstances(consentGroupID string) (*ConsentInstanceResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/instance", consentGroupID)) + c.HTTPClient.SetMethod(http.MethodGet) + res, err := c.HTTPClient.MakeRequest(nil) + if res.StatusCode == http.StatusNoContent { + return &ConsentInstanceResponse{ + Success: false, + Status: http.StatusNoContent, + }, nil + } + if err != nil { + return nil, err + } + defer res.Body.Close() + var response ConsentInstanceResponse + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json body, %w, %s", err, consentGroupID) + } + return &response, nil +} + +func (c *ConsentClient) Delete(consentID string) error { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/instance", consentID)) + c.HTTPClient.SetMethod(http.MethodDelete) + res, err := c.HTTPClient.MakeRequest(nil) + if err != nil { + return err + } + defer res.Body.Close() + return nil +} diff --git a/internal/provider.go b/internal/provider.go index 1668349..c1979a0 100644 --- a/internal/provider.go +++ b/internal/provider.go @@ -71,6 +71,7 @@ func (p *cidaasProvider) Resources(_ context.Context) []func() resource.Resource cidaasResource.NewTemplateGroupResource, cidaasResource.NewTemplateResource, cidaasResource.NewPasswordPolicy, + cidaasResource.NewConsentResource, } } diff --git a/internal/resources/resource_consent.go b/internal/resources/resource_consent.go new file mode 100644 index 0000000..52a4be2 --- /dev/null +++ b/internal/resources/resource_consent.go @@ -0,0 +1,200 @@ +package resources + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + "github.com/Cidaas/terraform-provider-cidaas/helpers/util" + "github.com/Cidaas/terraform-provider-cidaas/internal/validators" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type ConsentResource struct { + cidaasClient *cidaas.Client +} + +type ConsentConfig struct { + ID types.String `tfsdk:"id"` + ConsentGroupID types.String `tfsdk:"consent_group_id"` + Name types.String `tfsdk:"name"` + Enabled types.Bool `tfsdk:"enabled"` + CreatedAt types.String `tfsdk:"created_at"` + UpdatedAt types.String `tfsdk:"updated_at"` +} + +func NewConsentResource() resource.Resource { + return &ConsentResource{} +} + +func (r *ConsentResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_consent" +} + +func (r *ConsentResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*cidaas.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected cidaas.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.cidaasClient = client +} + +func (r *ConsentResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + &validators.UniqueIdentifier{}, + }, + }, + "consent_group_id": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + &validators.UniqueIdentifier{}, + }, + }, + "enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "created_at": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "updated_at": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (r *ConsentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan ConsentConfig + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + consent := cidaas.ConsentModel{ + ConsentName: plan.Name.ValueString(), + ConsentGroupID: plan.ConsentGroupID.ValueString(), + Enabled: plan.Enabled.ValueBool(), + } + res, err := r.cidaasClient.Consent.Upsert(consent) + if err != nil { + resp.Diagnostics.AddError("failed to create consent", fmt.Sprintf("Error: %s", err.Error())) + return + } + plan.ID = util.StringValueOrNull(&res.Data.ID) + plan.CreatedAt = util.StringValueOrNull(&res.Data.CreatedTime) + plan.UpdatedAt = util.StringValueOrNull(&res.Data.UpdatedTime) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *ConsentResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state ConsentConfig + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + res, err := r.cidaasClient.Consent.GetConsentInstances(state.ConsentGroupID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Failed to read consent", fmt.Sprintf("Error: %s ", err.Error())) + return + } + if !res.Success && res.Status == http.StatusNoContent { + resp.Diagnostics.AddError("Invalid consent_group_id", fmt.Sprintf("No consent group found for the provided consent_group_id %+v", state.ConsentGroupID.String())) + return + } + isAvailable := false + if len(res.Data) > 0 { + for _, instance := range res.Data { + if instance.ConsentName == state.Name.ValueString() { + isAvailable = true + state.ID = util.StringValueOrNull(&instance.ID) + state.Name = util.StringValueOrNull(&instance.ConsentName) + state.Enabled = types.BoolValue(instance.Enabled) + state.CreatedAt = util.StringValueOrNull(&instance.CreatedTime) + state.UpdatedAt = util.StringValueOrNull(&instance.UpdatedTime) + } + } + } + + if !isAvailable { + resp.Diagnostics.AddError("Consent not found", fmt.Sprintf("consent %s not found for the provided consent_group_id %s", state.Name.String(), state.ConsentGroupID.String())) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *ConsentResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state ConsentConfig + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + consent := cidaas.ConsentModel{ + ID: state.ID.ValueString(), + ConsentName: plan.Name.ValueString(), + ConsentGroupID: plan.ConsentGroupID.ValueString(), + Enabled: plan.Enabled.ValueBool(), + } + res, err := r.cidaasClient.Consent.Upsert(consent) + if err != nil { + resp.Diagnostics.AddError("failed to update consent", fmt.Sprintf("Error: %s", err.Error())) + return + } + plan.UpdatedAt = util.StringValueOrNull(&res.Data.UpdatedTime) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *ConsentResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state ConsentConfig + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + err := r.cidaasClient.Consent.Delete(state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed to delete consent", fmt.Sprintf("Error: %s", err.Error())) + return + } +} + +func (r *ConsentResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + id := req.ID + parts := strings.Split(id, ":") + if len(parts) != 2 { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: 'consent_group_id:name', got: %s", id), + ) + return + } + resp.State.SetAttribute(ctx, path.Root("consent_group_id"), parts[0]) + resp.State.SetAttribute(ctx, path.Root("name"), parts[1]) +} From 6dda67cf43f957d29a2ecef561faa0a6880942c7 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Tue, 13 Aug 2024 14:59:17 +0530 Subject: [PATCH 17/36] consent version and local implementation fix test case gosec lint fix gosec lint fix part2 --- README.md | 147 +++++++ docs/resources/consent.md | 55 +++ docs/resources/consent_version.md | 107 +++++ examples/resources/cidaas_consent/import.sh | 4 + examples/resources/cidaas_consent/resource.tf | 5 + .../cidaas_consent_version/import.sh | 7 + .../cidaas_consent_version/resource.tf | 37 ++ helpers/cidaas/cidaas.go | 2 + helpers/cidaas/consent_version.go | 139 ++++++ internal/provider.go | 1 + internal/resources/resource_consent.go | 39 +- .../resources/resource_consent_version.go | 410 ++++++++++++++++++ internal/resources/resource_role_test.go | 2 +- internal/validators/unique_identifier.go | 54 ++- templates/resources/consent.md.tmpl | 26 ++ templates/resources/consent_version.md.tmpl | 28 ++ 16 files changed, 1046 insertions(+), 17 deletions(-) create mode 100644 docs/resources/consent.md create mode 100644 docs/resources/consent_version.md create mode 100644 examples/resources/cidaas_consent/import.sh create mode 100644 examples/resources/cidaas_consent/resource.tf create mode 100644 examples/resources/cidaas_consent_version/import.sh create mode 100644 examples/resources/cidaas_consent_version/resource.tf create mode 100644 helpers/cidaas/consent_version.go create mode 100644 internal/resources/resource_consent_version.go create mode 100644 templates/resources/consent.md.tmpl create mode 100644 templates/resources/consent_version.md.tmpl diff --git a/README.md b/README.md index fe60a5b..c69d256 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,9 @@ The Terraform provider for Cidaas supports a variety of resources that enables y Explore the following resources to understand their attributes, functionalities and how to use them in your Terraform configurations: - [cidaas_app](#cidaas_app-resource) +- [cidaas_consent](#cidaas_consent-resource) - [cidaas_consent_group](#cidaas_consent_group-resource) +- [cidaas_consent_version](#cidaas_consent_version-resource) - [cidaas_custom_provider](#cidaas_custom_provider-resource) - [cidaas_group_type](#cidaas_group_type-resource-previously-cidaas_user_group_category) - [cidaas_hosted_page](#cidaas_hosted_page-resource) @@ -851,6 +853,53 @@ Import is supported using the following syntax: terraform import cidaas_app.sample client_id ``` +# cidaas_consent (Resource) + +The Consent resource in the provider allows you to manage different consents within a specific consent group in Cidaas. + + Ensure that the below scopes are assigned to the client with the specified `client_id`: +- cidaas:tenant_consent_read +- cidaas:tenant_consent_write +- cidaas:tenant_consent_delete + +## Example Usage + +```terraform +resource "cidaas_consent" "sample" { + consent_group_id = cidaas_consent_group.sample.id + name = "sample_consent" + enabled = true # By default enabled is set to 'true' +} +``` + + +## Schema + +### Required + +- `consent_group_id` (String) The `consent_group_id` to which the consent belongs. +- `name` (String) The name of the consent. + +### Optional + +- `enabled` (Boolean) The flag to enable or disable a speicific consent. By default, the value is set to `true` + +### Read-Only + +- `created_at` (String) The timestamp when the consent version was created. +- `id` (String) The unique identifier of the consent resource. +- `updated_at` (String) The timestamp when the consent version was last updated. + +## Import + +In the import statement, the identifier is the combination of `consent_group_id` and `consent_name` joined by the special character ":". + +Below is an exmaple of import command to import a consent: + +```shell +terraform import cidaas_consent.sample a0508317-cec9-4f3e-afa4:sample_consent +``` + # cidaas_consent_group (Resource) The Consent Group resource in the provider allows you to define and manage consent groups in Cidaas. @@ -895,6 +944,104 @@ Import is supported using the following syntax: terraform import cidaas_consent_group.sample id ``` +# cidaas_consent_version (Resource) + +The Consent Version resource in the provider allows you to manage different versions of a specific consent in Cidaas. + This resource also supports managing consent versions across multiple locales enabling different configurations such as URLs and content for each locale. + + Ensure that the below scopes are assigned to the client with the specified `client_id`: +- cidaas:tenant_consent_read +- cidaas:tenant_consent_write +- cidaas:tenant_consent_delete + +## Example Usage + +```terraform +# cidaas_consent_version sample for consent_type "SCOPES" +resource "cidaas_consent_version" "v1" { + version = 1 + consent_id = cidaas_consent.sample.id + consent_type = "SCOPES" + scopes = ["developer"] + required_fields = ["name"] + consent_locales = [ + { + content = "consent version in German" + locale = "de" + }, + { + content = "consent version in English" + locale = "en" + } + ] +} + +# cidaas_consent_version sample for consent_type "URL" +resource "cidaas_consent_version" "v2" { + version = 2 + consent_id = cidaas_consent.sample.id + consent_type = "URL" + consent_locales = [ + { + content = "consent version in German" + locale = "de" + url = "https://cidaas.de/de" + }, + { + content = "consent version in English" + locale = "en" + url = "https://cidaas.de/en" + } + ] +} +``` + + +## Schema + +### Required + +- `consent_id` (String) The `consent_id` for which the consent version is created. It can not be updated for a specific consent version. +- `consent_locales` (Attributes Set) (see [below for nested schema](#nestedatt--consent_locales)) +- `version` (Number) The version number of the consent. It can not be updated for a specific consent version. + +### Optional + +- `consent_type` (String) Specifies the type of consent. The allowed values are `SCOPES` or `URL`. It can not be updated for a specific consent version. +- `required_fields` (Set of String) A set of fields that are required for the consent. It can not be updated for a specific consent version. +Note that the attribute `required_fields` is required only if the `consent_type` is set to **SCOPES**. +- `scopes` (Set of String) A set of scopes related to the consent. It can not be updated for a specific consent version. +Note that the attribute `scopes` is required only if the `consent_type` is set to **SCOPES**. + +### Read-Only + +- `id` (String) The unique identifier of the consent version. + + +### Nested Schema for `consent_locales` + +Required: + +- `locale` (String) The locale for which the consent version is created. e.g. `en-us`, `de`. + +Optional: + +- `content` (String) The content of the consent version associated with a specific locale. +- `url` (String) The url to the consent page of the created consent version. +Note that the attribute `url` is required only if the `consent_type` is set to **URL**. + +## Import + +In the import statement, the identifier is the combination of `consent_id`, `consent_version_id` and `locale` joined by the special character ":". +To import a consent version for multiple locales, you need to append the locales separated by ":". +For example, the identifier "3f453233-92d4-475b-b10e:813fbd47-6c50-4fc4-881a:en-us:de:en" imports the consent version for the locales `en-us`, `de` and `en`. + +Below is an exmaple of import command to import a consent version: + +```shell +terraform import cidaas_consent_version.v1 3f453233-92d4-475b-b10e:813fbd47-6c50-4fc4-881a:en-us +``` + # cidaas_custom_provider (Resource) This example demonstrates the configuration of a custom provider resource for interacting with Cidaas. diff --git a/docs/resources/consent.md b/docs/resources/consent.md new file mode 100644 index 0000000..804a1c5 --- /dev/null +++ b/docs/resources/consent.md @@ -0,0 +1,55 @@ +--- +page_title: "cidaas_consent Resource - cidaas" +subcategory: "" +description: |- + The Consent resource in the provider allows you to manage different consents within a specific consent group in Cidaas. + Ensure that the below scopes are assigned to the client with the specified client_id: + cidaas:tenant_consent_readcidaas:tenant_consent_writecidaas:tenant_consent_delete +--- + +# cidaas_consent (Resource) + +The Consent resource in the provider allows you to manage different consents within a specific consent group in Cidaas. + + Ensure that the below scopes are assigned to the client with the specified `client_id`: +- cidaas:tenant_consent_read +- cidaas:tenant_consent_write +- cidaas:tenant_consent_delete + +## Example Usage + +```terraform +resource "cidaas_consent" "sample" { + consent_group_id = cidaas_consent_group.sample.id + name = "sample_consent" + enabled = true # By default enabled is set to 'true' +} +``` + + +## Schema + +### Required + +- `consent_group_id` (String) The `consent_group_id` to which the consent belongs. +- `name` (String) The name of the consent. + +### Optional + +- `enabled` (Boolean) The flag to enable or disable a speicific consent. By default, the value is set to `true` + +### Read-Only + +- `created_at` (String) The timestamp when the consent version was created. +- `id` (String) The unique identifier of the consent resource. +- `updated_at` (String) The timestamp when the consent version was last updated. + +## Import + +In the import statement, the identifier is the combination of `consent_group_id` and `consent_name` joined by the special character ":". + +Below is an exmaple of import command to import a consent: + +```shell +terraform import cidaas_consent.sample a0508317-cec9-4f3e-afa4:sample_consent +``` \ No newline at end of file diff --git a/docs/resources/consent_version.md b/docs/resources/consent_version.md new file mode 100644 index 0000000..32452a8 --- /dev/null +++ b/docs/resources/consent_version.md @@ -0,0 +1,107 @@ +--- +page_title: "cidaas_consent_version Resource - cidaas" +subcategory: "" +description: |- + The Consent Version resource in the provider allows you to manage different versions of a specific consent in Cidaas. + This resource also supports managing consent versions across multiple locales enabling different configurations such as URLs and content for each locale. + Ensure that the below scopes are assigned to the client with the specified client_id: + cidaas:tenant_consent_readcidaas:tenant_consent_writecidaas:tenant_consent_delete +--- + +# cidaas_consent_version (Resource) + +The Consent Version resource in the provider allows you to manage different versions of a specific consent in Cidaas. + This resource also supports managing consent versions across multiple locales enabling different configurations such as URLs and content for each locale. + + Ensure that the below scopes are assigned to the client with the specified `client_id`: +- cidaas:tenant_consent_read +- cidaas:tenant_consent_write +- cidaas:tenant_consent_delete + +## Example Usage + +```terraform +# cidaas_consent_version sample for consent_type "SCOPES" +resource "cidaas_consent_version" "v1" { + version = 1 + consent_id = cidaas_consent.sample.id + consent_type = "SCOPES" + scopes = ["developer"] + required_fields = ["name"] + consent_locales = [ + { + content = "consent version in German" + locale = "de" + }, + { + content = "consent version in English" + locale = "en" + } + ] +} + +# cidaas_consent_version sample for consent_type "URL" +resource "cidaas_consent_version" "v2" { + version = 2 + consent_id = cidaas_consent.sample.id + consent_type = "URL" + consent_locales = [ + { + content = "consent version in German" + locale = "de" + url = "https://cidaas.de/de" + }, + { + content = "consent version in English" + locale = "en" + url = "https://cidaas.de/en" + } + ] +} +``` + + +## Schema + +### Required + +- `consent_id` (String) The `consent_id` for which the consent version is created. It can not be updated for a specific consent version. +- `consent_locales` (Attributes Set) (see [below for nested schema](#nestedatt--consent_locales)) +- `version` (Number) The version number of the consent. It can not be updated for a specific consent version. + +### Optional + +- `consent_type` (String) Specifies the type of consent. The allowed values are `SCOPES` or `URL`. It can not be updated for a specific consent version. +- `required_fields` (Set of String) A set of fields that are required for the consent. It can not be updated for a specific consent version. +Note that the attribute `required_fields` is required only if the `consent_type` is set to **SCOPES**. +- `scopes` (Set of String) A set of scopes related to the consent. It can not be updated for a specific consent version. +Note that the attribute `scopes` is required only if the `consent_type` is set to **SCOPES**. + +### Read-Only + +- `id` (String) The unique identifier of the consent version. + + +### Nested Schema for `consent_locales` + +Required: + +- `locale` (String) The locale for which the consent version is created. e.g. `en-us`, `de`. + +Optional: + +- `content` (String) The content of the consent version associated with a specific locale. +- `url` (String) The url to the consent page of the created consent version. +Note that the attribute `url` is required only if the `consent_type` is set to **URL**. + +## Import + +In the import statement, the identifier is the combination of `consent_id`, `consent_version_id` and `locale` joined by the special character ":". +To import a consent version for multiple locales, you need to append the locales separated by ":". +For example, the identifier "3f453233-92d4-475b-b10e:813fbd47-6c50-4fc4-881a:en-us:de:en" imports the consent version for the locales `en-us`, `de` and `en`. + +Below is an exmaple of import command to import a consent version: + +```shell +terraform import cidaas_consent_version.v1 3f453233-92d4-475b-b10e:813fbd47-6c50-4fc4-881a:en-us +``` \ No newline at end of file diff --git a/examples/resources/cidaas_consent/import.sh b/examples/resources/cidaas_consent/import.sh new file mode 100644 index 0000000..899fbe0 --- /dev/null +++ b/examples/resources/cidaas_consent/import.sh @@ -0,0 +1,4 @@ +# In the import statement, the identifier is the combination of consent_group_id +# and consent_name joined by the special character ":" + +terraform import cidaas_consent.sample a0508317-cec9-4f3e-afa4:sample_consent \ No newline at end of file diff --git a/examples/resources/cidaas_consent/resource.tf b/examples/resources/cidaas_consent/resource.tf new file mode 100644 index 0000000..f065323 --- /dev/null +++ b/examples/resources/cidaas_consent/resource.tf @@ -0,0 +1,5 @@ +resource "cidaas_consent" "sample" { + consent_group_id = cidaas_consent_group.sample.id + name = "sample_consent" + enabled = true # By default enabled is set to 'true' +} diff --git a/examples/resources/cidaas_consent_version/import.sh b/examples/resources/cidaas_consent_version/import.sh new file mode 100644 index 0000000..676cc10 --- /dev/null +++ b/examples/resources/cidaas_consent_version/import.sh @@ -0,0 +1,7 @@ +# In the import statement, the identifier is the combination of consent_id, +# consent_version_id and locale joined by the special character ":" +# To import a consent version for multiple locales, you need to append the locales separated by ":". +# For example, "3f453233-92d4-475b-b10e:813fbd47-6c50-4fc4-881a:en-us:de:en" imports +# the consent version for the locales en-us, de and en. + +terraform import cidaas_consent_version.v1 3f453233-92d4-475b-b10e:813fbd47-6c50-4fc4-881a:en-us \ No newline at end of file diff --git a/examples/resources/cidaas_consent_version/resource.tf b/examples/resources/cidaas_consent_version/resource.tf new file mode 100644 index 0000000..ad4195f --- /dev/null +++ b/examples/resources/cidaas_consent_version/resource.tf @@ -0,0 +1,37 @@ +# cidaas_consent_version sample for consent_type "SCOPES" +resource "cidaas_consent_version" "v1" { + version = 1 + consent_id = cidaas_consent.sample.id + consent_type = "SCOPES" + scopes = ["developer"] + required_fields = ["name"] + consent_locales = [ + { + content = "consent version in German" + locale = "de" + }, + { + content = "consent version in English" + locale = "en" + } + ] +} + +# cidaas_consent_version sample for consent_type "URL" +resource "cidaas_consent_version" "v2" { + version = 2 + consent_id = cidaas_consent.sample.id + consent_type = "URL" + consent_locales = [ + { + content = "consent version in German" + locale = "de" + url = "https://cidaas.de/de" + }, + { + content = "consent version in English" + locale = "en" + url = "https://cidaas.de/en" + } + ] +} diff --git a/helpers/cidaas/cidaas.go b/helpers/cidaas/cidaas.go index 87dcd1c..1275d37 100644 --- a/helpers/cidaas/cidaas.go +++ b/helpers/cidaas/cidaas.go @@ -26,6 +26,7 @@ type Client struct { Template TemplateService PasswordPolicy PasswordPolicyService Consent ConsentService + ConsentVersion ConsentVersionService } type ClientConfig struct { @@ -79,6 +80,7 @@ func NewClient(config ClientConfig) (*Client, error) { PasswordPolicy: NewPasswordPolicy(config), ConsentGroup: NewConsentGroup(config), Consent: NewConsent(config), + ConsentVersion: NewConsentVersion(config), } return client, nil } diff --git a/helpers/cidaas/consent_version.go b/helpers/cidaas/consent_version.go new file mode 100644 index 0000000..d133b99 --- /dev/null +++ b/helpers/cidaas/consent_version.go @@ -0,0 +1,139 @@ +package cidaas + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/util" +) + +type ConsentVersionResponse struct { + Success bool `json:"success,omitempty"` + Status int `json:"status,omitempty"` + Data ConsentVersionModel `json:"data,omitempty"` +} + +type ConsentVersionReadResponse struct { + Success bool `json:"success,omitempty"` + Status int `json:"status,omitempty"` + Data []ConsentVersionModel `json:"data,omitempty"` +} + +type ConsentVersionModel struct { + ID string `json:"_id,omitempty"` + Version float64 `json:"version,omitempty"` + ConsentID string `json:"consent_id,omitempty"` + ConsentType string `json:"consentType,omitempty"` + Scopes []string `json:"scopes,omitempty"` + RequiredFields []string `json:"required_fields,omitempty"` + ConsentLocale ConsentLocale `json:"consent_locale,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` +} + +type ConsentLocalResponse struct { + Success bool `json:"success,omitempty"` + Status int `json:"status,omitempty"` + Data ConsentLocalModel `json:"data,omitempty"` +} + +type ConsentLocalModel struct { + ConsentVersionID string `json:"consent_version_id,omitempty"` + ConsentID string `json:"consent_id,omitempty"` + Content string `json:"content,omitempty"` + Locale string `json:"locale,omitempty"` + URL string `json:"url,omitempty"` + Scopes []string `json:"scopes,omitempty"` + RequiredFields []string `json:"required_fields,omitempty"` +} +type ConsentLocale struct { + Locale string `json:"locale,omitempty"` + Content string `json:"content,omitempty"` + URL string `json:"url,omitempty"` +} + +type ConsentVersion struct { + HTTPClient util.HTTPClientInterface +} + +type ConsentVersionService interface { + Upsert(consent ConsentVersionModel) (*ConsentVersionResponse, error) + Get(consentID string) (*ConsentVersionReadResponse, error) + UpsertLocal(consentLocal ConsentLocalModel) (*ConsentLocalResponse, error) + GetLocal(consentVersionID string, locale string) (*ConsentLocalResponse, error) +} + +func NewConsentVersion(httpClient util.HTTPClientInterface) ConsentVersionService { + return &ConsentVersion{HTTPClient: httpClient} +} + +func (c *ConsentVersion) Upsert(consentVersionConfig ConsentVersionModel) (*ConsentVersionResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/versions")) + c.HTTPClient.SetMethod(http.MethodPost) + res, err := c.HTTPClient.MakeRequest(consentVersionConfig) + if err != nil { + return nil, err + } + defer res.Body.Close() + var response ConsentVersionResponse + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + } + return &response, nil +} + +func (c *ConsentVersion) Get(consentID string) (*ConsentVersionReadResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/versions/list", consentID)) + c.HTTPClient.SetMethod(http.MethodGet) + res, err := c.HTTPClient.MakeRequest(nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + var response ConsentVersionReadResponse + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json body, %w, %s", err, consentID) + } + return &response, nil +} + +func (c *ConsentVersion) UpsertLocal(consentLocal ConsentLocalModel) (*ConsentLocalResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/locale")) + c.HTTPClient.SetMethod(http.MethodPost) + res, err := c.HTTPClient.MakeRequest(consentLocal) + if err != nil { + return nil, err + } + defer res.Body.Close() + var response ConsentLocalResponse + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + } + return &response, nil +} + +func (c *ConsentVersion) GetLocal(consentVersionID string, locale string) (*ConsentLocalResponse, error) { + c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s?locale=%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/locale", consentVersionID, locale)) + c.HTTPClient.SetMethod(http.MethodGet) + res, err := c.HTTPClient.MakeRequest(nil) + if res.StatusCode == http.StatusNoContent { + return &ConsentLocalResponse{ + Success: false, + Status: http.StatusNoContent, + }, nil + } + if err != nil { + return nil, err + } + defer res.Body.Close() + var response ConsentLocalResponse + err = json.NewDecoder(res.Body).Decode(&response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal json body, %w, %s", err, consentVersionID) + } + return &response, nil +} diff --git a/internal/provider.go b/internal/provider.go index c1979a0..6e08acd 100644 --- a/internal/provider.go +++ b/internal/provider.go @@ -72,6 +72,7 @@ func (p *cidaasProvider) Resources(_ context.Context) []func() resource.Resource cidaasResource.NewTemplateResource, cidaasResource.NewPasswordPolicy, cidaasResource.NewConsentResource, + cidaasResource.NewConsentVersionResource, } } diff --git a/internal/resources/resource_consent.go b/internal/resources/resource_consent.go index 52a4be2..f3cb32c 100644 --- a/internal/resources/resource_consent.go +++ b/internal/resources/resource_consent.go @@ -58,15 +58,22 @@ func (r *ConsentResource) Configure(_ context.Context, req resource.ConfigureReq func (r *ConsentResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ + MarkdownDescription: "The Consent resource in the provider allows you to manage different consents within a specific consent group in Cidaas." + + "\n\n Ensure that the below scopes are assigned to the client with the specified `client_id`:" + + "\n- cidaas:tenant_consent_read" + + "\n- cidaas:tenant_consent_write" + + "\n- cidaas:tenant_consent_delete", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - Computed: true, + Computed: true, + MarkdownDescription: "The unique identifier of the consent resource.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "name": schema.StringAttribute{ - Required: true, + Required: true, + MarkdownDescription: "The name of the consent.", Validators: []validator.String{ stringvalidator.LengthAtLeast(1), }, @@ -75,7 +82,8 @@ func (r *ConsentResource) Schema(_ context.Context, _ resource.SchemaRequest, re }, }, "consent_group_id": schema.StringAttribute{ - Required: true, + Required: true, + MarkdownDescription: "The `consent_group_id` to which the consent belongs.", Validators: []validator.String{ stringvalidator.LengthAtLeast(1), }, @@ -84,18 +92,21 @@ func (r *ConsentResource) Schema(_ context.Context, _ resource.SchemaRequest, re }, }, "enabled": schema.BoolAttribute{ - Optional: true, - Computed: true, - Default: booldefault.StaticBool(true), + Optional: true, + Computed: true, + MarkdownDescription: "The flag to enable or disable a speicific consent. By default, the value is set to `true`", + Default: booldefault.StaticBool(true), }, "created_at": schema.StringAttribute{ - Computed: true, + Computed: true, + MarkdownDescription: "The timestamp when the consent version was created.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "updated_at": schema.StringAttribute{ - Computed: true, + Computed: true, + MarkdownDescription: "The timestamp when the consent version was last updated.", }, }, } @@ -137,11 +148,15 @@ func (r *ConsentResource) Read(ctx context.Context, req resource.ReadRequest, re for _, instance := range res.Data { if instance.ConsentName == state.Name.ValueString() { isAvailable = true - state.ID = util.StringValueOrNull(&instance.ID) - state.Name = util.StringValueOrNull(&instance.ConsentName) + id := instance.ID + consentName := instance.ConsentName + createdTime := instance.CreatedTime + updatedTime := instance.UpdatedTime + state.ID = util.StringValueOrNull(&id) + state.Name = util.StringValueOrNull(&consentName) state.Enabled = types.BoolValue(instance.Enabled) - state.CreatedAt = util.StringValueOrNull(&instance.CreatedTime) - state.UpdatedAt = util.StringValueOrNull(&instance.UpdatedTime) + state.CreatedAt = util.StringValueOrNull(&createdTime) + state.UpdatedAt = util.StringValueOrNull(&updatedTime) } } } diff --git a/internal/resources/resource_consent_version.go b/internal/resources/resource_consent_version.go new file mode 100644 index 0000000..f61a833 --- /dev/null +++ b/internal/resources/resource_consent_version.go @@ -0,0 +1,410 @@ +package resources + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + "github.com/Cidaas/terraform-provider-cidaas/helpers/util" + "github.com/Cidaas/terraform-provider-cidaas/internal/validators" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +const ( + SCOPES = "SCOPES" + URL = "URL" +) + +type ConsentVersionResource struct { + cidaasClient *cidaas.Client +} + +type ConsentVersionConfig struct { + ID types.String `tfsdk:"id"` + Version types.Float64 `tfsdk:"version"` + ConsentID types.String `tfsdk:"consent_id"` + ConsentType types.String `tfsdk:"consent_type"` + Scopes types.Set `tfsdk:"scopes"` + RequiredFields types.Set `tfsdk:"required_fields"` + ConsentLocales types.Set `tfsdk:"consent_locales"` + + consentLocale []*ConsentLocale +} + +type ConsentLocale struct { + Content types.String `tfsdk:"content"` + Locale types.String `tfsdk:"locale"` + URL types.String `tfsdk:"url"` +} + +func NewConsentVersionResource() resource.Resource { + return &ConsentVersionResource{} +} + +func (r *ConsentVersionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_consent_version" +} + +func (r *ConsentVersionResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*cidaas.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected cidaas.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.cidaasClient = client +} + +func (r *ConsentVersionResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, res *resource.ValidateConfigResponse) { + var config ConsentVersionConfig + res.Diagnostics.Append(req.Config.Get(ctx, &config)...) + res.Diagnostics.Append(config.extract(ctx)...) + + localeMap := make(map[string]bool) + + if !config.ConsentLocales.IsNull() && !config.ConsentLocales.IsUnknown() && len(config.consentLocale) > 0 { + for _, loc := range config.consentLocale { + locale := loc.Locale.ValueString() + if _, exists := localeMap[locale]; exists { + res.Diagnostics.AddError("Duplicate locale not allowed", fmt.Sprintf("Duplicate locale '%s' found in consent_locales", locale)) + } + if config.ConsentType.ValueString() == SCOPES && !loc.URL.IsNull() && !loc.URL.IsUnknown() { + res.Diagnostics.AddError("Unsupported attribute", "attribute 'consent_locales.url' not supported when consent_type is 'SCOPES'") + } + if config.ConsentType.ValueString() == URL && (loc.URL.IsNull() || loc.URL.ValueString() == "") { + res.Diagnostics.AddError("Missing required attribute", "attribute 'consent_locales.url' is required or can't be empty when consent_type is 'URL'") + } + localeMap[locale] = true + } + } + if config.ConsentType.ValueString() == SCOPES && config.Scopes.IsNull() { + res.Diagnostics.AddError("Missing required attribute", "attribute 'scopes' is required when consent_type is 'SCOPES'") + } + if config.ConsentType.ValueString() == SCOPES && config.RequiredFields.IsNull() { + res.Diagnostics.AddError("Missing required attribute", "attribute 'required_fields' is required when consent_type is 'SCOPES'") + } + if config.ConsentType.ValueString() == URL && !config.Scopes.IsNull() && !config.Scopes.IsUnknown() { + res.Diagnostics.AddError("Unsupported attribute", "attribute 'scopes' not supported when consent_type is 'URL'") + } + if config.ConsentType.ValueString() == URL && !config.RequiredFields.IsNull() && !config.RequiredFields.IsUnknown() { + res.Diagnostics.AddError("Unsupported attribute", "attribute 'required_fields' not supported when consent_type is 'URL'") + } +} + +func (r *ConsentVersionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "The Consent Version resource in the provider allows you to manage different versions of a specific consent in Cidaas." + + "\n This resource also supports managing consent versions across multiple locales enabling different configurations such as URLs and content for each locale." + + "\n\n Ensure that the below scopes are assigned to the client with the specified `client_id`:" + + "\n- cidaas:tenant_consent_read" + + "\n- cidaas:tenant_consent_write" + + "\n- cidaas:tenant_consent_delete", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The unique identifier of the consent version.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "consent_type": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Specifies the type of consent. The allowed values are `SCOPES` or `URL`. It can not be updated for a specific consent version.", + Validators: []validator.String{ + stringvalidator.OneOf(SCOPES, URL), + }, + PlanModifiers: []planmodifier.String{ + validators.UniqueIdentifier{}, + }, + }, + "consent_id": schema.StringAttribute{ + Required: true, + MarkdownDescription: "The `consent_id` for which the consent version is created. It can not be updated for a specific consent version.", + PlanModifiers: []planmodifier.String{ + validators.UniqueIdentifier{}, + }, + }, + "version": schema.Float64Attribute{ + Required: true, + MarkdownDescription: "The version number of the consent. It can not be updated for a specific consent version.", + PlanModifiers: []planmodifier.Float64{ + validators.ImmutableInt64Identifier{}, + }, + }, + "scopes": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "A set of scopes related to the consent. It can not be updated for a specific consent version." + + "\nNote that the attribute `scopes` is required only if the `consent_type` is set to **SCOPES**.", + PlanModifiers: []planmodifier.Set{ + validators.ImmutableSetIdentifier{}, + }, + }, + "required_fields": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "A set of fields that are required for the consent. It can not be updated for a specific consent version." + + "\nNote that the attribute `required_fields` is required only if the `consent_type` is set to **SCOPES**.", + PlanModifiers: []planmodifier.Set{ + validators.ImmutableSetIdentifier{}, + }, + }, + "consent_locales": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "content": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "The content of the consent version associated with a specific locale.", + }, + "locale": schema.StringAttribute{ + Required: true, + MarkdownDescription: "The locale for which the consent version is created. e.g. `en-us`, `de`.", + Validators: []validator.String{ + stringvalidator.OneOf( + func() []string { + validLocals := make([]string, len(util.Locals)) //nolint:gofumpt + for i, locale := range util.Locals { + validLocals[i] = strings.ToLower(locale.LocaleString) + } + return validLocals + }()...), + }, + }, + "url": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "The url to the consent page of the created consent version." + + "\nNote that the attribute `url` is required only if the `consent_type` is set to **URL**.", + }, + }, + }, + Required: true, + }, + }, + } +} + +func (cv *ConsentVersionConfig) extract(ctx context.Context) diag.Diagnostics { + var diags diag.Diagnostics + if !cv.ConsentLocales.IsNull() && !cv.ConsentLocales.IsUnknown() { + cv.consentLocale = make([]*ConsentLocale, 0, len(cv.ConsentLocales.Elements())) + diags = cv.ConsentLocales.ElementsAs(ctx, &cv.consentLocale, false) + } + return diags +} + +func (r *ConsentVersionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan ConsentVersionConfig + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(plan.extract(ctx)...) + consent := cidaas.ConsentVersionModel{ + Version: plan.Version.ValueFloat64(), + ConsentID: plan.ConsentID.ValueString(), + ConsentType: plan.ConsentType.ValueString(), + } + + if consent.ConsentType == SCOPES { + if len(plan.Scopes.Elements()) > 0 { + resp.Diagnostics.Append(plan.Scopes.ElementsAs(ctx, &consent.Scopes, false)...) + } + if len(plan.RequiredFields.Elements()) > 0 { + resp.Diagnostics.Append(plan.RequiredFields.ElementsAs(ctx, &consent.RequiredFields, false)...) + } + } + + var restLocals []*ConsentLocale + if !plan.ConsentLocales.IsNull() && !plan.ConsentLocales.IsUnknown() && len(plan.consentLocale) > 0 { + // consent version is created with the first element from the consent_local slice, + // as the API requires the consent_local field to be present in the request payload. + // later rest consent locals are processed and locals are created separately with the consent local api + consent.ConsentLocale.Locale = plan.consentLocale[0].Locale.ValueString() + if !plan.consentLocale[0].Content.IsNull() { + consent.ConsentLocale.Content = plan.consentLocale[0].Content.ValueString() + } + if consent.ConsentType == URL && !plan.consentLocale[0].URL.IsNull() { + consent.ConsentLocale.URL = plan.consentLocale[0].URL.ValueString() + } + restLocals = plan.consentLocale[1:] + } + + res, err := r.cidaasClient.ConsentVersion.Upsert(consent) + if err != nil { + resp.Diagnostics.AddError("failed to create consent version", fmt.Sprintf("Error: %s", err.Error())) + return + } + plan.ID = util.StringValueOrNull(&res.Data.ID) + for _, pcl := range restLocals { + consentLocal := cidaas.ConsentLocalModel{ + ConsentID: plan.ConsentID.ValueString(), + ConsentVersionID: plan.ID.ValueString(), + Content: pcl.Content.ValueString(), + Locale: pcl.Locale.ValueString(), + } + if plan.ConsentType.ValueString() == URL { + consentLocal.URL = pcl.URL.ValueString() + } + _, err := r.cidaasClient.ConsentVersion.UpsertLocal(consentLocal) + if err != nil { + resp.Diagnostics.AddError("failed to create consent locale", fmt.Sprintf("Error: %s", err.Error())) + return + } + } + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *ConsentVersionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state ConsentVersionConfig + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + resp.Diagnostics.Append(state.extract(ctx)...) + res, err := r.cidaasClient.ConsentVersion.Get(state.ConsentID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Failed to read consent version", fmt.Sprintf("Error: %s ", err.Error())) + return + } + if res.Success && res.Status == http.StatusOK && len(res.Data) == 0 { + resp.Diagnostics.AddError("Invalid consent_id", fmt.Sprintf("No consent version found for the provided consent_id %+v", state.ConsentID.String())) + return + } + isAvailable := false + if len(res.Data) > 0 { + for _, version := range res.Data { + if version.ID == state.ID.ValueString() { + isAvailable = true + consentType := version.ConsentType + state.Version = types.Float64Value(version.Version) + state.ConsentType = util.StringValueOrNull(&consentType) + } + } + } + var diag diag.Diagnostics + var objectValues []attr.Value + consentLocalObjectType := types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "content": types.StringType, + "locale": types.StringType, + "url": types.StringType, + }, + } + + for _, cl := range state.consentLocale { + res, err := r.cidaasClient.ConsentVersion.GetLocal(state.ID.ValueString(), cl.Locale.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Failed to read consent version locale", fmt.Sprintf("Error: %s ", err.Error())) + return + } + if !res.Success && res.Status == http.StatusNoContent { + resp.Diagnostics.AddError( + "Consent Version Local not found", + fmt.Sprintf("No consent version locale found for the combination of consent_version_id %s and locale %s.", state.ID.String(), cl.Locale.String()), + ) + return + } + state.ConsentID = util.StringValueOrNull(&res.Data.ConsentID) + if state.ConsentType.ValueString() == SCOPES { + state.Scopes = util.SetValueOrNull(res.Data.Scopes) + state.RequiredFields = util.SetValueOrNull(res.Data.RequiredFields) + } + + objValue := types.ObjectValueMust( + consentLocalObjectType.AttrTypes, + map[string]attr.Value{ + "content": util.StringValueOrNull(&res.Data.Content), + "locale": util.StringValueOrNull(&res.Data.Locale), + "url": util.StringValueOrNull(&res.Data.URL), + }) + objectValues = append(objectValues, objValue) + + } + state.ConsentLocales, diag = types.SetValueFrom(ctx, consentLocalObjectType, objectValues) + resp.Diagnostics.Append(diag...) + if resp.Diagnostics.HasError() { + return + } + if !isAvailable { + resp.Diagnostics.AddError( + "Consent Version not found", + fmt.Sprintf("Consent Version with ID %s not found for the provided consent_id %s", state.ID.String(), state.ConsentID.String()), + ) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *ConsentVersionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state ConsentVersionConfig + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(plan.extract(ctx)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + + for _, pcl := range plan.consentLocale { + consentLocal := cidaas.ConsentLocalModel{ + ConsentID: state.ConsentID.ValueString(), + ConsentVersionID: state.ID.ValueString(), + Content: pcl.Content.ValueString(), + Locale: pcl.Locale.ValueString(), + } + if plan.ConsentType.ValueString() == URL { + consentLocal.URL = pcl.URL.ValueString() + } + _, err := r.cidaasClient.ConsentVersion.UpsertLocal(consentLocal) + if err != nil { + resp.Diagnostics.AddError("Failed to update consent locale", fmt.Sprintf("Error: %s", err.Error())) + return + } + } + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *ConsentVersionResource) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) { +} + +func (r *ConsentVersionResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + id := req.ID + parts := strings.Split(id, ":") + if len(parts) < 3 { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: 'consent_id:id:locale', got: %s", id), + ) + return + } + locals := parts[2:] + var objectValues []attr.Value + consentLocalObjectType := types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "content": types.StringType, + "locale": types.StringType, + "url": types.StringType, + }, + } + + for _, locale := range locals { + objValue := types.ObjectValueMust( + consentLocalObjectType.AttrTypes, + map[string]attr.Value{ + "content": types.StringNull(), + "locale": types.StringValue(locale), + "url": types.StringNull(), + }) + objectValues = append(objectValues, objValue) + } + resp.State.SetAttribute(ctx, path.Root("consent_id"), parts[0]) + resp.State.SetAttribute(ctx, path.Root("id"), parts[1]) + resp.State.SetAttribute(ctx, path.Root("consent_locales"), types.SetValueMust(consentLocalObjectType, objectValues)) +} diff --git a/internal/resources/resource_role_test.go b/internal/resources/resource_role_test.go index 335bd63..8944a97 100644 --- a/internal/resources/resource_role_test.go +++ b/internal/resources/resource_role_test.go @@ -113,7 +113,7 @@ func TestAccRoleResource_updateRoleFails(t *testing.T) { }, { Config: testAccRoleResourceConfig(updatedRole, name, description), - ExpectError: regexp.MustCompile("Attribute role can't be modified"), + ExpectError: regexp.MustCompile("Attribute 'role' can't be modified"), }, }, }) diff --git a/internal/validators/unique_identifier.go b/internal/validators/unique_identifier.go index b3e864b..68baeba 100644 --- a/internal/validators/unique_identifier.go +++ b/internal/validators/unique_identifier.go @@ -7,12 +7,20 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" ) -var _ planmodifier.String = UniqueIdentifier{} +var ( + _ planmodifier.String = UniqueIdentifier{} + _ planmodifier.Float64 = ImmutableInt64Identifier{} + _ planmodifier.Set = ImmutableSetIdentifier{} +) -type UniqueIdentifier struct{} +type ( + UniqueIdentifier struct{} + ImmutableInt64Identifier struct{} + ImmutableSetIdentifier struct{} +) func (v UniqueIdentifier) Description(_ context.Context) string { - return "Checks if a unique identifier has been changed" + return "Checks if a immutable attribute has been changed." } func (v UniqueIdentifier) MarkdownDescription(ctx context.Context) string { @@ -26,6 +34,44 @@ func (v UniqueIdentifier) PlanModifyString(_ context.Context, req planmodifier.S if !req.ConfigValue.Equal(req.StateValue) { resp.Diagnostics.AddError("Unexpected Resource Configuration", - fmt.Sprintf("Attribute %s can't be modified. Existing value %s, got %s", req.Path.String(), req.StateValue.ValueString(), req.ConfigValue.ValueString())) + fmt.Sprintf("Attribute '%s' can't be modified. Existing value %s, got %s", req.Path.String(), req.StateValue.ValueString(), req.ConfigValue.ValueString())) + } +} + +func (v ImmutableInt64Identifier) Description(_ context.Context) string { + return "Checks if an immutable int64 attribute has been changed." +} + +func (v ImmutableInt64Identifier) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v ImmutableInt64Identifier) PlanModifyFloat64(_ context.Context, req planmodifier.Float64Request, resp *planmodifier.Float64Response) { + if req.StateValue.IsNull() || req.PlanValue.IsUnknown() || req.ConfigValue.IsUnknown() { + return + } + + if !req.ConfigValue.Equal(req.StateValue) { + resp.Diagnostics.AddError("Unexpected Resource Configuration", + fmt.Sprintf("Attribute '%s' can't be modified. Existing value %v, got %v", req.Path.String(), req.StateValue.ValueFloat64(), req.ConfigValue.ValueFloat64())) + } +} + +func (v ImmutableSetIdentifier) Description(_ context.Context) string { + return "Checks if an immutable set attribute has been changed." +} + +func (v ImmutableSetIdentifier) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v ImmutableSetIdentifier) PlanModifySet(_ context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) { + if req.StateValue.IsNull() || req.PlanValue.IsUnknown() || req.ConfigValue.IsUnknown() { + return + } + + if !req.ConfigValue.Equal(req.StateValue) { + resp.Diagnostics.AddError("Unexpected Resource Configuration", + fmt.Sprintf("Attribute '%s' can't be modified. Existing value %v, got %v", req.Path.String(), req.StateValue.Elements(), req.ConfigValue.Elements())) } } diff --git a/templates/resources/consent.md.tmpl b/templates/resources/consent.md.tmpl new file mode 100644 index 0000000..4c13462 --- /dev/null +++ b/templates/resources/consent.md.tmpl @@ -0,0 +1,26 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/resources/cidaas_consent/resource.tf" }} + +{{ .SchemaMarkdown | trimspace }} + +## Import + +In the import statement, the identifier is the combination of `consent_group_id` and `consent_name` joined by the special character ":". + +Below is an exmaple of import command to import a consent: + +```shell +terraform import cidaas_consent.sample a0508317-cec9-4f3e-afa4:sample_consent +``` \ No newline at end of file diff --git a/templates/resources/consent_version.md.tmpl b/templates/resources/consent_version.md.tmpl new file mode 100644 index 0000000..1603f07 --- /dev/null +++ b/templates/resources/consent_version.md.tmpl @@ -0,0 +1,28 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/resources/cidaas_consent_version/resource.tf" }} + +{{ .SchemaMarkdown | trimspace }} + +## Import + +In the import statement, the identifier is the combination of `consent_id`, `consent_version_id` and `locale` joined by the special character ":". +To import a consent version for multiple locales, you need to append the locales separated by ":". +For example, the identifier "3f453233-92d4-475b-b10e:813fbd47-6c50-4fc4-881a:en-us:de:en" imports the consent version for the locales `en-us`, `de` and `en`. + +Below is an exmaple of import command to import a consent version: + +```shell +terraform import cidaas_consent_version.v1 3f453233-92d4-475b-b10e:813fbd47-6c50-4fc4-881a:en-us +``` \ No newline at end of file From 56729316acbffd3099f8325c9dcba123278eaad9 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Tue, 20 Aug 2024 11:45:07 +0530 Subject: [PATCH 18/36] httpclient update in consent & consent version --- helpers/cidaas/consent.go | 49 ++++++++++---------- helpers/cidaas/consent_version.go | 74 ++++++++++++++++--------------- 2 files changed, 63 insertions(+), 60 deletions(-) diff --git a/helpers/cidaas/consent.go b/helpers/cidaas/consent.go index 18a4678..56c70d6 100644 --- a/helpers/cidaas/consent.go +++ b/helpers/cidaas/consent.go @@ -1,7 +1,6 @@ package cidaas import ( - "encoding/json" "fmt" "net/http" @@ -30,7 +29,7 @@ type ConsentInstanceResponse struct { } type ConsentClient struct { - HTTPClient util.HTTPClientInterface + ClientConfig } type ConsentService interface { Upsert(consent ConsentModel) (*ConsentResponse, error) @@ -38,53 +37,55 @@ type ConsentService interface { Delete(consentID string) error } -func NewConsent(httpClient util.HTTPClientInterface) ConsentService { - return &ConsentClient{HTTPClient: httpClient} +func NewConsent(clientConfig ClientConfig) ConsentService { + return &ConsentClient{clientConfig} } func (c *ConsentClient) Upsert(consentConfig ConsentModel) (*ConsentResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/instance")) - c.HTTPClient.SetMethod(http.MethodPost) - res, err := c.HTTPClient.MakeRequest(consentConfig) - if err != nil { + var response ConsentResponse + url := fmt.Sprintf("%s/%s", c.BaseURL, "consent-management-srv/v2/consent/instance") + httpClient := util.NewHTTPClient(url, http.MethodPost, c.AccessToken) + + res, err := httpClient.MakeRequest(consentConfig) + if err = util.HandleResponseError(res, err); err != nil { return nil, err } defer res.Body.Close() - var response ConsentResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + + if err = util.ProcessResponse(res, &response); err != nil { + return nil, err } return &response, nil } func (c *ConsentClient) GetConsentInstances(consentGroupID string) (*ConsentInstanceResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/instance", consentGroupID)) - c.HTTPClient.SetMethod(http.MethodGet) - res, err := c.HTTPClient.MakeRequest(nil) + var response ConsentInstanceResponse + url := fmt.Sprintf("%s/%s/%s", c.BaseURL, "consent-management-srv/v2/consent/instance", consentGroupID) + httpClient := util.NewHTTPClient(url, http.MethodGet, c.AccessToken) + + res, err := httpClient.MakeRequest(nil) if res.StatusCode == http.StatusNoContent { return &ConsentInstanceResponse{ Success: false, Status: http.StatusNoContent, }, nil } - if err != nil { + if err = util.HandleResponseError(res, err); err != nil { return nil, err } defer res.Body.Close() - var response ConsentInstanceResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json body, %w, %s", err, consentGroupID) + if err = util.ProcessResponse(res, &response); err != nil { + return nil, err } return &response, nil } func (c *ConsentClient) Delete(consentID string) error { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/instance", consentID)) - c.HTTPClient.SetMethod(http.MethodDelete) - res, err := c.HTTPClient.MakeRequest(nil) - if err != nil { + url := fmt.Sprintf("%s/%s/%s", c.BaseURL, "consent-management-srv/v2/consent/instance", consentID) + httpClient := util.NewHTTPClient(url, http.MethodDelete, c.AccessToken) + + res, err := httpClient.MakeRequest(nil) + if err = util.HandleResponseError(res, err); err != nil { return err } defer res.Body.Close() diff --git a/helpers/cidaas/consent_version.go b/helpers/cidaas/consent_version.go index d133b99..b4e3292 100644 --- a/helpers/cidaas/consent_version.go +++ b/helpers/cidaas/consent_version.go @@ -1,7 +1,6 @@ package cidaas import ( - "encoding/json" "fmt" "net/http" @@ -54,7 +53,7 @@ type ConsentLocale struct { } type ConsentVersion struct { - HTTPClient util.HTTPClientInterface + ClientConfig } type ConsentVersionService interface { @@ -64,76 +63,79 @@ type ConsentVersionService interface { GetLocal(consentVersionID string, locale string) (*ConsentLocalResponse, error) } -func NewConsentVersion(httpClient util.HTTPClientInterface) ConsentVersionService { - return &ConsentVersion{HTTPClient: httpClient} +func NewConsentVersion(clientConfig ClientConfig) ConsentVersionService { + return &ConsentVersion{clientConfig} } func (c *ConsentVersion) Upsert(consentVersionConfig ConsentVersionModel) (*ConsentVersionResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/versions")) - c.HTTPClient.SetMethod(http.MethodPost) - res, err := c.HTTPClient.MakeRequest(consentVersionConfig) - if err != nil { + var response ConsentVersionResponse + url := fmt.Sprintf("%s/%s", c.BaseURL, "consent-management-srv/v2/consent/versions") + httpClient := util.NewHTTPClient(url, http.MethodPost, c.AccessToken) + + res, err := httpClient.MakeRequest(consentVersionConfig) + if err = util.HandleResponseError(res, err); err != nil { return nil, err } defer res.Body.Close() - var response ConsentVersionResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + + if err = util.ProcessResponse(res, &response); err != nil { + return nil, err } return &response, nil } func (c *ConsentVersion) Get(consentID string) (*ConsentVersionReadResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/versions/list", consentID)) - c.HTTPClient.SetMethod(http.MethodGet) - res, err := c.HTTPClient.MakeRequest(nil) - if err != nil { + var response ConsentVersionReadResponse + url := fmt.Sprintf("%s/%s/%s", c.BaseURL, "consent-management-srv/v2/consent/versions/list", consentID) + httpClient := util.NewHTTPClient(url, http.MethodGet, c.AccessToken) + + res, err := httpClient.MakeRequest(nil) + if err = util.HandleResponseError(res, err); err != nil { return nil, err } defer res.Body.Close() - var response ConsentVersionReadResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json body, %w, %s", err, consentID) + + if err = util.ProcessResponse(res, &response); err != nil { + return nil, err } return &response, nil } func (c *ConsentVersion) UpsertLocal(consentLocal ConsentLocalModel) (*ConsentLocalResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/locale")) - c.HTTPClient.SetMethod(http.MethodPost) - res, err := c.HTTPClient.MakeRequest(consentLocal) - if err != nil { + var response ConsentLocalResponse + url := fmt.Sprintf("%s/%s", c.BaseURL, "consent-management-srv/v2/consent/locale") + httpClient := util.NewHTTPClient(url, http.MethodPost, c.AccessToken) + + res, err := httpClient.MakeRequest(consentLocal) + if err = util.HandleResponseError(res, err); err != nil { return nil, err } defer res.Body.Close() - var response ConsentLocalResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json body, %w", err) + + if err = util.ProcessResponse(res, &response); err != nil { + return nil, err } return &response, nil } func (c *ConsentVersion) GetLocal(consentVersionID string, locale string) (*ConsentLocalResponse, error) { - c.HTTPClient.SetURL(fmt.Sprintf("%s/%s/%s?locale=%s", c.HTTPClient.GetHost(), "consent-management-srv/v2/consent/locale", consentVersionID, locale)) - c.HTTPClient.SetMethod(http.MethodGet) - res, err := c.HTTPClient.MakeRequest(nil) + var response ConsentLocalResponse + url := fmt.Sprintf("%s/%s/%s?locale=%s", c.BaseURL, "consent-management-srv/v2/consent/locale", consentVersionID, locale) + httpClient := util.NewHTTPClient(url, http.MethodGet, c.AccessToken) + + res, err := httpClient.MakeRequest(nil) if res.StatusCode == http.StatusNoContent { return &ConsentLocalResponse{ Success: false, Status: http.StatusNoContent, }, nil } - if err != nil { + if err = util.HandleResponseError(res, err); err != nil { return nil, err } defer res.Body.Close() - var response ConsentLocalResponse - err = json.NewDecoder(res.Body).Decode(&response) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal json body, %w, %s", err, consentVersionID) + if err = util.ProcessResponse(res, &response); err != nil { + return nil, err } return &response, nil } From 5e0f2d59b0e1cf2582b4782f78d7e3d5ed024eb7 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Tue, 20 Aug 2024 14:54:08 +0530 Subject: [PATCH 19/36] lint fix --- helpers/cidaas/consent_group.go | 1 + helpers/cidaas/scope_group.go | 1 + internal/resources/resource_custom_provider.go | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/helpers/cidaas/consent_group.go b/helpers/cidaas/consent_group.go index 6e17a6a..0038094 100644 --- a/helpers/cidaas/consent_group.go +++ b/helpers/cidaas/consent_group.go @@ -1,3 +1,4 @@ +//nolint:dupl package cidaas import ( diff --git a/helpers/cidaas/scope_group.go b/helpers/cidaas/scope_group.go index 1d10100..3a2d27b 100644 --- a/helpers/cidaas/scope_group.go +++ b/helpers/cidaas/scope_group.go @@ -1,3 +1,4 @@ +// nolint:dupl package cidaas import ( diff --git a/internal/resources/resource_custom_provider.go b/internal/resources/resource_custom_provider.go index 98ec70e..77227ed 100644 --- a/internal/resources/resource_custom_provider.go +++ b/internal/resources/resource_custom_provider.go @@ -363,7 +363,6 @@ func (r *CustomProvider) Read(ctx context.Context, req resource.ReadRequest, res "profile", "picture", "website", "gender", "birthdate", "zoneinfo", "locale", "updated_at", "email", "email_verified", "phone_number", "mobile_number", "address", "sub", } - if strings.HasPrefix(key, "customFields.") { customFields[strings.TrimPrefix(key, "customFields.")] = util.StringValueOrNull(&val) hasCustomfield = true From 0907ff59689d946ad9ab522d654031fd00ffa2c1 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Wed, 21 Aug 2024 15:01:32 +0530 Subject: [PATCH 20/36] acc test for hosted_page and group_type --- .../resources/resource_group_type_test.go | 186 ++++++++++++++++ .../resources/resource_hosted_page_test.go | 203 ++++++++++++++++++ internal/resources/resource_role_test.go | 4 +- 3 files changed, 391 insertions(+), 2 deletions(-) create mode 100644 internal/resources/resource_group_type_test.go create mode 100644 internal/resources/resource_hosted_page_test.go diff --git a/internal/resources/resource_group_type_test.go b/internal/resources/resource_group_type_test.go new file mode 100644 index 0000000..12f249c --- /dev/null +++ b/internal/resources/resource_group_type_test.go @@ -0,0 +1,186 @@ +package resources_test + +import ( + "fmt" + "regexp" + "testing" + + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const resourceGroupType = "cidaas_group_type.test" + +// basic create, read and update test +func TestAccGroupTypeResource_Basic(t *testing.T) { + groupType := acctest.RandString(10) + roleMode := "any_roles" + description := "Test Group Type Description" + updatedDescription := "Updated Group Type Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckGroupTypeResourceDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccGroupTypeResourceConfig(groupType, roleMode, description), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceGroupType, "group_type", groupType), + resource.TestCheckResourceAttr(resourceGroupType, "role_mode", roleMode), + resource.TestCheckResourceAttr(resourceGroupType, "description", description), + resource.TestCheckResourceAttrSet(resourceGroupType, "id"), + resource.TestCheckResourceAttrSet(resourceGroupType, "created_at"), + ), + }, + { + Config: testAccGroupTypeResourceConfig(groupType, roleMode, updatedDescription), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceGroupType, "description", updatedDescription), + resource.TestCheckResourceAttrSet(resourceGroupType, "updated_at"), + ), + }, + }, + }) +} + +func testAccGroupTypeResourceConfig(groupType, roleMode, description string) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_group_type" "test" { + group_type = "%s" + role_mode = "%s" + description = "%s" + } + `, groupType, roleMode, description) +} + +func testAccCheckGroupTypeResourceDestroyed(s *terraform.State) error { + // Implement check to ensure the resource is destroyed + return nil +} + +// validation test for role_mode +func TestAccGroupTypeResource_InvalidRoleMode(t *testing.T) { + groupType := acctest.RandString(10) + invalidRoleMode := "invalid_role_mode" + description := "Test Group Type Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccGroupTypeResourceConfig(groupType, invalidRoleMode, description), + ExpectError: regexp.MustCompile(`Attribute role_mode value must be one of:`), // TODO: full string comparison + }, + }, + }) +} + +// import test +// the group type tried to import here is an existing role in cidaas. if deleted the test will throw error +func TestAccGroupTypeResource_import(t *testing.T) { + groupType := acctest.RandString(10) + roleMode := "any_roles" + description := "Test Group Type Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + // CheckDestroy: testAccCheckGroupTypeResourceDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccGroupTypeResourceConfig(groupType, roleMode, description), + }, + { + ResourceName: resourceGroupType, + ImportStateId: groupType, + ImportState: true, + ImportStateVerify: true, + // remove ImportStateVerifyIgnore to enhance the result + ImportStateVerifyIgnore: []string{"updated_at", "created_at"}, + }, + }, + }) +} + +// update failure test for group_type +func TestAccGroupTypeResource_UpdateFails(t *testing.T) { + groupType := acctest.RandString(10) + updatedGroupType := acctest.RandString(10) + roleMode := "any_roles" + description := "Test Group Type Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccGroupTypeResourceConfig(groupType, roleMode, description), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceGroupType, "group_type", groupType), + ), + }, + { + Config: testAccGroupTypeResourceConfig(updatedGroupType, roleMode, description), + ExpectError: regexp.MustCompile("Attribute 'group_type' can't be modified"), // TODO: full string comparison + }, + }, + }) +} + +// valid empty allowed_roles test +func TestAccGroupTypeResource_EmptyAllowedRoles(t *testing.T) { + groupType := acctest.RandString(10) + roleMode := "any_roles" + description := "Test Group Type Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckGroupTypeResourceDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccGroupTypeResourceConfig(groupType, roleMode, description), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceGroupType, "group_type", groupType), + resource.TestCheckResourceAttr(resourceGroupType, "role_mode", roleMode), + resource.TestCheckResourceAttr(resourceGroupType, "description", description), + resource.TestCheckResourceAttr(resourceGroupType, "allowed_roles.#", "0"), + ), + }, + }, + }) +} + +// invalid empty allowed roles when role_mode is allowed_roles +func TestAccGroupTypeResource_EmptyAllowedRolesError(t *testing.T) { + groupType := acctest.RandString(10) + roleMode := "allowed_roles" + description := "Test Group Type Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_group_type" "test" { + group_type = "%s" + role_mode = "%s" + description = "%s" + allowed_roles = [] + } + `, groupType, roleMode, description), + ExpectError: regexp.MustCompile("The attribute allowed_roles cannot be empty when role_mode is set to"), // TODO: full string comparison + }, + }, + }) +} diff --git a/internal/resources/resource_hosted_page_test.go b/internal/resources/resource_hosted_page_test.go new file mode 100644 index 0000000..6f5467c --- /dev/null +++ b/internal/resources/resource_hosted_page_test.go @@ -0,0 +1,203 @@ +package resources_test + +import ( + "regexp" + "testing" + + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const resourceHostedPage = "cidaas_hosted_page.test" + +func TestAccHostedPageResource_Basic(t *testing.T) { + hostedPageID := "register_success" + hostedPageURL := "https://cidaad.de/register_success" + updatedHostedPageURL := "https://cidaad.de/updated_register_success" + hostedPageContent := "Success" + hostedPageGroupName := "Test Hosted Page Group" + defaultLocale := "en-US" + hostedPages := []map[string]string{ + { + "hosted_page_id": hostedPageID, + "locale": defaultLocale, + "url": hostedPageURL, + "content": hostedPageContent, + }, + } + updatedHostedPages := []map[string]string{ + { + "hosted_page_id": hostedPageID, + "locale": defaultLocale, + "url": updatedHostedPageURL, + "content": "Updated Success", + }, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: testAccHostedPageResourceConfig( + hostedPageGroupName, + defaultLocale, + hostedPages, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_page_group_name", hostedPageGroupName), + resource.TestCheckResourceAttr(resourceHostedPage, "default_locale", defaultLocale), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.hosted_page_id", hostedPageID), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.locale", defaultLocale), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.url", hostedPageURL), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.content", hostedPageContent), + ), + }, + // ImportState testing + { + ResourceName: resourceHostedPage, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"created_at", "updated_at"}, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_page_group_name", hostedPageGroupName), + resource.TestCheckResourceAttr(resourceHostedPage, "default_locale", defaultLocale), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.hosted_page_id", hostedPageID), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.locale", defaultLocale), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.url", hostedPageURL), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.content", hostedPageContent), + ), + }, + // Update + { + Config: testAccHostedPageResourceConfig( + hostedPageGroupName, + defaultLocale, + updatedHostedPages, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.url", updatedHostedPageURL), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.content", "Updated Success"), + ), + }, + }, + }) +} + +func testAccHostedPageResourceConfig(hostedPageGroupName, defaultLocale string, hostedPages []map[string]string) string { + return ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_hosted_page" "test" { + hosted_page_group_name = "` + hostedPageGroupName + `" + default_locale = "` + defaultLocale + `" + + hosted_pages =[ + { + hosted_page_id = "` + hostedPages[0]["hosted_page_id"] + `" + locale = "` + hostedPages[0]["locale"] + `" + url = "` + hostedPages[0]["url"] + `" + content = "` + hostedPages[0]["content"] + `" + } + ] +} +` +} + +func testAccCheckHostedPageResourceDestroy(s *terraform.State) error { + // Implement your destroy check logic here, usually checking that the resource + // does not exist in the real system. + return nil +} + +// invalid locale +func TestAccHostedPageResource_InvalidLocale(t *testing.T) { + hostedPageGroupName := "Test Hosted Page Group" + invalidLocale := "invalid-locale" + hostedPages := []map[string]string{ + { + "hosted_page_id": "register_success", + "locale": "en-US", + "url": "https://cidaad.de/register_success", + "content": "Success", + }, + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + PreCheck: func() { acctest.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testAccHostedPageResourceConfig(hostedPageGroupName, invalidLocale, hostedPages), + ExpectError: regexp.MustCompile("Attribute default_locale value must be one of"), + }, + }, + }) +} + +// missing required fields validation +func TestAccHostedPageResource_MissingRequiredFields(t *testing.T) { + config1 := ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_hosted_page" "test" { + hosted_page_group_name = "" + default_locale = "en-US" + hosted_pages =[{ + hosted_page_id = "register_success" + url = "" + }] + } + ` + config2 := ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_hosted_page" "test" { + hosted_page_group_name = "" + default_locale = "en-US" + } + ` + config3 := ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_hosted_page" "test" { + hosted_page_group_name = "" + default_locale = "en-US" + hosted_pages =[] + } + ` + // validation where hosted_page_id and url is required + // config4 := ` + // provider "cidaas" { + // base_url = "https://kube-nightlybuild-dev.cidaas.de" + // } + // resource "cidaas_hosted_page" "test" { + // hosted_page_group_name = "" + // default_locale = "en-US" + // hosted_pages =[{}] + // } + // ` + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + PreCheck: func() { acctest.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: config1, + ExpectError: regexp.MustCompile("Attribute hosted_page_group_name string length must be at least 1, got: 0"), + }, + { + Config: config2, + ExpectError: regexp.MustCompile(`The argument "hosted_pages" is required, but no definition was found.`), + }, + { + Config: config3, + ExpectError: regexp.MustCompile(`Attribute hosted_pages list must contain at least 1 elements, got: 0`), + }, + }, + }) +} diff --git a/internal/resources/resource_role_test.go b/internal/resources/resource_role_test.go index 8944a97..0e44db5 100644 --- a/internal/resources/resource_role_test.go +++ b/internal/resources/resource_role_test.go @@ -113,7 +113,7 @@ func TestAccRoleResource_updateRoleFails(t *testing.T) { }, { Config: testAccRoleResourceConfig(updatedRole, name, description), - ExpectError: regexp.MustCompile("Attribute 'role' can't be modified"), + ExpectError: regexp.MustCompile("Attribute 'role' can't be modified"), // TODO: full string comparison }, }, }) @@ -176,7 +176,7 @@ func TestAccRoleResource_createMissingFields(t *testing.T) { Steps: []resource.TestStep{ { Config: missingRoleConfig, - ExpectError: regexp.MustCompile(`The argument "role" is required, but no definition was found.`), + ExpectError: regexp.MustCompile(`The argument "role" is required, but no definition was found.`), // TODO: full string comparison }, }, }) From 0d0743b572551ed50ea4c73d0435e1b6ee1dd7cb Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Fri, 23 Aug 2024 19:15:56 +0530 Subject: [PATCH 21/36] acc test for consent, scope group and consent group --- .../resources/resource_consent_group_test.go | 149 +++++++++++++ internal/resources/resource_consent_test.go | 180 ++++++++++++++++ .../resources/resource_group_type_test.go | 95 +++----- .../resources/resource_hosted_page_test.go | 202 +++++++++++++++--- .../resources/resource_scope_group_test.go | 149 +++++++++++++ internal/test/acctest.go | 24 +++ 6 files changed, 714 insertions(+), 85 deletions(-) create mode 100644 internal/resources/resource_consent_group_test.go create mode 100644 internal/resources/resource_consent_test.go create mode 100644 internal/resources/resource_scope_group_test.go diff --git a/internal/resources/resource_consent_group_test.go b/internal/resources/resource_consent_group_test.go new file mode 100644 index 0000000..c1d238c --- /dev/null +++ b/internal/resources/resource_consent_group_test.go @@ -0,0 +1,149 @@ +package resources_test + +import ( + "fmt" + "os" + "regexp" + "testing" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const resourceConsentGroup = "cidaas_consent_group.example" + +// create, read and update test +func TestAccConsentGroupResource_Basic(t *testing.T) { + groupName := acctest.RandString(10) + description := "Test consent Description" + updatedDescription := "Updated consent Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: testCheckConsentGroupDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccConsentGroupResourceConfig(groupName, description), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceConsentGroup, "group_name", groupName), + resource.TestCheckResourceAttr(resourceConsentGroup, "description", description), + resource.TestCheckResourceAttrSet(resourceConsentGroup, "id"), + resource.TestCheckResourceAttrSet(resourceConsentGroup, "created_at"), + resource.TestCheckResourceAttrSet(resourceConsentGroup, "updated_at"), + ), + }, + { + ResourceName: resourceConsentGroup, + ImportStateVerifyIdentifierAttribute: "id", + ImportState: true, + ImportStateVerify: true, + // TODO: remove ImportStateVerifyIgnore + ImportStateVerifyIgnore: []string{"updated_at", "created_at"}, + }, + { + Config: testAccConsentGroupResourceConfig(groupName, updatedDescription), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceConsentGroup, "description", updatedDescription), + resource.TestCheckResourceAttrSet(resourceConsentGroup, "updated_at"), + ), + }, + }, + }) +} + +func testAccConsentGroupResourceConfig(groupName, description string) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_consent_group" "example" { + group_name = "%s" + description = "%s" + } + `, groupName, description) +} + +func testCheckConsentGroupDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceConsentGroup] + if !ok { + return fmt.Errorf("resource %s not fround", resourceConsentGroup) + } + + consentGroup := cidaas.ConsentGroup{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TEST_TOKEN, + }, + } + res, _ := consentGroup.Get(rs.Primary.ID) + if res != nil { + // when resource exits in remote + return fmt.Errorf("resource stil exists %+v", res) + } + return nil +} + +// failed validation on updating immutable proprty group_name +func TestAccConsentGroupResource_GoupNameUpdateFail(t *testing.T) { + groupName := acctest.RandString(10) + updateGroupName := acctest.RandString(10) + description := "Test consent Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccConsentGroupResourceConfig(groupName, description), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceConsentGroup, "group_name", groupName), + ), + }, + { + Config: testAccConsentGroupResourceConfig(updateGroupName, description), + ExpectError: regexp.MustCompile(`Attribute 'group_name' can't be modified.`), + }, + }, + }) +} + +// Empty group_name validation test +func TestAccConsentGroupResource_EmptyGroupName(t *testing.T) { + groupName := "" + description := "Test consent Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccConsentGroupResourceConfig(groupName, description), + ExpectError: regexp.MustCompile(`Attribute group_name string length must be at least 1, got: 0`), + }, + }, + }) +} + +// missing required parameter +func TestAccConsentGroupResource_MissingRequired(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_consent_group" "example" { + description = "test description" + } + `, + ExpectError: regexp.MustCompile(`The argument "group_name" is required, but no definition was found.`), + }, + }, + }) +} diff --git a/internal/resources/resource_consent_test.go b/internal/resources/resource_consent_test.go new file mode 100644 index 0000000..fad0ffe --- /dev/null +++ b/internal/resources/resource_consent_test.go @@ -0,0 +1,180 @@ +package resources_test + +import ( + "fmt" + "net/http" + "os" + "regexp" + "testing" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +const resourceConsent = "cidaas_consent.example" +const refResourceConsentGroup = "cidaas_consent_group.example" + +// create, read and update test +func TestAccConsentResource_Basic(t *testing.T) { + groupName := acctest.RandString(10) + name := acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: testCheckConsentDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccConsentResourceConfig(groupName, name, true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceConsent, "consent_group_id", refResourceConsentGroup, "id"), + resource.TestCheckResourceAttr(resourceConsent, "name", name), + resource.TestCheckResourceAttrSet(resourceConsent, "id"), + resource.TestCheckResourceAttrSet(resourceConsent, "enabled"), + resource.TestCheckResourceAttrSet(resourceConsent, "created_at"), + resource.TestCheckResourceAttrSet(resourceConsent, "updated_at"), + ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "cidaas_consent.example", + tfjsonpath.New("enabled"), + knownvalue.Bool(true), + ), + }, + }, + { + ResourceName: resourceConsent, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[refResourceConsentGroup] + if !ok { + return "", fmt.Errorf("Not found: %s", refResourceConsentGroup) + } + return rs.Primary.ID + ":" + name, nil + }, + ImportState: true, + ImportStateVerify: true, + // TODO: remove ImportStateVerifyIgnore + ImportStateVerifyIgnore: []string{"updated_at", "created_at"}, + }, + { + Config: testAccConsentResourceConfig(groupName, name, false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceConsent, "updated_at"), + ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue( + "cidaas_consent.example", + tfjsonpath.New("enabled"), + knownvalue.Bool(false), + ), + }, + }, + }, + }) +} + +func testAccConsentResourceConfig(groupName, name string, enabled bool) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_consent_group" "example" { + group_name = "%s" + } + resource "cidaas_consent" "example" { + consent_group_id = cidaas_consent_group.example.id + name = "%s" + enabled = "%v" + } + `, groupName, name, enabled) +} + +func testCheckConsentDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[refResourceConsentGroup] + if !ok { + return fmt.Errorf("resource %s not fround", refResourceConsentGroup) + } + + consent := cidaas.ConsentClient{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TEST_TOKEN, + }, + } + res, _ := consent.GetConsentInstances(rs.Primary.ID) + if res != nil && res.Status != http.StatusNoContent && len(res.Data) > 0 { + // when resource exits in remote + return fmt.Errorf("resource stil exists %+v", res) + } + return nil +} + +// failed validation on updating immutable proprty group_name +func TestAccConsentResource_GoupNameUpdateFail(t *testing.T) { + groupName := acctest.RandString(10) + name := acctest.RandString(10) + updatedName := acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccConsentResourceConfig(groupName, name, true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceConsent, "consent_group_id", refResourceConsentGroup, "id"), + ), + }, + { + Config: testAccConsentResourceConfig(groupName, updatedName, true), + ExpectError: regexp.MustCompile(`Attribute 'name' can't be modified.`), + }, + }, + }) +} + +// empty consent_group_id & group_name validation test +func TestAccConsentResource_EmptyGroupName(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_consent" "example" { + consent_group_id = "" + name = "" + } + `, + ExpectError: regexp.MustCompile(`Attribute consent_group_id string length must be at least 1, got: 0`), + }, + }, + }) +} + +// missing required parameters +func TestAccConsentResource_MissingRequired(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_consent" "example" { + } + `, + ExpectError: regexp.MustCompile(`The argument "name" is required, but no definition was found.`), + }, + }, + }) +} diff --git a/internal/resources/resource_group_type_test.go b/internal/resources/resource_group_type_test.go index 12f249c..1ec6af0 100644 --- a/internal/resources/resource_group_type_test.go +++ b/internal/resources/resource_group_type_test.go @@ -2,17 +2,19 @@ package resources_test import ( "fmt" + "os" "regexp" "testing" + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" ) -const resourceGroupType = "cidaas_group_type.test" +const resourceGroupType = "cidaas_group_type.example" -// basic create, read and update test +// create, read and update test func TestAccGroupTypeResource_Basic(t *testing.T) { groupType := acctest.RandString(10) roleMode := "any_roles" @@ -22,7 +24,7 @@ func TestAccGroupTypeResource_Basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, - CheckDestroy: testAccCheckGroupTypeResourceDestroyed, + CheckDestroy: testCheckGroupTypeDestroyed, Steps: []resource.TestStep{ { Config: testAccGroupTypeResourceConfig(groupType, roleMode, description), @@ -34,6 +36,14 @@ func TestAccGroupTypeResource_Basic(t *testing.T) { resource.TestCheckResourceAttrSet(resourceGroupType, "created_at"), ), }, + { + ResourceName: resourceGroupType, + ImportStateId: groupType, + ImportState: true, + ImportStateVerify: true, + // remove ImportStateVerifyIgnore to enhance the result + ImportStateVerifyIgnore: []string{"updated_at", "created_at"}, + }, { Config: testAccGroupTypeResourceConfig(groupType, roleMode, updatedDescription), Check: resource.ComposeAggregateTestCheckFunc( @@ -50,7 +60,7 @@ func testAccGroupTypeResourceConfig(groupType, roleMode, description string) str provider "cidaas" { base_url = "https://kube-nightlybuild-dev.cidaas.de" } - resource "cidaas_group_type" "test" { + resource "cidaas_group_type" "example" { group_type = "%s" role_mode = "%s" description = "%s" @@ -58,8 +68,24 @@ func testAccGroupTypeResourceConfig(groupType, roleMode, description string) str `, groupType, roleMode, description) } -func testAccCheckGroupTypeResourceDestroyed(s *terraform.State) error { - // Implement check to ensure the resource is destroyed +func testCheckGroupTypeDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceGroupType] + if !ok { + return fmt.Errorf("resource %s not fround", resourceGroupType) + } + + groupType := cidaas.GroupType{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TEST_TOKEN, + }, + } + res, _ := groupType.Get(rs.Primary.Attributes["group_type"]) + + if res != nil { + // when resource exits in remote + return fmt.Errorf("resource %s stil exists", res.Data.GroupType) + } return nil } @@ -81,34 +107,7 @@ func TestAccGroupTypeResource_InvalidRoleMode(t *testing.T) { }) } -// import test -// the group type tried to import here is an existing role in cidaas. if deleted the test will throw error -func TestAccGroupTypeResource_import(t *testing.T) { - groupType := acctest.RandString(10) - roleMode := "any_roles" - description := "Test Group Type Description" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.TestAccPreCheck(t) }, - ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, - // CheckDestroy: testAccCheckGroupTypeResourceDestroyed, - Steps: []resource.TestStep{ - { - Config: testAccGroupTypeResourceConfig(groupType, roleMode, description), - }, - { - ResourceName: resourceGroupType, - ImportStateId: groupType, - ImportState: true, - ImportStateVerify: true, - // remove ImportStateVerifyIgnore to enhance the result - ImportStateVerifyIgnore: []string{"updated_at", "created_at"}, - }, - }, - }) -} - -// update failure test for group_type +// group_type can't be modified func TestAccGroupTypeResource_UpdateFails(t *testing.T) { groupType := acctest.RandString(10) updatedGroupType := acctest.RandString(10) @@ -133,31 +132,7 @@ func TestAccGroupTypeResource_UpdateFails(t *testing.T) { }) } -// valid empty allowed_roles test -func TestAccGroupTypeResource_EmptyAllowedRoles(t *testing.T) { - groupType := acctest.RandString(10) - roleMode := "any_roles" - description := "Test Group Type Description" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.TestAccPreCheck(t) }, - ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, - CheckDestroy: testAccCheckGroupTypeResourceDestroyed, - Steps: []resource.TestStep{ - { - Config: testAccGroupTypeResourceConfig(groupType, roleMode, description), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceGroupType, "group_type", groupType), - resource.TestCheckResourceAttr(resourceGroupType, "role_mode", roleMode), - resource.TestCheckResourceAttr(resourceGroupType, "description", description), - resource.TestCheckResourceAttr(resourceGroupType, "allowed_roles.#", "0"), - ), - }, - }, - }) -} - -// invalid empty allowed roles when role_mode is allowed_roles +// allowed_roles must have value when role_mode is allowed_roles or roles_required func TestAccGroupTypeResource_EmptyAllowedRolesError(t *testing.T) { groupType := acctest.RandString(10) roleMode := "allowed_roles" @@ -172,7 +147,7 @@ func TestAccGroupTypeResource_EmptyAllowedRolesError(t *testing.T) { provider "cidaas" { base_url = "https://kube-nightlybuild-dev.cidaas.de" } - resource "cidaas_group_type" "test" { + resource "cidaas_group_type" "example" { group_type = "%s" role_mode = "%s" description = "%s" diff --git a/internal/resources/resource_hosted_page_test.go b/internal/resources/resource_hosted_page_test.go index 6f5467c..d9446f5 100644 --- a/internal/resources/resource_hosted_page_test.go +++ b/internal/resources/resource_hosted_page_test.go @@ -1,16 +1,20 @@ package resources_test import ( + "fmt" + "os" "regexp" "testing" + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" ) -const resourceHostedPage = "cidaas_hosted_page.test" +const resourceHostedPage = "cidaas_hosted_page.example" +// create, read and update test func TestAccHostedPageResource_Basic(t *testing.T) { hostedPageID := "register_success" hostedPageURL := "https://cidaad.de/register_success" @@ -38,8 +42,8 @@ func TestAccHostedPageResource_Basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: testCheckHostedPageDestroyed, Steps: []resource.TestStep{ - // Create and Read testing { Config: testAccHostedPageResourceConfig( hostedPageGroupName, @@ -47,6 +51,7 @@ func TestAccHostedPageResource_Basic(t *testing.T) { hostedPages, ), Check: resource.ComposeAggregateTestCheckFunc( + testCheckHostedPageExists(resourceHostedPage), resource.TestCheckResourceAttr(resourceHostedPage, "hosted_page_group_name", hostedPageGroupName), resource.TestCheckResourceAttr(resourceHostedPage, "default_locale", defaultLocale), resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.hosted_page_id", hostedPageID), @@ -55,7 +60,6 @@ func TestAccHostedPageResource_Basic(t *testing.T) { resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.content", hostedPageContent), ), }, - // ImportState testing { ResourceName: resourceHostedPage, ImportState: true, @@ -70,7 +74,6 @@ func TestAccHostedPageResource_Basic(t *testing.T) { resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.content", hostedPageContent), ), }, - // Update { Config: testAccHostedPageResourceConfig( hostedPageGroupName, @@ -88,28 +91,52 @@ func TestAccHostedPageResource_Basic(t *testing.T) { func testAccHostedPageResourceConfig(hostedPageGroupName, defaultLocale string, hostedPages []map[string]string) string { return ` - provider "cidaas" { - base_url = "https://kube-nightlybuild-dev.cidaas.de" - } - resource "cidaas_hosted_page" "test" { - hosted_page_group_name = "` + hostedPageGroupName + `" - default_locale = "` + defaultLocale + `" + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_hosted_page" "example" { + hosted_page_group_name = "` + hostedPageGroupName + `" + default_locale = "` + defaultLocale + `" - hosted_pages =[ - { - hosted_page_id = "` + hostedPages[0]["hosted_page_id"] + `" - locale = "` + hostedPages[0]["locale"] + `" - url = "` + hostedPages[0]["url"] + `" - content = "` + hostedPages[0]["content"] + `" + hosted_pages =[ + { + hosted_page_id = "` + hostedPages[0]["hosted_page_id"] + `" + locale = "` + hostedPages[0]["locale"] + `" + url = "` + hostedPages[0]["url"] + `" + content = "` + hostedPages[0]["content"] + `" + } + ] } - ] + ` } -` + +func testCheckHostedPageExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if _, ok := s.RootModule().Resources[resourceName]; !ok { + return fmt.Errorf("Not found: %s, found resource %s", resourceName, s.RootModule().Resources) + } + return nil + } } -func testAccCheckHostedPageResourceDestroy(s *terraform.State) error { - // Implement your destroy check logic here, usually checking that the resource - // does not exist in the real system. +func testCheckHostedPageDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceHostedPage] + if !ok { + return fmt.Errorf("resource %s not fround", resourceHostedPage) + } + + hp := cidaas.HostedPage{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TEST_TOKEN, + }, + } + res, _ := hp.Get(rs.Primary.Attributes["hosted_page_group_name"]) + + if res != nil { + // when resource exits in remote + return fmt.Errorf("resource %s stil exists", res.Data) + } return nil } @@ -143,7 +170,7 @@ func TestAccHostedPageResource_MissingRequiredFields(t *testing.T) { provider "cidaas" { base_url = "https://kube-nightlybuild-dev.cidaas.de" } - resource "cidaas_hosted_page" "test" { + resource "cidaas_hosted_page" "example" { hosted_page_group_name = "" default_locale = "en-US" hosted_pages =[{ @@ -156,7 +183,7 @@ func TestAccHostedPageResource_MissingRequiredFields(t *testing.T) { provider "cidaas" { base_url = "https://kube-nightlybuild-dev.cidaas.de" } - resource "cidaas_hosted_page" "test" { + resource "cidaas_hosted_page" "example" { hosted_page_group_name = "" default_locale = "en-US" } @@ -165,7 +192,7 @@ func TestAccHostedPageResource_MissingRequiredFields(t *testing.T) { provider "cidaas" { base_url = "https://kube-nightlybuild-dev.cidaas.de" } - resource "cidaas_hosted_page" "test" { + resource "cidaas_hosted_page" "example" { hosted_page_group_name = "" default_locale = "en-US" hosted_pages =[] @@ -176,7 +203,7 @@ func TestAccHostedPageResource_MissingRequiredFields(t *testing.T) { // provider "cidaas" { // base_url = "https://kube-nightlybuild-dev.cidaas.de" // } - // resource "cidaas_hosted_page" "test" { + // resource "cidaas_hosted_page" "example" { // hosted_page_group_name = "" // default_locale = "en-US" // hosted_pages =[{}] @@ -201,3 +228,128 @@ func TestAccHostedPageResource_MissingRequiredFields(t *testing.T) { }, }) } + +// Immutable attribute hosted_page_group_name validation +func TestAccHostedPageResource_UniqueIdentifier(t *testing.T) { + hostedPageID := "register_success" + hostedPageURL := "https://cidaad.de/register_success" + hostedPageGroupName := "Unique Hosted Page Group" + updatedHostedPageGroupName := "Updated Hosted Page Group" + defaultLocale := "en-US" + hostedPageContent := "Success" + hostedPages := []map[string]string{ + { + "hosted_page_id": hostedPageID, + "locale": defaultLocale, + "url": hostedPageURL, + "content": hostedPageContent, + }, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccHostedPageResourceConfig( + hostedPageGroupName, + defaultLocale, + hostedPages, + ), + }, + { + Config: testAccHostedPageResourceConfig( + updatedHostedPageGroupName, + defaultLocale, + hostedPages, + ), + ExpectError: regexp.MustCompile("Attribute 'hosted_page_group_name' can't be modified"), + }, + }, + }) +} + +// Invalid hosted_page_id +func TestAccHostedPageResource_InvalidHostedPageID(t *testing.T) { + hostedPageID := "invalid" + hostedPageURL := "https://cidaad.de/register_success" + hostedPageGroupName := "Unique Hosted Page Group" + defaultLocale := "en-US" + hostedPageContent := "Success" + hostedPages := []map[string]string{ + { + "hosted_page_id": hostedPageID, + "locale": defaultLocale, + "url": hostedPageURL, + "content": hostedPageContent, + }, + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + PreCheck: func() { acctest.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: testAccHostedPageResourceConfig( + hostedPageGroupName, + defaultLocale, + hostedPages, + ), + ExpectError: regexp.MustCompile("hosted_page_id value must be one of"), // TODO: full string comparison + }, + }, + }) +} + +// validate multiple hosted pages +func TestAccHostedPageResource_MultipleHostedPages(t *testing.T) { + hostedPageID1 := "register_success" + hostedPageURL1 := "https://cidaad.de/register_success" + hostedPageID2 := "login_success" + hostedPageURL2 := "https://cidaad.de/login_success" + hostedPageGroupName := "Test Hosted Page Group" + content1 := "Register Success" + content2 := "Login Success" + defaultLocale := "en-US" + + config := ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_hosted_page" "example" { + hosted_page_group_name = "` + hostedPageGroupName + `" + default_locale = "` + defaultLocale + `" + + hosted_pages =[ + { + hosted_page_id = "` + hostedPageID1 + `" + locale = "` + defaultLocale + `" + url = "` + hostedPageURL1 + `" + content = "` + content1 + `" + }, + { + hosted_page_id = "` + hostedPageID2 + `" + locale = "en-IN" + url = "` + hostedPageURL2 + `" + content = "` + content2 + `" + } + ] + } + ` + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + PreCheck: func() { acctest.TestAccPreCheck(t) }, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.hosted_page_id", hostedPageID1), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.url", hostedPageURL1), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.content", content1), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.1.hosted_page_id", hostedPageID2), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.1.url", hostedPageURL2), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.1.content", content2), + ), + }, + }, + }) +} diff --git a/internal/resources/resource_scope_group_test.go b/internal/resources/resource_scope_group_test.go new file mode 100644 index 0000000..5bd7d11 --- /dev/null +++ b/internal/resources/resource_scope_group_test.go @@ -0,0 +1,149 @@ +package resources_test + +import ( + "fmt" + "os" + "regexp" + "testing" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const resourceScopeGroup = "cidaas_scope_group.example" + +// create, read and update test +func TestAccScopeGroupResource_Basic(t *testing.T) { + groupName := acctest.RandString(10) + description := "Test Scope Group Description" + updatedDescription := "Updated Scope Group Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: testCheckScopeGroupDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccScopeGroupResourceConfig(groupName, description), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceScopeGroup, "group_name", groupName), + resource.TestCheckResourceAttr(resourceScopeGroup, "description", description), + resource.TestCheckResourceAttrSet(resourceScopeGroup, "id"), + resource.TestCheckResourceAttrSet(resourceScopeGroup, "created_at"), + resource.TestCheckResourceAttrSet(resourceScopeGroup, "updated_at"), + ), + }, + { + ResourceName: resourceScopeGroup, + ImportStateId: groupName, + ImportState: true, + ImportStateVerify: true, + // TODO: remove ImportStateVerifyIgnore + ImportStateVerifyIgnore: []string{"updated_at", "created_at"}, + }, + { + Config: testAccScopeGroupResourceConfig(groupName, updatedDescription), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceScopeGroup, "description", updatedDescription), + resource.TestCheckResourceAttrSet(resourceScopeGroup, "updated_at"), + ), + }, + }, + }) +} + +func testAccScopeGroupResourceConfig(groupType, description string) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_scope_group" "example" { + group_name = "%s" + description = "%s" + } + `, groupType, description) +} + +func testCheckScopeGroupDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceScopeGroup] + if !ok { + return fmt.Errorf("resource %s not fround", resourceScopeGroup) + } + + scopeGroup := cidaas.ScopeGroup{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TEST_TOKEN, + }, + } + res, _ := scopeGroup.Get(rs.Primary.Attributes["group_name"]) + if res.Data.ID != "" { + // when resource exits in remote + return fmt.Errorf("resource stil exists %+v", res) + } + return nil +} + +// failed validation on updating immutable proprty group_name +func TestAccScopeGroupResource_GoupNameUpdateFail(t *testing.T) { + groupName := acctest.RandString(10) + updateGroupName := acctest.RandString(10) + description := "Test Scope Group Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccScopeGroupResourceConfig(groupName, description), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceScopeGroup, "group_name", groupName), + ), + }, + { + Config: testAccScopeGroupResourceConfig(updateGroupName, description), + ExpectError: regexp.MustCompile("Attribute 'group_name' can't be modified"), // TODO: full string comparison + }, + }, + }) +} + +// Empty group_name validation test +func TestAccScopeGroupResource_EmptyGroupName(t *testing.T) { + groupName := "" + description := "Test Scope Group Description" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccScopeGroupResourceConfig(groupName, description), + ExpectError: regexp.MustCompile(`Attribute group_name string length must be at least 1, got: 0`), + }, + }, + }) +} + +// missing required parameter +func TestAccScopeGroupResource_MissingRequired(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_scope_group" "example" { + description = "test description" + } + `, + ExpectError: regexp.MustCompile(`The argument "group_name" is required, but no definition was found.`), + }, + }, + }) +} diff --git a/internal/test/acctest.go b/internal/test/acctest.go index bde607a..42328f1 100644 --- a/internal/test/acctest.go +++ b/internal/test/acctest.go @@ -1,11 +1,15 @@ package acctest import ( + "fmt" "math/rand" + "net/http" "os" "testing" "time" + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + "github.com/Cidaas/terraform-provider-cidaas/helpers/util" provider "github.com/Cidaas/terraform-provider-cidaas/internal" "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" @@ -19,6 +23,8 @@ var TestAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServe "cidaas": providerserver.NewProtocol6WithError(provider.Cidaas("test")()), } +var TEST_TOKEN string + func TestAccPreCheck(t *testing.T) { // You can add code here to run prior to any test case execution, for example assertions // about the appropriate environment variables being set are common to see in a pre-check @@ -32,6 +38,24 @@ func TestAccPreCheck(t *testing.T) { if os.Getenv("TERRAFORM_PROVIDER_CIDAAS_CLIENT_SECRET") == "" { t.Fatal("TERRAFORM_PROVIDER_CIDAAS_CLIENT_SECRET must be set for acceptance tests") } + + tokenUrl := fmt.Sprintf("%s/%s", os.Getenv("BASE_URL"), "token-srv/token") + httpClient := util.NewHTTPClient(tokenUrl, http.MethodPost) + payload := map[string]string{ + "client_id": os.Getenv("TERRAFORM_PROVIDER_CIDAAS_CLIENT_ID"), + "client_secret": os.Getenv("TERRAFORM_PROVIDER_CIDAAS_CLIENT_SECRET"), + "grant_type": "client_credentials", + } + res, err := httpClient.MakeRequest(payload) + if err = util.HandleResponseError(res, err); err != nil { + t.Fatalf("failed to generate access token %s", err.Error()) + } + defer res.Body.Close() + var response cidaas.TokenResponse + if err = util.ProcessResponse(res, &response); err != nil { + t.Fatalf("failed to generate access token %s", err.Error()) + } + TEST_TOKEN = response.AccessToken } // RandString generates a random string with the given length. From e3357ace426f23ecee86732164123ed99fb1b83c Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Fri, 23 Aug 2024 19:20:09 +0530 Subject: [PATCH 22/36] ci update with new variable BASE_URL --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cdc8971..e310325 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -13,7 +13,7 @@ acceptance_test: before_script: - echo "machine gitlab.widas.de login $GITLAB_LOGIN password $GITLAB_TOKEN" > ~/.netrc script: - - export TERRAFORM_PROVIDER_CIDAAS_CLIENT_ID=$CI_ID TERRAFORM_PROVIDER_CIDAAS_CLIENT_SECRET=$CI_SECRET + - export TERRAFORM_PROVIDER_CIDAAS_CLIENT_ID=$CI_ID TERRAFORM_PROVIDER_CIDAAS_CLIENT_SECRET=$CI_SECRET BASE_URL=$BASE_URL - make test-ci coverage: '/total:\s+\(statements\)\s+(\d+(?:\.\d+)?%)/' artifacts: From f61fbc808bd8bcebd40a314c815a6298ede4145b Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Mon, 26 Aug 2024 15:24:29 +0530 Subject: [PATCH 23/36] cidaas_password_policy acceptance test acceptance test added for cidaas_password_policy resource --- .../resource_password_policy_test.go | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 internal/resources/resource_password_policy_test.go diff --git a/internal/resources/resource_password_policy_test.go b/internal/resources/resource_password_policy_test.go new file mode 100644 index 0000000..af6039d --- /dev/null +++ b/internal/resources/resource_password_policy_test.go @@ -0,0 +1,109 @@ +package resources_test + +import ( + "fmt" + "regexp" + "testing" + + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +const resourcePwdPolicy = "cidaas_password_policy.example" + +// create, read and update test +func TestAccPwdPolicyResource_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccPwdPolicyResourceConfig(8, 200, 1), + ExpectError: regexp.MustCompile(`Creating this resource using`), + }, + { + ResourceName: resourcePwdPolicy, + ImportState: true, + ImportStateVerify: false, // made false as it compares existing state id and imported id. we don't have existing as create is not supported + ImportStateId: "cidaas", + ImportStatePersist: true, + }, + // update is skipped as password policy resource cannot be deleted in cidaas + // update might result in unintended changes or incorrect values being applied. + }, + }) +} + +func testAccPwdPolicyResourceConfig(minimumLength, maximumLength, reuseLimit int64) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_password_policy" "example" { + minimum_length = %d + maximum_length = %d + lower_and_uppercase = true + no_of_digits = 1 + expiration_in_days = 30 + no_of_special_chars = 1 + no_of_days_to_remind = 1 + reuse_limit = %d + } + `, minimumLength, maximumLength, reuseLimit) +} + +// maximum_length should be greater than minimum_length +func TestAccPwdPolicyResource_MaxMinLengthValidation(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + // minimumLength 20 & maximumLength 10 + Config: testAccPwdPolicyResourceConfig(20, 10, 1), + // TODO: add no_of_special_chars + no_of_digits + lower_and_uppercase in validation + ExpectError: regexp.MustCompile(`Attribute maximum_length value must be at least sum of minimum_length`), + }, + }, + }) +} + +// invalid reuse limit +func TestAccPwdPolicyResource_InvalidReuseLimit(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + // invalid limit cannot be greater than 5 + Config: testAccPwdPolicyResourceConfig(10, 20, 10), + ExpectError: regexp.MustCompile(`reuse_limit value must be at most 5`), // improve error and extra oarams to sum + }, + }, + }) +} + +// missing required parameter +func TestAccPwdPolicyResource_MissingRequired(t *testing.T) { + requiredParams := []string{ + "minimum_length", "lower_and_uppercase", "no_of_digits", "expiration_in_days", + "no_of_special_chars", "no_of_days_to_remind", "reuse_limit", "maximum_length", + } + for _, param := range requiredParams { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_password_policy" "example" {} + `, + ExpectError: regexp.MustCompile(fmt.Sprintf(`The argument "%s" is required, but no definition was found.`, param)), + }, + }, + }) + } +} From a7d7d134659ca69a4bbe63bc7d453239ffab2208 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Mon, 26 Aug 2024 16:17:06 +0530 Subject: [PATCH 24/36] lint fix fixed lint issues in test files --- .../resources/resource_consent_group_test.go | 18 ++--- internal/resources/resource_consent_test.go | 8 +- .../resources/resource_group_type_test.go | 29 +++---- .../resources/resource_hosted_page_test.go | 80 +++++++------------ .../resources/resource_scope_group_test.go | 34 ++++---- internal/test/acctest.go | 8 +- 6 files changed, 75 insertions(+), 102 deletions(-) diff --git a/internal/resources/resource_consent_group_test.go b/internal/resources/resource_consent_group_test.go index c1d238c..eb0e477 100644 --- a/internal/resources/resource_consent_group_test.go +++ b/internal/resources/resource_consent_group_test.go @@ -12,12 +12,15 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) -const resourceConsentGroup = "cidaas_consent_group.example" +const ( + resourceConsentGroup = "cidaas_consent_group.example" + description = "Test consent Description" +) + +var groupName = acctest.RandString(10) // create, read and update test func TestAccConsentGroupResource_Basic(t *testing.T) { - groupName := acctest.RandString(10) - description := "Test consent Description" updatedDescription := "Updated consent Description" resource.Test(t, resource.TestCase{ @@ -75,7 +78,7 @@ func testCheckConsentGroupDestroyed(s *terraform.State) error { consentGroup := cidaas.ConsentGroup{ ClientConfig: cidaas.ClientConfig{ BaseURL: os.Getenv("BASE_URL"), - AccessToken: acctest.TEST_TOKEN, + AccessToken: acctest.TestToken, }, } res, _ := consentGroup.Get(rs.Primary.ID) @@ -88,9 +91,7 @@ func testCheckConsentGroupDestroyed(s *terraform.State) error { // failed validation on updating immutable proprty group_name func TestAccConsentGroupResource_GoupNameUpdateFail(t *testing.T) { - groupName := acctest.RandString(10) updateGroupName := acctest.RandString(10) - description := "Test consent Description" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, @@ -112,15 +113,14 @@ func TestAccConsentGroupResource_GoupNameUpdateFail(t *testing.T) { // Empty group_name validation test func TestAccConsentGroupResource_EmptyGroupName(t *testing.T) { - groupName := "" - description := "Test consent Description" + emptyGroupName := "" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccConsentGroupResourceConfig(groupName, description), + Config: testAccConsentGroupResourceConfig(emptyGroupName, description), ExpectError: regexp.MustCompile(`Attribute group_name string length must be at least 1, got: 0`), }, }, diff --git a/internal/resources/resource_consent_test.go b/internal/resources/resource_consent_test.go index fad0ffe..2de960f 100644 --- a/internal/resources/resource_consent_test.go +++ b/internal/resources/resource_consent_test.go @@ -16,8 +16,10 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) -const resourceConsent = "cidaas_consent.example" -const refResourceConsentGroup = "cidaas_consent_group.example" +const ( + resourceConsent = "cidaas_consent.example" + refResourceConsentGroup = "cidaas_consent_group.example" +) // create, read and update test func TestAccConsentResource_Basic(t *testing.T) { @@ -102,7 +104,7 @@ func testCheckConsentDestroyed(s *terraform.State) error { consent := cidaas.ConsentClient{ ClientConfig: cidaas.ClientConfig{ BaseURL: os.Getenv("BASE_URL"), - AccessToken: acctest.TEST_TOKEN, + AccessToken: acctest.TestToken, }, } res, _ := consent.GetConsentInstances(rs.Primary.ID) diff --git a/internal/resources/resource_group_type_test.go b/internal/resources/resource_group_type_test.go index 1ec6af0..3970c1d 100644 --- a/internal/resources/resource_group_type_test.go +++ b/internal/resources/resource_group_type_test.go @@ -12,13 +12,16 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) -const resourceGroupType = "cidaas_group_type.example" +const ( + resourceGroupType = "cidaas_group_type.example" + groupTypedescription = "Test Group Type Description" +) + +var groupType = acctest.RandString(10) // create, read and update test func TestAccGroupTypeResource_Basic(t *testing.T) { - groupType := acctest.RandString(10) roleMode := "any_roles" - description := "Test Group Type Description" updatedDescription := "Updated Group Type Description" resource.Test(t, resource.TestCase{ @@ -27,11 +30,11 @@ func TestAccGroupTypeResource_Basic(t *testing.T) { CheckDestroy: testCheckGroupTypeDestroyed, Steps: []resource.TestStep{ { - Config: testAccGroupTypeResourceConfig(groupType, roleMode, description), + Config: testAccGroupTypeResourceConfig(groupType, roleMode, groupTypedescription), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceGroupType, "group_type", groupType), resource.TestCheckResourceAttr(resourceGroupType, "role_mode", roleMode), - resource.TestCheckResourceAttr(resourceGroupType, "description", description), + resource.TestCheckResourceAttr(resourceGroupType, "description", groupTypedescription), resource.TestCheckResourceAttrSet(resourceGroupType, "id"), resource.TestCheckResourceAttrSet(resourceGroupType, "created_at"), ), @@ -77,7 +80,7 @@ func testCheckGroupTypeDestroyed(s *terraform.State) error { groupType := cidaas.GroupType{ ClientConfig: cidaas.ClientConfig{ BaseURL: os.Getenv("BASE_URL"), - AccessToken: acctest.TEST_TOKEN, + AccessToken: acctest.TestToken, }, } res, _ := groupType.Get(rs.Primary.Attributes["group_type"]) @@ -91,16 +94,14 @@ func testCheckGroupTypeDestroyed(s *terraform.State) error { // validation test for role_mode func TestAccGroupTypeResource_InvalidRoleMode(t *testing.T) { - groupType := acctest.RandString(10) invalidRoleMode := "invalid_role_mode" - description := "Test Group Type Description" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccGroupTypeResourceConfig(groupType, invalidRoleMode, description), + Config: testAccGroupTypeResourceConfig(groupType, invalidRoleMode, groupTypedescription), ExpectError: regexp.MustCompile(`Attribute role_mode value must be one of:`), // TODO: full string comparison }, }, @@ -109,23 +110,21 @@ func TestAccGroupTypeResource_InvalidRoleMode(t *testing.T) { // group_type can't be modified func TestAccGroupTypeResource_UpdateFails(t *testing.T) { - groupType := acctest.RandString(10) updatedGroupType := acctest.RandString(10) roleMode := "any_roles" - description := "Test Group Type Description" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccGroupTypeResourceConfig(groupType, roleMode, description), + Config: testAccGroupTypeResourceConfig(groupType, roleMode, groupTypedescription), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceGroupType, "group_type", groupType), ), }, { - Config: testAccGroupTypeResourceConfig(updatedGroupType, roleMode, description), + Config: testAccGroupTypeResourceConfig(updatedGroupType, roleMode, groupTypedescription), ExpectError: regexp.MustCompile("Attribute 'group_type' can't be modified"), // TODO: full string comparison }, }, @@ -134,9 +133,7 @@ func TestAccGroupTypeResource_UpdateFails(t *testing.T) { // allowed_roles must have value when role_mode is allowed_roles or roles_required func TestAccGroupTypeResource_EmptyAllowedRolesError(t *testing.T) { - groupType := acctest.RandString(10) roleMode := "allowed_roles" - description := "Test Group Type Description" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, @@ -153,7 +150,7 @@ func TestAccGroupTypeResource_EmptyAllowedRolesError(t *testing.T) { description = "%s" allowed_roles = [] } - `, groupType, roleMode, description), + `, groupType, roleMode, groupTypedescription), ExpectError: regexp.MustCompile("The attribute allowed_roles cannot be empty when role_mode is set to"), // TODO: full string comparison }, }, diff --git a/internal/resources/resource_hosted_page_test.go b/internal/resources/resource_hosted_page_test.go index d9446f5..226f4ad 100644 --- a/internal/resources/resource_hosted_page_test.go +++ b/internal/resources/resource_hosted_page_test.go @@ -12,17 +12,17 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) -const resourceHostedPage = "cidaas_hosted_page.example" +const ( + resourceHostedPage = "cidaas_hosted_page.example" + hostedPageURL = "https://cidaad.de/register_success" + hostedPageID = "register_success" + hostedPageContent = "Register Success" + defaultLocale = "en-US" +) -// create, read and update test -func TestAccHostedPageResource_Basic(t *testing.T) { - hostedPageID := "register_success" - hostedPageURL := "https://cidaad.de/register_success" - updatedHostedPageURL := "https://cidaad.de/updated_register_success" - hostedPageContent := "Success" - hostedPageGroupName := "Test Hosted Page Group" - defaultLocale := "en-US" - hostedPages := []map[string]string{ +var ( + hostedPageGroupName = acctest.RandString(10) + hostedPages = []map[string]string{ { "hosted_page_id": hostedPageID, "locale": defaultLocale, @@ -30,6 +30,11 @@ func TestAccHostedPageResource_Basic(t *testing.T) { "content": hostedPageContent, }, } +) + +// create, read and update test +func TestAccHostedPageResource_Basic(t *testing.T) { + updatedHostedPageURL := "https://cidaad.de/updated_register_success" updatedHostedPages := []map[string]string{ { "hosted_page_id": hostedPageID, @@ -128,7 +133,7 @@ func testCheckHostedPageDestroyed(s *terraform.State) error { hp := cidaas.HostedPage{ ClientConfig: cidaas.ClientConfig{ BaseURL: os.Getenv("BASE_URL"), - AccessToken: acctest.TEST_TOKEN, + AccessToken: acctest.TestToken, }, } res, _ := hp.Get(rs.Primary.Attributes["hosted_page_group_name"]) @@ -142,16 +147,7 @@ func testCheckHostedPageDestroyed(s *terraform.State) error { // invalid locale func TestAccHostedPageResource_InvalidLocale(t *testing.T) { - hostedPageGroupName := "Test Hosted Page Group" invalidLocale := "invalid-locale" - hostedPages := []map[string]string{ - { - "hosted_page_id": "register_success", - "locale": "en-US", - "url": "https://cidaad.de/register_success", - "content": "Success", - }, - } resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, PreCheck: func() { acctest.TestAccPreCheck(t) }, @@ -231,20 +227,7 @@ func TestAccHostedPageResource_MissingRequiredFields(t *testing.T) { // Immutable attribute hosted_page_group_name validation func TestAccHostedPageResource_UniqueIdentifier(t *testing.T) { - hostedPageID := "register_success" - hostedPageURL := "https://cidaad.de/register_success" - hostedPageGroupName := "Unique Hosted Page Group" updatedHostedPageGroupName := "Updated Hosted Page Group" - defaultLocale := "en-US" - hostedPageContent := "Success" - hostedPages := []map[string]string{ - { - "hosted_page_id": hostedPageID, - "locale": defaultLocale, - "url": hostedPageURL, - "content": hostedPageContent, - }, - } resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, @@ -271,14 +254,10 @@ func TestAccHostedPageResource_UniqueIdentifier(t *testing.T) { // Invalid hosted_page_id func TestAccHostedPageResource_InvalidHostedPageID(t *testing.T) { - hostedPageID := "invalid" - hostedPageURL := "https://cidaad.de/register_success" - hostedPageGroupName := "Unique Hosted Page Group" - defaultLocale := "en-US" - hostedPageContent := "Success" + InvalidHostedPageID := "invalid" hostedPages := []map[string]string{ { - "hosted_page_id": hostedPageID, + "hosted_page_id": InvalidHostedPageID, "locale": defaultLocale, "url": hostedPageURL, "content": hostedPageContent, @@ -302,14 +281,9 @@ func TestAccHostedPageResource_InvalidHostedPageID(t *testing.T) { // validate multiple hosted pages func TestAccHostedPageResource_MultipleHostedPages(t *testing.T) { - hostedPageID1 := "register_success" - hostedPageURL1 := "https://cidaad.de/register_success" hostedPageID2 := "login_success" hostedPageURL2 := "https://cidaad.de/login_success" - hostedPageGroupName := "Test Hosted Page Group" - content1 := "Register Success" - content2 := "Login Success" - defaultLocale := "en-US" + hostedPageContent2 := "Login Success" config := ` provider "cidaas" { @@ -321,16 +295,16 @@ func TestAccHostedPageResource_MultipleHostedPages(t *testing.T) { hosted_pages =[ { - hosted_page_id = "` + hostedPageID1 + `" + hosted_page_id = "` + hostedPageID + `" locale = "` + defaultLocale + `" - url = "` + hostedPageURL1 + `" - content = "` + content1 + `" + url = "` + hostedPageURL + `" + content = "` + hostedPageContent + `" }, { hosted_page_id = "` + hostedPageID2 + `" locale = "en-IN" url = "` + hostedPageURL2 + `" - content = "` + content2 + `" + content = "` + hostedPageContent2 + `" } ] } @@ -342,12 +316,12 @@ func TestAccHostedPageResource_MultipleHostedPages(t *testing.T) { { Config: config, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.hosted_page_id", hostedPageID1), - resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.url", hostedPageURL1), - resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.content", content1), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.hosted_page_id", hostedPageID), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.url", hostedPageURL), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.0.content", hostedPageContent), resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.1.hosted_page_id", hostedPageID2), resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.1.url", hostedPageURL2), - resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.1.content", content2), + resource.TestCheckResourceAttr(resourceHostedPage, "hosted_pages.1.content", hostedPageContent2), ), }, }, diff --git a/internal/resources/resource_scope_group_test.go b/internal/resources/resource_scope_group_test.go index 5bd7d11..920799f 100644 --- a/internal/resources/resource_scope_group_test.go +++ b/internal/resources/resource_scope_group_test.go @@ -12,12 +12,15 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) -const resourceScopeGroup = "cidaas_scope_group.example" +const ( + resourceScopeGroup = "cidaas_scope_group.example" + scopeGroupdescription = "Test Scope Group Description" +) + +var scopeGroupName = acctest.RandString(10) // create, read and update test func TestAccScopeGroupResource_Basic(t *testing.T) { - groupName := acctest.RandString(10) - description := "Test Scope Group Description" updatedDescription := "Updated Scope Group Description" resource.Test(t, resource.TestCase{ @@ -26,10 +29,10 @@ func TestAccScopeGroupResource_Basic(t *testing.T) { CheckDestroy: testCheckScopeGroupDestroyed, Steps: []resource.TestStep{ { - Config: testAccScopeGroupResourceConfig(groupName, description), + Config: testAccScopeGroupResourceConfig(scopeGroupName, scopeGroupdescription), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceScopeGroup, "group_name", groupName), - resource.TestCheckResourceAttr(resourceScopeGroup, "description", description), + resource.TestCheckResourceAttr(resourceScopeGroup, "group_name", scopeGroupName), + resource.TestCheckResourceAttr(resourceScopeGroup, "description", scopeGroupdescription), resource.TestCheckResourceAttrSet(resourceScopeGroup, "id"), resource.TestCheckResourceAttrSet(resourceScopeGroup, "created_at"), resource.TestCheckResourceAttrSet(resourceScopeGroup, "updated_at"), @@ -37,14 +40,14 @@ func TestAccScopeGroupResource_Basic(t *testing.T) { }, { ResourceName: resourceScopeGroup, - ImportStateId: groupName, + ImportStateId: scopeGroupName, ImportState: true, ImportStateVerify: true, // TODO: remove ImportStateVerifyIgnore ImportStateVerifyIgnore: []string{"updated_at", "created_at"}, }, { - Config: testAccScopeGroupResourceConfig(groupName, updatedDescription), + Config: testAccScopeGroupResourceConfig(scopeGroupName, updatedDescription), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceScopeGroup, "description", updatedDescription), resource.TestCheckResourceAttrSet(resourceScopeGroup, "updated_at"), @@ -75,7 +78,7 @@ func testCheckScopeGroupDestroyed(s *terraform.State) error { scopeGroup := cidaas.ScopeGroup{ ClientConfig: cidaas.ClientConfig{ BaseURL: os.Getenv("BASE_URL"), - AccessToken: acctest.TEST_TOKEN, + AccessToken: acctest.TestToken, }, } res, _ := scopeGroup.Get(rs.Primary.Attributes["group_name"]) @@ -88,22 +91,20 @@ func testCheckScopeGroupDestroyed(s *terraform.State) error { // failed validation on updating immutable proprty group_name func TestAccScopeGroupResource_GoupNameUpdateFail(t *testing.T) { - groupName := acctest.RandString(10) updateGroupName := acctest.RandString(10) - description := "Test Scope Group Description" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccScopeGroupResourceConfig(groupName, description), + Config: testAccScopeGroupResourceConfig(scopeGroupName, scopeGroupdescription), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceScopeGroup, "group_name", groupName), + resource.TestCheckResourceAttr(resourceScopeGroup, "group_name", scopeGroupName), ), }, { - Config: testAccScopeGroupResourceConfig(updateGroupName, description), + Config: testAccScopeGroupResourceConfig(updateGroupName, scopeGroupdescription), ExpectError: regexp.MustCompile("Attribute 'group_name' can't be modified"), // TODO: full string comparison }, }, @@ -112,15 +113,14 @@ func TestAccScopeGroupResource_GoupNameUpdateFail(t *testing.T) { // Empty group_name validation test func TestAccScopeGroupResource_EmptyGroupName(t *testing.T) { - groupName := "" - description := "Test Scope Group Description" + emptyScopeGroupName := "" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccScopeGroupResourceConfig(groupName, description), + Config: testAccScopeGroupResourceConfig(emptyScopeGroupName, scopeGroupdescription), ExpectError: regexp.MustCompile(`Attribute group_name string length must be at least 1, got: 0`), }, }, diff --git a/internal/test/acctest.go b/internal/test/acctest.go index 42328f1..cd948b7 100644 --- a/internal/test/acctest.go +++ b/internal/test/acctest.go @@ -23,7 +23,7 @@ var TestAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServe "cidaas": providerserver.NewProtocol6WithError(provider.Cidaas("test")()), } -var TEST_TOKEN string +var TestToken string func TestAccPreCheck(t *testing.T) { // You can add code here to run prior to any test case execution, for example assertions @@ -39,8 +39,8 @@ func TestAccPreCheck(t *testing.T) { t.Fatal("TERRAFORM_PROVIDER_CIDAAS_CLIENT_SECRET must be set for acceptance tests") } - tokenUrl := fmt.Sprintf("%s/%s", os.Getenv("BASE_URL"), "token-srv/token") - httpClient := util.NewHTTPClient(tokenUrl, http.MethodPost) + tokenURL := fmt.Sprintf("%s/%s", os.Getenv("BASE_URL"), "token-srv/token") + httpClient := util.NewHTTPClient(tokenURL, http.MethodPost) payload := map[string]string{ "client_id": os.Getenv("TERRAFORM_PROVIDER_CIDAAS_CLIENT_ID"), "client_secret": os.Getenv("TERRAFORM_PROVIDER_CIDAAS_CLIENT_SECRET"), @@ -55,7 +55,7 @@ func TestAccPreCheck(t *testing.T) { if err = util.ProcessResponse(res, &response); err != nil { t.Fatalf("failed to generate access token %s", err.Error()) } - TEST_TOKEN = response.AccessToken + TestToken = response.AccessToken } // RandString generates a random string with the given length. From 35d094554b938c35255023c190e89b626772f9f6 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Tue, 27 Aug 2024 13:58:09 +0530 Subject: [PATCH 25/36] cidaas_scope acceptance test acceptance test added for cidaas_scope resource --- internal/resources/resource_scope_test.go | 292 ++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 internal/resources/resource_scope_test.go diff --git a/internal/resources/resource_scope_test.go b/internal/resources/resource_scope_test.go new file mode 100644 index 0000000..54b8cb7 --- /dev/null +++ b/internal/resources/resource_scope_test.go @@ -0,0 +1,292 @@ +package resources_test + +import ( + "fmt" + "os" + "regexp" + "strconv" + "strings" + "testing" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const ( + resourceScope = "cidaas_scope.example" + scopeSecurityLevel = "CONFIDENTIAL" + requiredUserConsent = false + title = "scope title in German" + locale = "de-DE" + scopeDescription = "The description of the scope in German" +) + +var ( + scopeKey = acctest.RandString(10) + defaultScopeGroupName = []string{"developer"} + localizedDescriptions = []map[string]string{ + { + "title": title, + "locale": locale, + "description": scopeDescription, + }, + } +) + +// TODO: empty groupNameString test fails as it returns a plan to +// update even though there is no change to update + +// create, read and update test +func TestAccScopeResource_Basic(t *testing.T) { + updatedScopeDescription := "Updated description of the scope in German" + updatedRequiredUserConsent := true + localizedDescriptions = []map[string]string{ + { + "title": title, + "locale": locale, + // description is updated to validate update operation + "description": updatedScopeDescription, + }, + } + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: testCheckScopeDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccScopeResourceConfig(scopeSecurityLevel, scopeKey, requiredUserConsent, defaultScopeGroupName, localizedDescriptions), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceScope, "security_level", scopeSecurityLevel), + resource.TestCheckResourceAttr(resourceScope, "scope_key", scopeKey), + resource.TestCheckResourceAttr(resourceScope, "required_user_consent", strconv.FormatBool(requiredUserConsent)), + resource.TestCheckResourceAttr(resourceScope, "group_name.0", "developer"), + resource.TestCheckResourceAttrSet(resourceScope, "id"), + resource.TestCheckResourceAttrSet(resourceScope, "scope_owner"), + ), + }, + { + ResourceName: resourceScope, + ImportStateId: scopeKey, + ImportState: true, + ImportStateVerify: true, + }, + { + // required_user_consent & description in localized_descriptions updated + Config: testAccScopeResourceConfig( + scopeSecurityLevel, + scopeKey, + updatedRequiredUserConsent, + defaultScopeGroupName, + localizedDescriptions, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceScope, "required_user_consent", strconv.FormatBool(updatedRequiredUserConsent)), + resource.TestCheckResourceAttr(resourceScope, "localized_descriptions.0.description", updatedScopeDescription), + ), + }, + }, + }) +} + +func testAccScopeResourceConfig( + securityLevel, scopeKey string, + requiredUserConsent bool, + groupName []string, + localizedDescriptions []map[string]string, +) string { + groupNameString := "[]" + if len(groupName) > 0 { + groupNameString = `["` + strings.Join(groupName, `", "`) + `"]` + } + + return ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_scope" "example" { + security_level = "` + securityLevel + `" + scope_key = "` + scopeKey + `" + required_user_consent = "` + strconv.FormatBool(requiredUserConsent) + `" + group_name = ` + groupNameString + ` + localized_descriptions =[ + { + title = "` + localizedDescriptions[0]["title"] + `" + locale = "` + localizedDescriptions[0]["locale"] + `" + description = "` + localizedDescriptions[0]["description"] + `" + } + ] + } + ` +} + +func testCheckScopeDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceScope] + if !ok { + return fmt.Errorf("resource %s not fround", resourceScope) + } + + scope := cidaas.ScopeImpl{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TestToken, + }, + } + res, _ := scope.Get(rs.Primary.Attributes["scope_key"]) + if res != nil { + // when resource exists in remote + return fmt.Errorf("resource stil exists %+v", res) + } + return nil +} + +// failed validation on updating immutable proprty scope_key +func TestAccScopeResource_ImmutableScopeKeyUpdateFail(t *testing.T) { + updatedScopeKey := acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccScopeResourceConfig(scopeSecurityLevel, scopeKey, requiredUserConsent, defaultScopeGroupName, localizedDescriptions), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceScope, "scope_key", scopeKey), + ), + }, + { + Config: testAccScopeResourceConfig(scopeSecurityLevel, updatedScopeKey, requiredUserConsent, defaultScopeGroupName, localizedDescriptions), + ExpectError: regexp.MustCompile(`Attribute 'scope_key' can't be modified.`), + }, + }, + }) +} + +// Invalid security_level validation +func TestAccScopeResource_InvalidSecurityLevel(t *testing.T) { + invalidSecurityLevel := "INVALID" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccScopeResourceConfig(invalidSecurityLevel, scopeKey, requiredUserConsent, defaultScopeGroupName, localizedDescriptions), + ExpectError: regexp.MustCompile(`Attribute security_level value must be one of: \["PUBLIC" "CONFIDENTIAL"\]`), + }, + }, + }) +} + +// missing required parameter scope_key +func TestAccScopeResource_MissingRequired(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_scope" "example" { + } + `, + ExpectError: regexp.MustCompile(`The argument "scope_key" is required, but no definition was found.`), + }, + }, + }) +} + +// check default required_user_consent is false +func TestAccScopeResource_DefaultRequiredConsent(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_scope" "example" { + security_level = "PUBLIC" + scope_key = "` + scopeKey + `" + group_name = ["developer"] + localized_descriptions =[ + { + title = "` + title + `" + locale = "` + locale + `" + description = "` + scopeDescription + `" + } + ] + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceScope, "required_user_consent"), + resource.TestCheckResourceAttr(resourceScope, "required_user_consent", strconv.FormatBool(false)), + ), + }, + }, + }) +} + +// localized_descriptions[i].title is required +func TestAccScopeResource_TitleRequired(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_scope" "example" { + security_level = "PUBLIC" + scope_key = "` + scopeKey + `" + group_name = ["developer"] + localized_descriptions =[ + { + locale = "` + locale + `" + description = "` + scopeDescription + `" + } + ] + } + `, + ExpectError: regexp.MustCompile(`attribute "title" is required`), + }, + }, + }) +} + +// Invalid locale validation +func TestAccScopeResource_InvalidLocale(t *testing.T) { + invalidLocale := "ab" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_scope" "example" { + security_level = "PUBLIC" + scope_key = "` + scopeKey + `" + group_name = ["developer"] + localized_descriptions =[ + { + title = "` + title + `" + locale = "` + invalidLocale + `" + description = "` + scopeDescription + `" + } + ] + } + `, + ExpectError: regexp.MustCompile(`locale value must be one of`), // TODO:full error string comparison + }, + }, + }) +} From f7c24e094b548888a8418b11e64b00a15306f0e7 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Tue, 27 Aug 2024 13:59:47 +0530 Subject: [PATCH 26/36] spelling correction corrected spelling from 'exits' to 'exists' --- internal/resources/resource_consent_group_test.go | 2 +- internal/resources/resource_consent_test.go | 2 +- internal/resources/resource_group_type_test.go | 2 +- internal/resources/resource_hosted_page_test.go | 2 +- internal/resources/resource_scope_group_test.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/resources/resource_consent_group_test.go b/internal/resources/resource_consent_group_test.go index eb0e477..7ec0492 100644 --- a/internal/resources/resource_consent_group_test.go +++ b/internal/resources/resource_consent_group_test.go @@ -83,7 +83,7 @@ func testCheckConsentGroupDestroyed(s *terraform.State) error { } res, _ := consentGroup.Get(rs.Primary.ID) if res != nil { - // when resource exits in remote + // when resource exists in remote return fmt.Errorf("resource stil exists %+v", res) } return nil diff --git a/internal/resources/resource_consent_test.go b/internal/resources/resource_consent_test.go index 2de960f..44ffd13 100644 --- a/internal/resources/resource_consent_test.go +++ b/internal/resources/resource_consent_test.go @@ -109,7 +109,7 @@ func testCheckConsentDestroyed(s *terraform.State) error { } res, _ := consent.GetConsentInstances(rs.Primary.ID) if res != nil && res.Status != http.StatusNoContent && len(res.Data) > 0 { - // when resource exits in remote + // when resource exists in remote return fmt.Errorf("resource stil exists %+v", res) } return nil diff --git a/internal/resources/resource_group_type_test.go b/internal/resources/resource_group_type_test.go index 3970c1d..76323c9 100644 --- a/internal/resources/resource_group_type_test.go +++ b/internal/resources/resource_group_type_test.go @@ -86,7 +86,7 @@ func testCheckGroupTypeDestroyed(s *terraform.State) error { res, _ := groupType.Get(rs.Primary.Attributes["group_type"]) if res != nil { - // when resource exits in remote + // when resource exists in remote return fmt.Errorf("resource %s stil exists", res.Data.GroupType) } return nil diff --git a/internal/resources/resource_hosted_page_test.go b/internal/resources/resource_hosted_page_test.go index 226f4ad..c1691a3 100644 --- a/internal/resources/resource_hosted_page_test.go +++ b/internal/resources/resource_hosted_page_test.go @@ -139,7 +139,7 @@ func testCheckHostedPageDestroyed(s *terraform.State) error { res, _ := hp.Get(rs.Primary.Attributes["hosted_page_group_name"]) if res != nil { - // when resource exits in remote + // when resource exists in remote return fmt.Errorf("resource %s stil exists", res.Data) } return nil diff --git a/internal/resources/resource_scope_group_test.go b/internal/resources/resource_scope_group_test.go index 920799f..64bbfd8 100644 --- a/internal/resources/resource_scope_group_test.go +++ b/internal/resources/resource_scope_group_test.go @@ -83,7 +83,7 @@ func testCheckScopeGroupDestroyed(s *terraform.State) error { } res, _ := scopeGroup.Get(rs.Primary.Attributes["group_name"]) if res.Data.ID != "" { - // when resource exits in remote + // when resource exists in remote return fmt.Errorf("resource stil exists %+v", res) } return nil From b76ad67ccf970faab8283ede2bc0f0ebdfb0e83d Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Wed, 28 Aug 2024 14:43:18 +0530 Subject: [PATCH 27/36] webhook acceptance test acceptance test added for the resource cidaas_webhook --- internal/resources/resource_webhook_test.go | 144 ++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 internal/resources/resource_webhook_test.go diff --git a/internal/resources/resource_webhook_test.go b/internal/resources/resource_webhook_test.go new file mode 100644 index 0000000..6ba0dbe --- /dev/null +++ b/internal/resources/resource_webhook_test.go @@ -0,0 +1,144 @@ +package resources_test + +import ( + "fmt" + "os" + "regexp" + "strings" + "testing" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const ( + resourceWebhook = "cidaas_webhook.example" + authType = "APIKEY" + url = "https://cidaas.de/webhook-srv/webhook" + apiConfigKey = "api-key" + apiPlaceholder = "key" + apiPlacement = "query" +) + +var ( + events = []string{"ACCOUNT_MODIFIED"} + apikey_config = map[string]string{ + "key": apiConfigKey, + "placeholder": apiPlaceholder, + "placement": apiPlacement, + } +) + +// test scenarios +// apikey_config.placeholder must contain only lowercase alphabets + +// create, read and update test +func TestAccWebhookResource_Basic(t *testing.T) { + updatedUrl := "https://cidaas.de/webhook-srv/v2/webhook" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: testCheckWebhookDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccWebhookResourceConfig(authType, url, events, apikey_config), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceWebhook, "auth_type", authType), + resource.TestCheckResourceAttr(resourceWebhook, "url", url), + resource.TestCheckResourceAttr(resourceWebhook, "events.0", "ACCOUNT_MODIFIED"), + resource.TestCheckResourceAttrSet(resourceWebhook, "id"), + resource.TestCheckResourceAttrSet(resourceWebhook, "disable"), + resource.TestCheckResourceAttrSet(resourceWebhook, "created_at"), + resource.TestCheckResourceAttrSet(resourceWebhook, "updated_at"), + ), + }, + { + ResourceName: resourceWebhook, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"created_at", "updated_at"}, + }, + { + // url updated + Config: testAccWebhookResourceConfig(authType, updatedUrl, events, apikey_config), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceWebhook, "url", updatedUrl), + ), + }, + }, + }) +} + +func testAccWebhookResourceConfig( + authType, url string, + events []string, + apikeyConfig map[string]string, +) string { + eventsString := "[]" + if len(events) > 0 { + eventsString = `["` + strings.Join(events, `", "`) + `"]` + } + + return ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_webhook" "example" { + auth_type = "` + authType + `" + url = "` + url + `" + events = ` + eventsString + ` + apikey_config = { + key = "` + apikeyConfig["key"] + `" + placeholder = "` + apikeyConfig["placeholder"] + `" + placement = "` + apikeyConfig["placement"] + `" + } + } + ` +} + +func testCheckWebhookDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceWebhook] + if !ok { + return fmt.Errorf("resource %s not fround", resourceWebhook) + } + + wb := cidaas.Webhook{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TestToken, + }, + } + res, _ := wb.Get(rs.Primary.Attributes["id"]) + if res != nil { + // when resource exists in remote + return fmt.Errorf("resource stil exists %+v", res) + } + return nil +} + +// Invalid auth_type validation +func TestAccWebhookResource_InvalidAllowedValue(t *testing.T) { + invalidAuthType := "INVALID" + invalidEvents := []string{"INVALID"} + apikey_config["placement"] = "body" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccWebhookResourceConfig(invalidAuthType, url, events, apikey_config), + ExpectError: regexp.MustCompile(`Attribute auth_type value must be one of: \["APIKEY" "TOTP" "CIDAAS_OAUTH2"\]`), + }, + { + Config: testAccWebhookResourceConfig(invalidAuthType, url, invalidEvents, apikey_config), + ExpectError: regexp.MustCompile(`value must be one of`), // TODO: full erro msg match + }, + { + Config: testAccWebhookResourceConfig(authType, url, events, apikey_config), + ExpectError: regexp.MustCompile(`placement value must be one of: \["query" "header"\]`), + }, + }, + }) +} From 40232f25b49ece78b0f75898d07a08f30cdaefbd Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Thu, 29 Aug 2024 17:53:18 +0530 Subject: [PATCH 28/36] acceptance test webhook additional acceptance tests added to the resource cidaas_webhook --- internal/resources/resource_webhook_test.go | 144 +++++++++++++++++--- 1 file changed, 127 insertions(+), 17 deletions(-) diff --git a/internal/resources/resource_webhook_test.go b/internal/resources/resource_webhook_test.go index 6ba0dbe..9f3d9f6 100644 --- a/internal/resources/resource_webhook_test.go +++ b/internal/resources/resource_webhook_test.go @@ -15,37 +15,36 @@ import ( const ( resourceWebhook = "cidaas_webhook.example" - authType = "APIKEY" + apiKey = "APIKEY" url = "https://cidaas.de/webhook-srv/webhook" apiConfigKey = "api-key" apiPlaceholder = "key" apiPlacement = "query" + totp = "TOTP" + oauth2 = "CIDAAS_OAUTH2" ) var ( - events = []string{"ACCOUNT_MODIFIED"} - apikey_config = map[string]string{ + events = []string{"ACCOUNT_MODIFIED"} + apikeyConfig = map[string]string{ "key": apiConfigKey, "placeholder": apiPlaceholder, "placement": apiPlacement, } ) -// test scenarios -// apikey_config.placeholder must contain only lowercase alphabets - // create, read and update test func TestAccWebhookResource_Basic(t *testing.T) { - updatedUrl := "https://cidaas.de/webhook-srv/v2/webhook" + updatedURL := "https://cidaas.de/webhook-srv/v2/webhook" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, CheckDestroy: testCheckWebhookDestroyed, Steps: []resource.TestStep{ { - Config: testAccWebhookResourceConfig(authType, url, events, apikey_config), + Config: testAccWebhookResourceConfig(apiKey, url, events, apikeyConfig), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceWebhook, "auth_type", authType), + resource.TestCheckResourceAttr(resourceWebhook, "auth_type", apiKey), resource.TestCheckResourceAttr(resourceWebhook, "url", url), resource.TestCheckResourceAttr(resourceWebhook, "events.0", "ACCOUNT_MODIFIED"), resource.TestCheckResourceAttrSet(resourceWebhook, "id"), @@ -62,9 +61,9 @@ func TestAccWebhookResource_Basic(t *testing.T) { }, { // url updated - Config: testAccWebhookResourceConfig(authType, updatedUrl, events, apikey_config), + Config: testAccWebhookResourceConfig(apiKey, updatedURL, events, apikeyConfig), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceWebhook, "url", updatedUrl), + resource.TestCheckResourceAttr(resourceWebhook, "url", updatedURL), ), }, }, @@ -118,27 +117,138 @@ func testCheckWebhookDestroyed(s *terraform.State) error { return nil } -// Invalid auth_type validation +// Invalid auth_type, events and apikey_config placement validation func TestAccWebhookResource_InvalidAllowedValue(t *testing.T) { invalidAuthType := "INVALID" invalidEvents := []string{"INVALID"} - apikey_config["placement"] = "body" + apikeyConfig["placement"] = "body" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccWebhookResourceConfig(invalidAuthType, url, events, apikey_config), + Config: testAccWebhookResourceConfig(invalidAuthType, url, events, apikeyConfig), ExpectError: regexp.MustCompile(`Attribute auth_type value must be one of: \["APIKEY" "TOTP" "CIDAAS_OAUTH2"\]`), }, { - Config: testAccWebhookResourceConfig(invalidAuthType, url, invalidEvents, apikey_config), - ExpectError: regexp.MustCompile(`value must be one of`), // TODO: full erro msg match + Config: testAccWebhookResourceConfig(apiKey, url, invalidEvents, apikeyConfig), + ExpectError: regexp.MustCompile(`value must be one of`), // TODO: full error msg match }, { - Config: testAccWebhookResourceConfig(authType, url, events, apikey_config), + Config: testAccWebhookResourceConfig(apiKey, url, events, apikeyConfig), ExpectError: regexp.MustCompile(`placement value must be one of: \["query" "header"\]`), }, }, }) } + +// apikey_config.placeholder must contain only lowercase alphabets +func TestAccWebhookResource_PlaceholderLowercase(t *testing.T) { + invalidPlaceholders := []string{"apiKey", "APIKEY", "api-key", "api_KEY"} + for _, v := range invalidPlaceholders { + apikeyConfig["placeholder"] = v + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccWebhookResourceConfig(apiKey, url, events, apikeyConfig), + ExpectError: regexp.MustCompile(`Attribute apikey_config.placeholder must contain only lowercase alphabets`), + }, + }, + }) + } +} + +// invalid auth_type and related config(apikey_config, totp_config & cidaas_auth_config) combination +func TestAccWebhookResource_InvalidAuthType(t *testing.T) { + // apikey_config is reverted to the correct old value after it was updated to invalid ones in previous tests + apikeyConfig["placement"] = "query" + apikeyConfig["placeholder"] = "key" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccWebhookResourceConfig(totp, url, events, apikeyConfig), + ExpectError: regexp.MustCompile(`The attribute totp_config cannot be empty when the auth_type is TOTP`), + }, + { + Config: testAccWebhookResourceConfig(oauth2, url, events, apikeyConfig), + ExpectError: regexp.MustCompile(`The attribute cidaas_auth_config cannot be empty when the auth_type is`), // TODO: fix why full string match not working + }, + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_webhook" "example" { + auth_type = "APIKEY" + url = "https://cidaas.de/webhook-srv/webhook" + events = ["ACCOUNT_MODIFIED"] + totp_config = { + key = "api-key" + placeholder = "key" + placement = "query" + } + } + `, + ExpectError: regexp.MustCompile(`The attribute apikey_config cannot be empty when the auth_type is APIKEY`), + }, + }, + }) +} + +// create webhook with all 3 auth_type configurations and switch between them +func TestAccWebhookResource_SwitchAuthType(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: webhookResouceFullConfig(apiKey), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceWebhook, "auth_type", apiKey), + ), + }, + { + Config: webhookResouceFullConfig(totp), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceWebhook, "auth_type", totp), + ), + }, + { + Config: webhookResouceFullConfig(oauth2), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceWebhook, "auth_type", oauth2), + ), + }, + }, + }) +} + +func webhookResouceFullConfig(authType string) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_webhook" "example" { + auth_type = "%s" + url = "https://cidaas.de/webhook-srv/webhook" + events = ["ACCOUNT_MODIFIED"] + apikey_config = { + key = "api-key" + placeholder = "key" + placement = "query" + } + totp_config = { + key = "totp-key" + placeholder = "key" + placement = "header" + } + cidaas_auth_config = { + client_id = "ce90d6ba-9a5a-49b6-9a50" + } + }`, authType) +} From 0a6b46b01df34b8f4d7718f8ef01bc148fa4a36a Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Fri, 30 Aug 2024 11:02:41 +0530 Subject: [PATCH 29/36] custom_provider acceptance test acceptance test added for the resource cidaas_custom_provider --- .../resource_custom_provider_test.go | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 internal/resources/resource_custom_provider_test.go diff --git a/internal/resources/resource_custom_provider_test.go b/internal/resources/resource_custom_provider_test.go new file mode 100644 index 0000000..4ab2522 --- /dev/null +++ b/internal/resources/resource_custom_provider_test.go @@ -0,0 +1,294 @@ +package resources_test + +import ( + "fmt" + "os" + "regexp" + "strconv" + "testing" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const ( + resourceCustomProvider = "cidaas_custom_provider.example" + oauth2StandardType = "OAUTH2" + providerName = "terraform_sample" + displayName = "Sample Terraform" + authorizationEndpoint = "https://cidaas.de/authz-srv/authz" + tokenEndpoint = "https://cidaas.de/token-srv/token" //nolint:gosec + logoURL = "https://cidaas.de/logo" + userinfoEndpoint = "https://cidaas.de/users-srv/userinfo" + scopeDisplayLabel = "terraform sample scope display name" +) + +var ( + clientID = acctest.RandString(10) + clientSecret = acctest.RandString(10) +) + +// create, read and update test +func TestAccCustomProviderResource_Basic(t *testing.T) { + updatedDisplayName := "Updated Sample Terraform" + updatedOauth2StandardType := "OPENID_CONNECT" + updatedAuthorizationEndpoint := "https://cidaas.de/authz-srv/v2/authz" + updatedTokenEndpoint := "https://cidaas.de/token-srv/v2/token" //nolint:gosec + updatedLogoURL := "https://cidaas.de/v2/logo" + updatedUserinfoEndpoint := "https://cidaas.de/users-srv/v2/userinfo" + updatedScopeDisplayLabel := "updated terraform sample scope display name" + updatedClientID := acctest.RandString(10) + updatedClientSecret := acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: checkCustomProviderDestroyed, + Steps: []resource.TestStep{ + { + Config: resourceCustomProviderConfig(oauth2StandardType, providerName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceCustomProvider, "standard_type", oauth2StandardType), + resource.TestCheckResourceAttr(resourceCustomProvider, "authorization_endpoint", authorizationEndpoint), + resource.TestCheckResourceAttr(resourceCustomProvider, "token_endpoint", tokenEndpoint), + resource.TestCheckResourceAttr(resourceCustomProvider, "provider_name", providerName), + resource.TestCheckResourceAttr(resourceCustomProvider, "display_name", displayName), + resource.TestCheckResourceAttr(resourceCustomProvider, "logo_url", logoURL), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_endpoint", userinfoEndpoint), + resource.TestCheckResourceAttr(resourceCustomProvider, "scope_display_label", scopeDisplayLabel), + resource.TestCheckResourceAttr(resourceCustomProvider, "client_id", clientID), + resource.TestCheckResourceAttr(resourceCustomProvider, "client_secret", clientSecret), + resource.TestCheckResourceAttr(resourceCustomProvider, "domains.0", "cidaas.de"), + resource.TestCheckResourceAttr(resourceCustomProvider, "domains.1", "cidaas.org"), + resource.TestCheckResourceAttr(resourceCustomProvider, "scopes.0.recommended", strconv.FormatBool(true)), + resource.TestCheckResourceAttr(resourceCustomProvider, "scopes.0.required", strconv.FormatBool(true)), + resource.TestCheckResourceAttr(resourceCustomProvider, "scopes.0.scope_name", "email"), + resource.TestCheckResourceAttrSet(resourceCustomProvider, "id"), + ), + }, + { + ResourceName: resourceCustomProvider, + ImportState: true, + ImportStateVerify: true, + ImportStateId: providerName, + }, + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_custom_provider" "example" { + standard_type = "` + updatedOauth2StandardType + `" + authorization_endpoint = "` + updatedAuthorizationEndpoint + `" + token_endpoint = "` + updatedTokenEndpoint + `" + provider_name = "` + providerName + `" + display_name = "` + updatedDisplayName + `" + logo_url = "` + updatedLogoURL + `" + userinfo_endpoint = "` + updatedUserinfoEndpoint + `" + scope_display_label = "` + updatedScopeDisplayLabel + `" + client_id = "` + updatedClientID + `" + client_secret = "` + updatedClientSecret + `" + domains = ["cidaas.in", "cidaas.com"] + + scopes = [ + { + scope_name = "openid" + } + ] + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceCustomProvider, "standard_type", updatedOauth2StandardType), + resource.TestCheckResourceAttr(resourceCustomProvider, "authorization_endpoint", updatedAuthorizationEndpoint), + resource.TestCheckResourceAttr(resourceCustomProvider, "token_endpoint", updatedTokenEndpoint), + resource.TestCheckResourceAttr(resourceCustomProvider, "provider_name", providerName), + resource.TestCheckResourceAttr(resourceCustomProvider, "display_name", updatedDisplayName), + resource.TestCheckResourceAttr(resourceCustomProvider, "logo_url", updatedLogoURL), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_endpoint", updatedUserinfoEndpoint), + resource.TestCheckResourceAttr(resourceCustomProvider, "scope_display_label", updatedScopeDisplayLabel), + resource.TestCheckResourceAttr(resourceCustomProvider, "client_id", updatedClientID), + resource.TestCheckResourceAttr(resourceCustomProvider, "client_secret", updatedClientSecret), + resource.TestCheckResourceAttr(resourceCustomProvider, "domains.0", "cidaas.com"), + resource.TestCheckResourceAttr(resourceCustomProvider, "domains.1", "cidaas.in"), + resource.TestCheckResourceAttr(resourceCustomProvider, "scopes.0.scope_name", "openid"), + // default value check scopes[i].recommended & scopes[i].required + resource.TestCheckResourceAttr(resourceCustomProvider, "scopes.0.recommended", strconv.FormatBool(false)), + resource.TestCheckResourceAttr(resourceCustomProvider, "scopes.0.required", strconv.FormatBool(false)), + resource.TestCheckResourceAttrSet(resourceCustomProvider, "id"), + ), + }, + { + // provider_name cannot be updated + Config: resourceCustomProviderConfig(oauth2StandardType, "new_provider_name"), + ExpectError: regexp.MustCompile(`Attribute 'provider_name' can't be modified`), + }, + }, + }) +} + +func resourceCustomProviderConfig(standardType, providerName string) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_custom_provider" "example" { + standard_type = "%s" + authorization_endpoint = "`+authorizationEndpoint+`" + token_endpoint = "`+tokenEndpoint+`" + provider_name = "%s" + display_name = "`+displayName+`" + logo_url = "`+logoURL+`" + userinfo_endpoint = "`+userinfoEndpoint+`" + scope_display_label = "`+scopeDisplayLabel+`" + client_id = "`+clientID+`" + client_secret = "`+clientSecret+`" + domains = ["cidaas.de", "cidaas.org"] + + scopes = [ + { + recommended = true + required = true + scope_name = "email" + } + ] + } + `, standardType, providerName) +} + +func checkCustomProviderDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceCustomProvider] + if !ok { + return fmt.Errorf("resource %s not fround", resourceCustomProvider) + } + + cp := cidaas.CustomProvider{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TestToken, + }, + } + res, _ := cp.GetCustomProvider(rs.Primary.Attributes["provider_name"]) + if res != nil { + // when resource exists in remote + return fmt.Errorf("resource stil exists %+v", res) + } + return nil +} + +// Invalid standard_type validation +func TestAccCustomProviderResource_InvalidStandardType(t *testing.T) { + invalidStandardType := "OAUTH1" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: resourceCustomProviderConfig(invalidStandardType, providerName), + ExpectError: regexp.MustCompile(`Attribute standard_type value must be one of: \["OPENID_CONNECT" "OAUTH2"\]`), + }, + }, + }) +} + +// missing required parameter +func TestAccCustomProviderResource_MissingRequired(t *testing.T) { + requiredParams := []string{ + "provider_name", "display_name", "client_id", "client_secret", + "authorization_endpoint", "token_endpoint", "userinfo_endpoint", "scope_display_label", + } + for _, param := range requiredParams { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_custom_provider" "example" {} + `, + ExpectError: regexp.MustCompile(fmt.Sprintf(`The argument "%s" is required`, param)), + }, + }, + }) + } +} + +// check userinfo_fields parameters +func TestAccCustomProviderResource_UserinfoFieldsCheck(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_custom_provider" "example" { + standard_type = "` + oauth2StandardType + `" + authorization_endpoint = "` + authorizationEndpoint + `" + token_endpoint = "` + tokenEndpoint + `" + provider_name = "` + providerName + `" + display_name = "` + displayName + `" + userinfo_endpoint = "` + userinfoEndpoint + `" + scope_display_label = "` + scopeDisplayLabel + `" + client_id = "` + clientID + `" + client_secret = "` + clientSecret + `" + scopes = [ + { + scope_name = "email" + } + ] + userinfo_fields = { + family_name = "cp_family_name" + address = "cp_address" + birthdate = "01-01-2000" + email = "cp@cidaas.de" + email_verified = "true" + gender = "male" + given_name = "cp_given_name" + locale = "cp_locale" + middle_name = "cp_middle_name" + mobile_number = "100000000" + phone_number = "10000000" + picture = "https://cidaas.de/image.jpg" + preferred_username = "cp_preferred_username" + profile = "cp_profile" + updated_at = "01-01-01" + website = "https://cidaas.de" + zoneinfo = "cp_zone_info" + custom_fields = { + zipcode = "123456" + alternate_phone = "1234567890" + } + } + }`, + Check: resource.ComposeAggregateTestCheckFunc( + // default value check + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.family_name", "cp_family_name"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.address", "cp_address"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.birthdate", "01-01-2000"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.email", "cp@cidaas.de"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.email_verified", "true"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.gender", "male"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.given_name", "cp_given_name"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.locale", "cp_locale"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.middle_name", "cp_middle_name"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.mobile_number", "100000000"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.phone_number", "10000000"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.picture", "https://cidaas.de/image.jpg"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.preferred_username", "cp_preferred_username"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.profile", "cp_profile"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.updated_at", "01-01-01"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.website", "https://cidaas.de"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.zoneinfo", "cp_zone_info"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.custom_fields.zipcode", "123456"), + resource.TestCheckResourceAttr(resourceCustomProvider, "userinfo_fields.custom_fields.alternate_phone", "1234567890"), + ), + }, + }, + }) +} From 45178952f34f8e270e9923555d1629b3fc8225f8 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Mon, 2 Sep 2024 15:25:35 +0530 Subject: [PATCH 30/36] user_group acceptance test acceptance test added for the resource cidaas_user_group --- internal/resources/resource_group_type.go | 1 + .../resources/resource_user_group_test.go | 198 ++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 internal/resources/resource_user_group_test.go diff --git a/internal/resources/resource_group_type.go b/internal/resources/resource_group_type.go index 743a4af..f317f52 100644 --- a/internal/resources/resource_group_type.go +++ b/internal/resources/resource_group_type.go @@ -80,6 +80,7 @@ func (r *GroupTypeResource) Schema(_ context.Context, _ resource.SchemaRequest, stringvalidator.OneOf([]string{"any_roles", "no_roles", "roles_required", "allowed_roles"}...), }, }, + // TODO: description is a required parameter in admin ui "description": schema.StringAttribute{ Optional: true, MarkdownDescription: "The `description` attribute provides details about the group type, explaining its purpose.", diff --git a/internal/resources/resource_user_group_test.go b/internal/resources/resource_user_group_test.go new file mode 100644 index 0000000..937d988 --- /dev/null +++ b/internal/resources/resource_user_group_test.go @@ -0,0 +1,198 @@ +package resources_test + +import ( + "fmt" + "os" + "regexp" + "strconv" + "testing" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const ( + resourceUserGroup = "cidaas_user_groups.example" +) + +var ( + userGroupType = acctest.RandString(10) + groupID = acctest.RandString(10) + userGroupName = acctest.RandString(10) +) + +// create, read and update test +func TestAccUserGroupResource_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: testCheckUserGroupDestroyed, + Steps: []resource.TestStep{ + { + Config: testAccUserGroupResourceConfig(userGroupType, groupID), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceUserGroup, "group_type", resourceGroupType, "group_type"), + resource.TestCheckResourceAttr(resourceUserGroup, "group_id", groupID), + resource.TestCheckResourceAttr(resourceUserGroup, "group_name", userGroupName), + resource.TestCheckResourceAttr(resourceUserGroup, "logo_url", "https://cidaas.de/logo"), + resource.TestCheckResourceAttr(resourceUserGroup, "description", "sample user groups description"), + resource.TestCheckResourceAttr(resourceUserGroup, "custom_fields.first_name", "cidaas"), + resource.TestCheckResourceAttr(resourceUserGroup, "custom_fields.family_name", "widas"), + // default value check + resource.TestCheckResourceAttr(resourceUserGroup, "make_first_user_admin", strconv.FormatBool(true)), + resource.TestCheckResourceAttr(resourceUserGroup, "member_profile_visibility", "full"), + resource.TestCheckResourceAttr(resourceUserGroup, "none_member_profile_visibility", "public"), + resource.TestCheckResourceAttr(resourceUserGroup, "parent_id", "root"), + // computed properties check + resource.TestCheckResourceAttrSet(resourceUserGroup, "id"), + resource.TestCheckResourceAttrSet(resourceUserGroup, "created_at"), + resource.TestCheckResourceAttrSet(resourceUserGroup, "updated_at"), + ), + }, + { + ResourceName: resourceUserGroup, + ImportState: true, + ImportStateVerify: true, + ImportStateId: groupID, + ImportStateVerifyIgnore: []string{"created_at", "updated_at"}, + }, + { + Config: testAccUserGroupResourceConfig(userGroupType, acctest.RandString(10)), + ExpectError: regexp.MustCompile("Attribute 'group_id' can't be modified"), + }, + // update works, but with update the order of deletion of both the resources changes group_type first and user groups next + // and we get conflict. so have these lines commented. + // TODO: check if the order of destruction can be controlled + // { + // Config: updatedUserGroupConfig(newRandomStr), + // Check: resource.ComposeAggregateTestCheckFunc( + // resource.TestCheckResourceAttr(resourceUserGroup, "group_type", userGroupType), + // resource.TestCheckResourceAttr(resourceUserGroup, "group_name", newRandomStr), + // resource.TestCheckResourceAttr(resourceUserGroup, "logo_url", "https://cidaas.de/v2/logo"), + // resource.TestCheckResourceAttr(resourceUserGroup, "description", "updated sample user groups description"), + // resource.TestCheckResourceAttr(resourceUserGroup, "custom_fields.first_name", "rob"), + // resource.TestCheckResourceAttr(resourceUserGroup, "custom_fields.family_name", "pike"), + // // default value check + // resource.TestCheckResourceAttr(resourceUserGroup, "make_first_user_admin", strconv.FormatBool(false)), + // resource.TestCheckResourceAttr(resourceUserGroup, "member_profile_visibility", "public"), + // resource.TestCheckResourceAttr(resourceUserGroup, "none_member_profile_visibility", "none"), + // ), + // }, + }, + }) +} + +func testAccUserGroupResourceConfig(groupType, groupID string) string { + return ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_group_type" "example" { + group_type = "` + groupType + `" + role_mode = "no_roles" + description = "group type description" + } + resource "cidaas_user_groups" "example" { + group_type = cidaas_group_type.example.group_type + group_id = "` + groupID + `" + group_name = "` + userGroupName + `" + logo_url = "https://cidaas.de/logo" + description = "sample user groups description" + custom_fields = { + first_name = "cidaas" + family_name = "widas" + } + make_first_user_admin = true + member_profile_visibility = "full" + none_member_profile_visibility = "public" + } + ` +} + +func testCheckUserGroupDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceUserGroup] + if !ok { + return fmt.Errorf("resource %s not fround", resourceUserGroup) + } + + ug := cidaas.UserGroup{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TestToken, + }, + } + res, _ := ug.Get(rs.Primary.Attributes["group_id"]) + if res != nil { + // when resource exists in remote + return fmt.Errorf("resource stil exists %+v", res) + } + + rs, ok = s.RootModule().Resources[resourceGroupType] + if !ok { + return fmt.Errorf("resource %s not fround", resourceGroupType) + } + + groupType := cidaas.GroupType{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TestToken, + }, + } + resp, _ := groupType.Get(rs.Primary.Attributes["group_type"]) + + if resp != nil { + // when resource exists in remote + return fmt.Errorf("resource %s stil exists", resp.Data.GroupType) + } + return nil +} + +// missing required fields group_type, group_name and group_id +func TestAccWebhookResource_MissingRequired(t *testing.T) { + requiredParams := []string{"group_type", "group_name", "group_id"} + for _, v := range requiredParams { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_user_groups" "example" {} + `, + ExpectError: regexp.MustCompile(fmt.Sprintf(`The argument "%s" is required`, v)), + }, + }, + }) + } +} + +// check if group_type, group_name and group_id are empty string +func TestAccWebhookResource_CheckEmptyString(t *testing.T) { + requiredParams := []string{"group_type", "group_name", "group_id"} + for _, v := range requiredParams { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_user_groups" "example" { + group_type ="" + group_id = "" + group_name = "" + } + `, + ExpectError: regexp.MustCompile(fmt.Sprintf(`Attribute %s string length must be at least 1`, v)), + }, + }, + }) + } +} From e41f70d7c56510703725a94767c7c13800f09229 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Mon, 2 Sep 2024 16:00:35 +0530 Subject: [PATCH 31/36] user group test name updated correction made to the name of the acceptance tests of the resource cidaas_user_group --- internal/resources/resource_user_group_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/resources/resource_user_group_test.go b/internal/resources/resource_user_group_test.go index 937d988..9c9b170 100644 --- a/internal/resources/resource_user_group_test.go +++ b/internal/resources/resource_user_group_test.go @@ -24,7 +24,7 @@ var ( ) // create, read and update test -func TestAccUserGroupResource_Basic(t *testing.T) { +func TestUserGroup_Basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, @@ -150,7 +150,7 @@ func testCheckUserGroupDestroyed(s *terraform.State) error { } // missing required fields group_type, group_name and group_id -func TestAccWebhookResource_MissingRequired(t *testing.T) { +func TestUserGroup_MissingRequired(t *testing.T) { requiredParams := []string{"group_type", "group_name", "group_id"} for _, v := range requiredParams { resource.Test(t, resource.TestCase{ @@ -172,7 +172,7 @@ func TestAccWebhookResource_MissingRequired(t *testing.T) { } // check if group_type, group_name and group_id are empty string -func TestAccWebhookResource_CheckEmptyString(t *testing.T) { +func TestUserGroup_CheckEmptyString(t *testing.T) { requiredParams := []string{"group_type", "group_name", "group_id"} for _, v := range requiredParams { resource.Test(t, resource.TestCase{ From b8f1e005a6caa83229d7b1b4c7ca143efd51cb89 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Mon, 2 Sep 2024 16:28:41 +0530 Subject: [PATCH 32/36] template_group acceptance test acceptance test added for the resource cidaas_template_group --- .../resources/resource_template_group_test.go | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 internal/resources/resource_template_group_test.go diff --git a/internal/resources/resource_template_group_test.go b/internal/resources/resource_template_group_test.go new file mode 100644 index 0000000..2870ac1 --- /dev/null +++ b/internal/resources/resource_template_group_test.go @@ -0,0 +1,122 @@ +package resources_test + +import ( + "fmt" + "net/http" + "os" + "regexp" + "testing" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const resourceTemplateGroup = "cidaas_template_group.example" + +var templateGroupID = acctest.RandString(10) + +// create, read and update test +func TestTemplateGroup_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: CheckTemplateGroupDestroyed, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_template_group" "example" { + group_id = "` + templateGroupID + `" + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceTemplateGroup, "group_id", templateGroupID), + + resource.TestCheckResourceAttrSet(resourceTemplateGroup, "id"), + resource.TestCheckResourceAttrSet(resourceTemplateGroup, "email_sender_config.from_email"), + resource.TestCheckResourceAttrSet(resourceTemplateGroup, "email_sender_config.from_name"), + resource.TestCheckResourceAttrSet(resourceTemplateGroup, "email_sender_config.reply_to"), + resource.TestCheckResourceAttrSet(resourceTemplateGroup, "email_sender_config.sender_names.#"), + resource.TestCheckResourceAttrSet(resourceTemplateGroup, "ivr_sender_config.sender_names.#"), + resource.TestCheckResourceAttrSet(resourceTemplateGroup, "push_sender_config.sender_names.#"), + resource.TestCheckResourceAttrSet(resourceTemplateGroup, "sms_sender_config.sender_names.#"), + ), + }, + { + ResourceName: resourceTemplateGroup, + ImportState: true, + ImportStateVerify: true, + ImportStateId: templateGroupID, + }, + // if you update only from_email in cidaas_template_group then the test fails + // TODO: fix refresh plan not empty issye terraform apply(update) + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_template_group" "example" { + group_id = "` + templateGroupID + `" + email_sender_config = { + from_email = "noreply@cidaas.eu" + from_name = "Kube-dev" + reply_to = "noreply@cidaas.de" + sender_names = [ + "SYSTEM", + ] + } + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceTemplateGroup, "email_sender_config.from_email", "noreply@cidaas.eu"), + ), + }, + }, + }) +} + +func CheckTemplateGroupDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceTemplateGroup] + if !ok { + return fmt.Errorf("resource %s not fround", resourceTemplateGroup) + } + + tg := cidaas.TemplateGroup{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TestToken, + }, + } + res, _ := tg.Get(rs.Primary.Attributes["group_id"]) + if res != nil && res.Status != http.StatusNoContent { + // when resource exists in remote + return fmt.Errorf("resource stil exists %+v", res) + } + return nil +} + +// group_id length should not be greater than 15 +func TestTemplateGroup_GourpIDLenghCheck(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: CheckTemplateGroupDestroyed, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_template_group" "example" { + group_id = "` + acctest.RandString(16) + `" + } + `, + ExpectError: regexp.MustCompile("group_id string length must be at most 15"), + }, + }, + }) +} From 82f026cacc5cf8e7cf6f417d94eeade3c05b1d92 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Mon, 2 Sep 2024 17:52:19 +0530 Subject: [PATCH 33/36] template acceptance test acceptance test added for the resource cidaas_template --- internal/resources/resource_template_test.go | 179 +++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 internal/resources/resource_template_test.go diff --git a/internal/resources/resource_template_test.go b/internal/resources/resource_template_test.go new file mode 100644 index 0000000..996e5da --- /dev/null +++ b/internal/resources/resource_template_test.go @@ -0,0 +1,179 @@ +package resources_test + +import ( + "fmt" + "net/http" + "os" + "regexp" + "strconv" + "strings" + "testing" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +// only covers custom temaplates tests +// locale validation is already done in other resources, hence skipped in template resource + +const resourceTemplate = "cidaas_template.example" + +var ( + templateLocale = "de-de" + templateKey = strings.ToUpper(acctest.RandString(10)) + templateType = "SMS" + templateContent = acctest.RandString(256) +) + +// create, read and update test +func TestTemplate_Basic(t *testing.T) { + updatedTemplateContent := acctest.RandString(256) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: checkTemplateDestroyed, + Steps: []resource.TestStep{ + { + Config: testTemplateConfig(templateLocale, templateKey, templateType, templateContent), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceTemplate, "locale", templateLocale), + resource.TestCheckResourceAttr(resourceTemplate, "template_key", templateKey), + resource.TestCheckResourceAttr(resourceTemplate, "template_type", templateType), + resource.TestCheckResourceAttr(resourceTemplate, "content", templateContent), + + resource.TestCheckResourceAttrSet(resourceTemplate, "id"), + resource.TestCheckResourceAttrSet(resourceTemplate, "template_owner"), + resource.TestCheckResourceAttrSet(resourceTemplate, "group_id"), + resource.TestCheckResourceAttrSet(resourceTemplate, "is_system_template"), + ), + }, + { + ResourceName: resourceTemplate, + ImportState: true, + ImportStateVerify: true, + ImportStateId: templateKey + ":" + templateType + ":" + templateLocale, + }, + { + Config: testTemplateConfig(templateLocale, templateKey, templateType, updatedTemplateContent), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceTemplate, "content", updatedTemplateContent), + // check default value + resource.TestCheckResourceAttr(resourceTemplate, "is_system_template", strconv.FormatBool(false)), + ), + }, + // locale, template_key and template type can't be modified + { + Config: testTemplateConfig("en-us", strings.ToUpper(acctest.RandString(10)), "IVR", updatedTemplateContent), + ExpectError: regexp.MustCompile("can't be modified"), + }, + }, + }) +} + +func testTemplateConfig(locale, templateKey, templateType, content string) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_template" "example" { + locale = "%s" + template_key = "%s" + template_type = "%s" + content = "%s" + } + `, locale, templateKey, templateType, content) +} + +func checkTemplateDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceTemplate] + if !ok { + return fmt.Errorf("resource %s not fround", resourceTemplate) + } + + template := cidaas.Template{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TestToken, + }, + } + + templatePayload := cidaas.TemplateModel{ + Locale: rs.Primary.Attributes["locale"], + TemplateKey: rs.Primary.Attributes["temaplte_key"], + TemplateType: rs.Primary.Attributes["temaplte_type"], + } + + res, _ := template.Get(templatePayload, false) + if res != nil && res.Status != http.StatusNoContent { + // when resource exists in remote + return fmt.Errorf("resource stil exists %+v", res) + } + return nil +} + +// subject can not be empty when template type is SMS +func TestTemplate_EmailSubjectCheck(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTemplateConfig(templateLocale, templateKey, "EMAIL", templateContent), + ExpectError: regexp.MustCompile("subject can not be empty when template_type is EMAIL"), + }, + }, + }) +} + +// template_key must be a valid string consisting only of uppercase letters, +// digits (0-9), underscores (_), and hyphens (-) +func TestTemplate_TemplateKeyValidation(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTemplateConfig(templateLocale, acctest.RandString(10), templateType, templateContent), + ExpectError: regexp.MustCompile("template_key must be a valid string consisting"), // TODO: full string validation + }, + }, + }) +} + +// template_type must be one of "EMAIL", "SMS", "IVR" and "PUSH" +func TestTemplate_TemplateTypeValidation(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testTemplateConfig(templateLocale, acctest.RandString(10), "INVALID", templateContent), + ExpectError: regexp.MustCompile("template_key must be a valid string consisting"), // TODO: full string validation + }, + }, + }) +} + +// required params locale, template_key, teamplte_type and content +func TestTemplate_MissingRequired(t *testing.T) { + requiredParams := []string{"locale", "template_key", "template_type", "content"} + for _, v := range requiredParams { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_template" "example" {} + `, + ExpectError: regexp.MustCompile(fmt.Sprintf(`"%s" is required`, v)), // TODO: full string validation + }, + }, + }) + } +} From cf5fd5dc269802dd83ea54d1bd3c7aae3db7f9d8 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Tue, 3 Sep 2024 14:50:50 +0530 Subject: [PATCH 34/36] social provider acceptance test --- .../resource_social_provider_test.go | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 internal/resources/resource_social_provider_test.go diff --git a/internal/resources/resource_social_provider_test.go b/internal/resources/resource_social_provider_test.go new file mode 100644 index 0000000..2467d5b --- /dev/null +++ b/internal/resources/resource_social_provider_test.go @@ -0,0 +1,176 @@ +package resources_test + +import ( + "fmt" + "os" + "regexp" + "strconv" + "testing" + + "github.com/Cidaas/terraform-provider-cidaas/helpers/cidaas" + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const ( + resourceSocialPorvider = "cidaas_social_provider.example" +) + +var ( + spName = acctest.RandString(10) + spProviderName = "google" + spClientID = acctest.RandString(10) + spClientSecret = acctest.RandString(10) +) + +// create, read and update test +func TestSocialProvider_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: checksocialProviderDestroyed, + Steps: []resource.TestStep{ + { + Config: socialProviderConfig(spName, spProviderName, spClientID, spClientSecret), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceSocialPorvider, "name", spName), + resource.TestCheckResourceAttr(resourceSocialPorvider, "provider_name", spProviderName), + resource.TestCheckResourceAttr(resourceSocialPorvider, "client_id", spClientID), + resource.TestCheckResourceAttr(resourceSocialPorvider, "client_secret", spClientSecret), + resource.TestCheckResourceAttr(resourceSocialPorvider, "claims.required_claims.user_info.0", "name"), + + // default value check + resource.TestCheckResourceAttr(resourceSocialPorvider, "enabled", strconv.FormatBool(false)), + resource.TestCheckResourceAttr(resourceSocialPorvider, "enabled_for_admin_portal", strconv.FormatBool(false)), + + resource.TestCheckResourceAttrSet(resourceSocialPorvider, "id"), + ), + }, + { + ResourceName: resourceSocialPorvider, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceSocialPorvider] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceSocialPorvider) + } + return rs.Primary.Attributes["provider_name"] + ":" + rs.Primary.ID, nil + }, + }, + { + Config: socialProviderConfig(spName, spProviderName, spClientID, spClientSecret), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceSocialPorvider, "id"), + ), + }, + // immutable name and provider_name validation + { + Config: socialProviderConfig(spName, "facebook", spClientID, spClientSecret), + ExpectError: regexp.MustCompile(`Attribute 'provider_name' can't be modified`), + }, + { + Config: socialProviderConfig(acctest.RandString(5), spProviderName, spClientID, spClientSecret), + ExpectError: regexp.MustCompile(`Attribute 'name' can't be modified`), + }, + }, + }) +} + +func socialProviderConfig(name, providerName, clientID, clientSecret string) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_social_provider" "example" { + name = "%s" + provider_name = "%s" + client_id = "%s" + client_secret = "%s" + scopes = ["profile", "email"] + claims = { + required_claims = { + user_info = ["name"] + id_token = ["phone_number"] + } + optional_claims = { + user_info = ["website"] + id_token = ["street_address"] + } + } + userinfo_fields = [ + { + inner_key = "sample_custom_field" + external_key = "external_sample_cf" + is_custom_field = true + is_system_field = false + }, + { + inner_key = "sample_system_field" + external_key = "external_sample_sf" + is_custom_field = false + is_system_field = true + } + ] + } + `, name, providerName, clientID, clientSecret) +} + +func checksocialProviderDestroyed(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceSocialPorvider] + if !ok { + return fmt.Errorf("resource %s not fround", resourceSocialPorvider) + } + + sp := cidaas.SocialProvider{ + ClientConfig: cidaas.ClientConfig{ + BaseURL: os.Getenv("BASE_URL"), + AccessToken: acctest.TestToken, + }, + } + res, _ := sp.Get(rs.Primary.Attributes["provider_name"], rs.Primary.Attributes["id"]) + if res != nil { + // when resource exists in remote + return fmt.Errorf("resource stil exists %+v", res) + } + return nil +} + +// Invalid provider_name validation +func TestSocialProvider_InvalidProviderName(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: socialProviderConfig(spName, acctest.RandString(10), spClientID, spClientSecret), + ExpectError: regexp.MustCompile(`Attribute provider_name value must be one of:`), + }, + }, + }) +} + +// missing required parameter +func TestSocialProvider_MissingRequired(t *testing.T) { + requiredParams := []string{ + "name", "provider_name", "client_id", "client_secret", + } + for _, param := range requiredParams { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_social_provider" "example" {} + `, + ExpectError: regexp.MustCompile(fmt.Sprintf(`The argument "%s" is required`, param)), + }, + }, + }) + } +} From 61e846d9fc3596e50d2ae2a46fc6df439b5f8a54 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Thu, 5 Sep 2024 10:57:59 +0530 Subject: [PATCH 35/36] registration_field and consent_version acceptance test acceptance test added for the resources cidaas_registration_field and cidaas_consent_version --- .../resource_consent_version_test.go | 83 ++++++++ .../resource_registration_field_test.go | 193 ++++++++++++++++++ 2 files changed, 276 insertions(+) create mode 100644 internal/resources/resource_consent_version_test.go create mode 100644 internal/resources/resource_registration_field_test.go diff --git a/internal/resources/resource_consent_version_test.go b/internal/resources/resource_consent_version_test.go new file mode 100644 index 0000000..e511afd --- /dev/null +++ b/internal/resources/resource_consent_version_test.go @@ -0,0 +1,83 @@ +package resources_test + +import ( + "fmt" + "testing" + + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +const ( + resourceConsentVersion = "cidaas_consent_version.example" +) + +// create, read and update test +func TestConsentVersion_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testConsentVersionConfig("consent version in German"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceConsentVersion, "consent_type", "SCOPES"), + resource.TestCheckResourceAttrSet(resourceConsentVersion, "id"), + ), + }, + { + ResourceName: resourceConsentVersion, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceConsentVersion] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceConsentVersion) + } + return rs.Primary.Attributes["consent_id"] + ":" + rs.Primary.ID + ":de:en", nil + }, + }, + { + Config: testConsentVersionConfig("updated consent version in German"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceConsentVersion, "consent_type", "SCOPES"), + ), + }, + }, + }) +} + +func testConsentVersionConfig(content string) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_consent_group" "sample" { + group_name = "sample_consent_group" + description = "sample description" + } + resource "cidaas_consent" "sample" { + consent_group_id = cidaas_consent_group.sample.id + name = "sample_consent" + enabled = true + } + resource "cidaas_consent_version" "example" { + version = 1 + consent_id = cidaas_consent.sample.id + consent_type = "SCOPES" + scopes = ["developer"] + required_fields = ["name"] + consent_locales = [ + { + content = "%s" + locale = "de" + }, + { + content = "consent version in English" + locale = "en" + } + ] + } + `, content) +} diff --git a/internal/resources/resource_registration_field_test.go b/internal/resources/resource_registration_field_test.go new file mode 100644 index 0000000..4909139 --- /dev/null +++ b/internal/resources/resource_registration_field_test.go @@ -0,0 +1,193 @@ +package resources_test + +import ( + "fmt" + "strconv" + "testing" + + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +const ( + resourceRegField = "cidaas_registration_field.example" +) + +// create, read and update test +func TestRegistrationField_CheckBoxBasic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + // CheckDestroy: checkRegFieldDestroyed, + Steps: []resource.TestStep{ + { + Config: testRegFieldConfig("CHECKBOX", "sample_checkbox_field", true, false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceRegField, "field_key", "sample_checkbox_field"), + resource.TestCheckResourceAttrSet(resourceRegField, "id"), + ), + }, + { + ResourceName: resourceRegField, + ImportState: true, + ImportStateVerify: true, + ImportStateId: "sample_checkbox_field", + }, + { + Config: testRegFieldConfig("CHECKBOX", "sample_checkbox_field", false, false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceRegField, "id"), + ), + }, + }, + }) +} + +func TestRegistrationField_GroupBasic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + // CheckDestroy: checkRegFieldDestroyed, + Steps: []resource.TestStep{ + { + Config: testRegFieldConfig("TEXT", "sample_group", true, true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceRegField, "field_key", "sample_group"), + resource.TestCheckResourceAttrSet(resourceRegField, "id"), + ), + }, + { + ResourceName: resourceRegField, + ImportState: true, + ImportStateVerify: true, + ImportStateId: "sample_group", + }, + { + Config: testRegFieldConfig("TEXT", "sample_group", false, true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceRegField, "id"), + ), + }, + }, + }) +} + +func TestRegistrationField_TextBasic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_registration_field" "example" { + data_type = "TEXT" + field_key = "sample_text_field" + field_type = "CUSTOM" // CUSTOM and SYSTEM, SYSTEM can not be created but modified + internal = true // Default: false + required = true // Default: false + read_only = true // Default: false + is_group = false // Default: false + unique = true // Default: false + overwrite_with_null_value_from_social_provider = false // Default: true + is_searchable = true // Default: true + enabled = true // Default: true + claimable = true // Default: true + order = 1 // Default: 1 + parent_group_id = "DEFAULT" // Default: DEFAULT + scopes = ["profile"] + local_texts = [ + { + locale = "en-US" + name = "Sample Field" + required_msg = "The field is required" + max_length_msg = "Maximum 99 chars allowed" + min_length_msg = "Minimum 99 chars allowed" + }, + { + locale = "de-DE" + name = "Beispielfeld" + required_msg = "Dieses Feld ist erforderlich" + max_length_msg = "DE maximum 99 chars allowed" + min_length_msg = "DE minimum 10 chars allowed" + } + ] + field_definition = { + max_length = 100 + min_length = 10 + } + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceRegField, "field_key", "sample_text_field"), + resource.TestCheckResourceAttrSet(resourceRegField, "id"), + ), + }, + { + ResourceName: resourceRegField, + ImportState: true, + ImportStateVerify: true, + ImportStateId: "sample_text_field", + }, + }, + }) +} + +func testRegFieldConfig(dataType, fieldKey string, internal, isGroup bool) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_registration_field" "example" { + data_type = "%s" + field_key = "%s" + field_type = "CUSTOM" + internal = %s + required = true + read_only = true + is_group = %s + unique = true + overwrite_with_null_value_from_social_provider = false + is_searchable = true + enabled = true + claimable = true + order = 1 + parent_group_id = "DEFAULT" + scopes = ["profile"] + local_texts = [ + { + locale = "en-US" + name = "Sample Field" + required_msg = "The field is required" + }, + { + locale = "de-DE" + name = "Beispielfeld" + required_msg = "Dieses Feld ist erforderlich" + } + ] + } + `, dataType, fieldKey, strconv.FormatBool(internal), strconv.FormatBool(isGroup)) +} + +// func checkRegFieldDestroyed(s *terraform.State) error { +// rs, ok := s.RootModule().Resources[resourceRegField] +// if !ok { +// return fmt.Errorf("resource %s not fround", resourceRegField) +// } + +// sp := cidaas.SocialProvider{ +// ClientConfig: cidaas.ClientConfig{ +// BaseURL: os.Getenv("BASE_URL"), +// AccessToken: acctest.TestToken, +// }, +// } +// res, _ := sp.Get(rs.Primary.Attributes["provider_name"], rs.Primary.Attributes["id"]) +// if res != nil { +// // when resource exists in remote +// return fmt.Errorf("resource stil exists %+v", res) +// } +// return nil +// } From 51a101f9799f8795e41c2039a49b9514d4dbe5e7 Mon Sep 17 00:00:00 2001 From: Tujit Bora Date: Thu, 5 Sep 2024 16:14:43 +0530 Subject: [PATCH 36/36] resource app acceptance test acceptance test added for the resource cidaas_app enhanced tests in template, user_group and registration_field resources --- internal/resources/resource_app_test.go | 256 ++++++++++++++++++ .../resource_registration_field_test.go | 87 ++++-- internal/resources/resource_template_test.go | 76 ++++++ .../resources/resource_user_group_test.go | 61 +++-- 4 files changed, 432 insertions(+), 48 deletions(-) create mode 100644 internal/resources/resource_app_test.go diff --git a/internal/resources/resource_app_test.go b/internal/resources/resource_app_test.go new file mode 100644 index 0000000..405ec29 --- /dev/null +++ b/internal/resources/resource_app_test.go @@ -0,0 +1,256 @@ +package resources_test + +import ( + "fmt" + "testing" + + acctest "github.com/Cidaas/terraform-provider-cidaas/internal/test" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +const ( + resourceApp = "cidaas_app.example" +) + +// create, read and update test +func TestApp_Basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAppConfig("https://cidaas.de"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceApp, "client_name", "aiPqVcrYQQ3WzW8J"), + resource.TestCheckResourceAttr(resourceApp, "company_website", "https://cidaas.de"), + resource.TestCheckResourceAttrSet(resourceApp, "id"), + ), + }, + { + Config: testAppConfig("https://cidaas.com"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceApp, "company_website", "https://cidaas.com"), + ), + }, + }, + }) +} + +func testAppConfig(companyWebsite string) string { + return fmt.Sprintf(` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + # The config below has the list of common config and main config +resource "cidaas_app" "example" { + client_name = "aiPqVcrYQQ3WzW8J" // unique + client_display_name = "Display Name of the app" // unique + content_align = "CENTER" // Default: CENTER + post_logout_redirect_uris = ["https://cidaas.com"] + logo_align = "CENTER" // Default: CENTER + allow_disposable_email = false // Default: false + validate_phone_number = false // Default: false + additional_access_token_payload = ["sample_payload"] + required_fields = ["email"] + mobile_settings = { + team_id = "sample-team-id" + bundle_id = "sample-bundle-id" + package_name = "sample-package-name" + key_hash = "sample-key-hash" + } + // for custom client credentials use client_id and client_secret, you can leave blank if you want cidaas to create a set for you + # client_id = "" + # client_secret = "" + policy_uri = "https://cidaas.com" + tos_uri = "https://cidaas.com" + imprint_uri = "https://cidaas.com" + contacts = ["support@cidas.de"] + token_endpoint_auth_method = "client_secret_post" // Default: client_secret_post + token_endpoint_auth_signing_alg = "RS256" // Default: RS256 + default_acr_values = ["default"] + web_message_uris = ["https://cidaas.com"] + allowed_fields = ["email"] + smart_mfa = false // Default: false + captcha_ref = "sample-captcha-ref" + captcha_refs = ["sample"] + consent_refs = ["sample"] + communication_medium_verification = "email_verification_required_on_usage" + mobile_number_verification_required = false // Default: false + enable_bot_detection = false // Default: false + allow_guest_login_groups = [{ + group_id = "developer101" + roles = ["developer", "qa", "admin"] + default_roles = ["developer"] + }] + is_login_success_page_enabled = false // Default: false + is_register_success_page_enabled = false // Default: false + group_ids = ["sample"] + is_group_login_selection_enabled = false // Default: false + group_selection = { + selectable_groups = ["developer-users"] + selectable_group_types = ["sample"] + } + group_types = ["sample"] + logo_uri = "https://cidaas.com" + initiate_login_uri = "https://cidaas.com" + registration_client_uri = "https://cidaas.com" + registration_access_token = "registration access token" + client_uri = "https://cidaas.com" + jwks_uri = "https://cidaas.com" + jwks = "https://cidaas.com/jwks" + sector_identifier_uri = "https://cidaas.com" + subject_type = "sample subject type" + id_token_signed_response_alg = "RS256" + id_token_encrypted_response_alg = "RS256" + id_token_encrypted_response_enc = "example" + userinfo_signed_response_alg = "RS256" + userinfo_encrypted_response_alg = "RS256" + userinfo_encrypted_response_enc = "example" + request_object_signing_alg = "RS256" + request_object_encryption_alg = "RS256" + request_object_encryption_enc = "userinfo_encrypted_response_enc" + request_uris = ["sample"] + description = "app description" + consent_page_group = "sample-consent-page-group" + password_policy_ref = "password-policy-ref" + blocking_mechanism_ref = "blocking-mechanism-ref" + sub = "sample-sub" + role = "sample-role" + mfa_configuration = "sample-configuration" + suggest_mfa = ["OFF"] + login_spi = { + oauth_client_id = "bcb-4a6b-9777-8a64abe6af" + spi_url = "https://cidaas.com/spi-url" + } + background_uri = "https://cidaas.com" + video_url = "https://cidaas.com" + bot_captcha_ref = "sample-bot-captcha-ref" + application_meta_data = { + status : "active" + version : "1.0.0" + } + // common config starts here. The attributes from common config can be part of main config + // if an attribute is available both common_config and main config then attribute from the main config will be considered to create an app + common_configs = { + client_type = "SINGLE_PAGE" + accent_color = "#ef4923" // Default: #ef4923 + primary_color = "#ef4923" // Default: #f7941d + media_type = "IMAGE" // Default: IMAGE + allow_login_with = ["EMAIL", "MOBILE", "USER_NAME"] // Default: ["EMAIL", "MOBILE", "USER_NAME"] + redirect_uris = ["https://cidaas.com"] + allowed_logout_urls = ["https://cidaas.com"] + enable_deduplication = true // Default: false + auto_login_after_register = true // Default: false + enable_passwordless_auth = false // Default: true + register_with_login_information = false // Default: false + fds_enabled = false // Default: true + hosted_page_group = "default" // Default: default + company_name = "Widas ID GmbH" + company_address = "01" + company_website = "%s" + allowed_scopes = ["openid", "cidaas:register", "profile"] + response_types = ["code", "token", "id_token"] // Default: ["code", "token", "id_token"] + grant_types = ["client_credentials"] // Default: ["implicit", "authorization_code", "password", "refresh_token"] + login_providers = ["login_provider1", "login_provider2"] + is_hybrid_app = true // Default: false + allowed_web_origins = ["https://cidaas.com"] + allowed_origins = ["https://cidaas.com"] + default_max_age = 86400 // Default: 86400 + token_lifetime_in_seconds = 86400 // Default: 86400 + id_token_lifetime_in_seconds = 86400 // Default: 86400 + refresh_token_lifetime_in_seconds = 15780000 // Default: 15780000 + template_group_id = "custtemp" // Default: default + editable = true // Default: true + social_providers = [{ + provider_name = "cidaas social provider" + social_id = "fdc63bd0-6044-4fa0-abff" + display_name = "cidaas" + }] + custom_providers = [{ + logo_url = "https://cidaas.com/logo-url" + provider_name = "sample-custom-provider" + display_name = "sample-custom-provider" + type = "CUSTOM_OPENID_CONNECT" + }] + saml_providers = [{ + logo_url = "https://cidaas.com/logo-url" + provider_name = "sample-sampl-provider" + display_name = "sample-sampl-provider" + type = "SAMPL_IDP_PROVIDER" + }] + ad_providers = [{ + logo_url = "https://cidaas.com/logo-url" + provider_name = "sample-ad-provider" + display_name = "sample-ad-provider" + type = "ADD_PROVIDER" + }] + jwe_enabled = true // Default: false + user_consent = true // Default: false + allowed_groups = [{ + group_id = "developer101" + roles = ["developer", "qa", "admin"] + default_roles = ["developer"] + }] + operations_allowed_groups = [{ + group_id = "developer101" + roles = ["developer", "qa", "admin"] + default_roles = ["developer"] + }] + enabled = true // Default: true + always_ask_mfa = true // Default: false + allowed_mfa = ["OFF"] + email_verification_required = true // Default: true + allowed_roles = ["sample"] + default_roles = ["sample"] + enable_classical_provider = true // Default: true + is_remember_me_selected = true // Default: true + bot_provider = "CIDAAS" // Default: CIDAAS + allow_guest_login = true // Default: false + # mfa Default: + # { + # setting = "OFF" + # } + mfa = { + setting = "OFF" + } + webfinger = "no_redirection" + default_scopes = ["sample"] + pending_scopes = ["sample"] + } +} + `, companyWebsite) +} + +func TestApp_CommonConfig(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_app" "example" { + client_type = "SINGLE_PAGE" + client_name = "aiPqVcrYQQ3WzW8J" + client_display_name = "The client Terraform Example App is a sample application designed to demonstrate the configuration of the terraform cidaas_app resource." + company_address = "12 Wimsheim, Germany" + company_website = "https://cidaas.de" + allowed_scopes = ["profile"] + company_name = "Cidaas" + allowed_logout_urls = ["https://ciddas.com", "https://ciddas.com/en"] + redirect_uris = ["https://ciddas.com"] + group_selection = {} + login_spi = {} + common_configs = {} + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceApp, "client_name", "aiPqVcrYQQ3WzW8J"), + resource.TestCheckResourceAttrSet(resourceApp, "id"), + ), + }, + }, + }) +} diff --git a/internal/resources/resource_registration_field_test.go b/internal/resources/resource_registration_field_test.go index 4909139..63bd425 100644 --- a/internal/resources/resource_registration_field_test.go +++ b/internal/resources/resource_registration_field_test.go @@ -18,7 +18,6 @@ func TestRegistrationField_CheckBoxBasic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, - // CheckDestroy: checkRegFieldDestroyed, Steps: []resource.TestStep{ { Config: testRegFieldConfig("CHECKBOX", "sample_checkbox_field", true, false), @@ -47,7 +46,6 @@ func TestRegistrationField_GroupBasic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.TestAccPreCheck(t) }, ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, - // CheckDestroy: checkRegFieldDestroyed, Steps: []resource.TestStep{ { Config: testRegFieldConfig("TEXT", "sample_group", true, true), @@ -172,22 +170,69 @@ func testRegFieldConfig(dataType, fieldKey string, internal, isGroup bool) strin `, dataType, fieldKey, strconv.FormatBool(internal), strconv.FormatBool(isGroup)) } -// func checkRegFieldDestroyed(s *terraform.State) error { -// rs, ok := s.RootModule().Resources[resourceRegField] -// if !ok { -// return fmt.Errorf("resource %s not fround", resourceRegField) -// } - -// sp := cidaas.SocialProvider{ -// ClientConfig: cidaas.ClientConfig{ -// BaseURL: os.Getenv("BASE_URL"), -// AccessToken: acctest.TestToken, -// }, -// } -// res, _ := sp.Get(rs.Primary.Attributes["provider_name"], rs.Primary.Attributes["id"]) -// if res != nil { -// // when resource exists in remote -// return fmt.Errorf("resource stil exists %+v", res) -// } -// return nil -// } +func TestRegistrationField_SelectBasic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_registration_field" "example" { + data_type = "RADIO" + field_key = "sample_select_field" + field_type = "CUSTOM" + internal = false + required = true + read_only = false + is_group = false + unique = false + overwrite_with_null_value_from_social_provider = false + is_searchable = true + enabled = true + claimable = true + order = 1 + parent_group_id = "DEFAULT" + scopes = ["profile"] + local_texts = [ + { + locale = "en-US" + name = "Sample Field" + required_msg = "The field is required" + attributes = [ + { + key = "test_key" + value = "test_value" + } + ] + }, + { + locale = "de-DE" + name = "Beispielfeld" + required_msg = "Dieses Feld ist erforderlich" + attributes = [ + { + key = "test_key" + value = "test_value" + } + ] + } + ] + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceRegField, "field_key", "sample_select_field"), + resource.TestCheckResourceAttrSet(resourceRegField, "id"), + ), + }, + { + ResourceName: resourceRegField, + ImportState: true, + ImportStateVerify: true, + ImportStateId: "sample_select_field", + }, + }, + }) +} diff --git a/internal/resources/resource_template_test.go b/internal/resources/resource_template_test.go index 996e5da..98c5398 100644 --- a/internal/resources/resource_template_test.go +++ b/internal/resources/resource_template_test.go @@ -177,3 +177,79 @@ func TestTemplate_MissingRequired(t *testing.T) { }) } } + +// System Template basic create update and delete, system template can not be imported +func TestTemplate_SystemTemplateBasic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + CheckDestroy: checkTemplateDestroyed, + Steps: []resource.TestStep{ + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_template" "example" { + locale = "en-us" + template_key = "VERIFY_USER" + template_type = "SMS" + content = "Hi {{name}}, here is the {{code}} to verify the user" + is_system_template = true + group_id = "sample_group" + processing_type = "GENERAL" + verification_type = "SMS" + usage_type = "VERIFICATION_CONFIGURATION" + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceTemplate, "is_system_template", strconv.FormatBool(true)), + resource.TestCheckResourceAttrSet(resourceTemplate, "id"), + ), + }, + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_template" "example" { + locale = "en-us" + template_key = "VERIFY_USER" + template_type = "SMS" + content = "Hi {{name}}, here is the {{code}} to verify the user updated" + is_system_template = true + group_id = "sample_group" + processing_type = "GENERAL" + verification_type = "SMS" + usage_type = "VERIFICATION_CONFIGURATION" + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceTemplate, "content", "Hi {{name}}, here is the {{code}} to verify the user updated"), + ), + }, + // templated reverted back to the old state + { + Config: ` + provider "cidaas" { + base_url = "https://kube-nightlybuild-dev.cidaas.de" + } + resource "cidaas_template" "example" { + locale = "en-us" + template_key = "VERIFY_USER" + template_type = "SMS" + content = "Hi {{name}}, here is the {{code}} to verify the user" + is_system_template = true + group_id = "sample_group" + processing_type = "GENERAL" + verification_type = "SMS" + usage_type = "VERIFICATION_CONFIGURATION" + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceTemplate, "content", "Hi {{name}}, here is the {{code}} to verify the user"), + ), + }, + }, + }) +} diff --git a/internal/resources/resource_user_group_test.go b/internal/resources/resource_user_group_test.go index 9c9b170..8755604 100644 --- a/internal/resources/resource_user_group_test.go +++ b/internal/resources/resource_user_group_test.go @@ -18,9 +18,10 @@ const ( ) var ( - userGroupType = acctest.RandString(10) - groupID = acctest.RandString(10) - userGroupName = acctest.RandString(10) + userGroupType = acctest.RandString(10) + groupID = acctest.RandString(10) + userGroupName = acctest.RandString(10) + userGroupDescription = "sample user groups description" ) // create, read and update test @@ -31,13 +32,13 @@ func TestUserGroup_Basic(t *testing.T) { CheckDestroy: testCheckUserGroupDestroyed, Steps: []resource.TestStep{ { - Config: testAccUserGroupResourceConfig(userGroupType, groupID), + Config: testAccUserGroupResourceConfig(userGroupType, groupID, userGroupDescription), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrPair(resourceUserGroup, "group_type", resourceGroupType, "group_type"), resource.TestCheckResourceAttr(resourceUserGroup, "group_id", groupID), resource.TestCheckResourceAttr(resourceUserGroup, "group_name", userGroupName), resource.TestCheckResourceAttr(resourceUserGroup, "logo_url", "https://cidaas.de/logo"), - resource.TestCheckResourceAttr(resourceUserGroup, "description", "sample user groups description"), + resource.TestCheckResourceAttr(resourceUserGroup, "description", userGroupDescription), resource.TestCheckResourceAttr(resourceUserGroup, "custom_fields.first_name", "cidaas"), resource.TestCheckResourceAttr(resourceUserGroup, "custom_fields.family_name", "widas"), // default value check @@ -59,32 +60,17 @@ func TestUserGroup_Basic(t *testing.T) { ImportStateVerifyIgnore: []string{"created_at", "updated_at"}, }, { - Config: testAccUserGroupResourceConfig(userGroupType, acctest.RandString(10)), - ExpectError: regexp.MustCompile("Attribute 'group_id' can't be modified"), + Config: testAccUserGroupResourceConfig(userGroupType, groupID, "updated user group description"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceUserGroup, "group_type", userGroupType), + resource.TestCheckResourceAttr(resourceUserGroup, "description", "updated user group description"), + ), }, - // update works, but with update the order of deletion of both the resources changes group_type first and user groups next - // and we get conflict. so have these lines commented. - // TODO: check if the order of destruction can be controlled - // { - // Config: updatedUserGroupConfig(newRandomStr), - // Check: resource.ComposeAggregateTestCheckFunc( - // resource.TestCheckResourceAttr(resourceUserGroup, "group_type", userGroupType), - // resource.TestCheckResourceAttr(resourceUserGroup, "group_name", newRandomStr), - // resource.TestCheckResourceAttr(resourceUserGroup, "logo_url", "https://cidaas.de/v2/logo"), - // resource.TestCheckResourceAttr(resourceUserGroup, "description", "updated sample user groups description"), - // resource.TestCheckResourceAttr(resourceUserGroup, "custom_fields.first_name", "rob"), - // resource.TestCheckResourceAttr(resourceUserGroup, "custom_fields.family_name", "pike"), - // // default value check - // resource.TestCheckResourceAttr(resourceUserGroup, "make_first_user_admin", strconv.FormatBool(false)), - // resource.TestCheckResourceAttr(resourceUserGroup, "member_profile_visibility", "public"), - // resource.TestCheckResourceAttr(resourceUserGroup, "none_member_profile_visibility", "none"), - // ), - // }, }, }) } -func testAccUserGroupResourceConfig(groupType, groupID string) string { +func testAccUserGroupResourceConfig(groupType, groupID, userGroupDescription string) string { return ` provider "cidaas" { base_url = "https://kube-nightlybuild-dev.cidaas.de" @@ -99,7 +85,7 @@ func testAccUserGroupResourceConfig(groupType, groupID string) string { group_id = "` + groupID + `" group_name = "` + userGroupName + `" logo_url = "https://cidaas.de/logo" - description = "sample user groups description" + description = "` + userGroupDescription + `" custom_fields = { first_name = "cidaas" family_name = "widas" @@ -196,3 +182,24 @@ func TestUserGroup_CheckEmptyString(t *testing.T) { }) } } + +// group_id can not be modified +func TestUserGroup_GroupIDIsImmutable(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccUserGroupResourceConfig(userGroupType, groupID, userGroupDescription), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(resourceUserGroup, "group_id", groupID), + resource.TestCheckResourceAttrSet(resourceUserGroup, "id"), + ), + }, + { + Config: testAccUserGroupResourceConfig(userGroupType, acctest.RandString(10), ""), + ExpectError: regexp.MustCompile("Attribute 'group_id' can't be modified"), + }, + }, + }) +}