teamcity/auth.go (584 lines of code) (raw):

package teamcity import ( "context" "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/types" "strconv" "terraform-provider-teamcity/client" "terraform-provider-teamcity/models" ) var ( _ resource.Resource = &authResource{} _ resource.ResourceWithConfigure = &authResource{} _ resource.ResourceWithValidateConfig = &authResource{} _ resource.ResourceWithImportState = &authResource{} ) func NewAuthResource() resource.Resource { return &authResource{} } type authResource struct { client *client.Client } type authResourceModel struct { AllowGuest types.Bool `tfsdk:"allow_guest"` GuestUsername types.String `tfsdk:"guest_username"` WelcomeText types.String `tfsdk:"welcome_text"` CollapseLoginForm types.Bool `tfsdk:"collapse_login_form"` //TwoFactorMode types.String `tfsdk:"two_factor_mode"` PerProjectPermissions types.Bool `tfsdk:"per_project_permissions"` EmailVerification types.Bool `tfsdk:"email_verification"` Modules authModulesModel `tfsdk:"modules"` } type authModulesModel struct { Token *authModuleTokenModel `tfsdk:"token"` BuiltIn *authModuleBuiltInModel `tfsdk:"built_in"` BasicHTTP *authModuleBasicHTTPModel `tfsdk:"basic_http"` Google *authModuleGoogleModel `tfsdk:"google"` GithubApp *authModuleGithubAppModel `tfsdk:"github_app"` GithubCom *authModuleGithubModel `tfsdk:"github"` GithubEnterprise *authModuleGithubModel `tfsdk:"github_enterprise"` LDAP *authModuleLDAPModel `tfsdk:"ldap"` Space *authModuleSpaceModel `tfsdk:"jetbrains_space"` } func (r *authResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { if req.ProviderData == nil { return } r.client = req.ProviderData.(*client.Client) } func (r *authResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_auth_settings" } func (r *authResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Description: "TeamCity can authenticate users via an internal database, or can integrate into your system and use external authentication sources such as Windows Domain, LDAP, or Git hosting providers. More details [here](https://www.jetbrains.com/help/teamcity/configuring-authentication-settings.html)", Attributes: map[string]schema.Attribute{ "allow_guest": schema.BoolAttribute{ Required: true, }, "guest_username": schema.StringAttribute{ Required: true, }, "welcome_text": schema.StringAttribute{ Required: true, }, "collapse_login_form": schema.BoolAttribute{ Required: true, }, //"two_factor_mode": schema.StringAttribute{ // Required: true, // Validators: []validator.String{ // stringvalidator.OneOf([]string{"DISABLED", "OPTIONAL", "MANDATORY"}...), // }, //}, "per_project_permissions": schema.BoolAttribute{ Required: true, }, "email_verification": schema.BoolAttribute{ Required: true, }, "modules": schema.SingleNestedAttribute{ Required: true, Attributes: map[string]schema.Attribute{ "token": schema.SingleNestedAttribute{ Required: true, }, "built_in": schema.SingleNestedAttribute{ Optional: true, Attributes: map[string]schema.Attribute{ "registration": schema.BoolAttribute{ Required: true, }, "change_passwords": schema.BoolAttribute{ Required: true, }, "reset_passwords": schema.BoolAttribute{ Optional: true, }, }, }, "basic_http": schema.SingleNestedAttribute{ Optional: true, }, "google": schema.SingleNestedAttribute{ Optional: true, Attributes: map[string]schema.Attribute{ "create_new_users": schema.BoolAttribute{ Required: true, }, "all_domains": schema.BoolAttribute{ Required: true, }, "domains": schema.StringAttribute{ Optional: true, }, }, }, "github_app": schema.SingleNestedAttribute{ Optional: true, Attributes: map[string]schema.Attribute{ "create_new_users": schema.BoolAttribute{ Required: true, }, "organizations": schema.StringAttribute{ Required: true, }, }, }, "github": schema.SingleNestedAttribute{ Optional: true, Attributes: map[string]schema.Attribute{ "create_new_users": schema.BoolAttribute{ Required: true, }, "organizations": schema.StringAttribute{ Required: true, }, }, }, "github_enterprise": schema.SingleNestedAttribute{ Optional: true, Attributes: map[string]schema.Attribute{ "create_new_users": schema.BoolAttribute{ Required: true, }, "organizations": schema.StringAttribute{ Required: true, }, }, }, "ldap": schema.SingleNestedAttribute{ Optional: true, Attributes: map[string]schema.Attribute{ "create_new_users": schema.BoolAttribute{ Required: true, }, }, }, "jetbrains_space": schema.SingleNestedAttribute{ Optional: true, Attributes: map[string]schema.Attribute{ "create_new_users": schema.BoolAttribute{ Required: true, }, }, }, }, }, }, } } func (r *authResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { var config authResourceModel diags := req.Config.Get(ctx, &config) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } if config.Modules.BuiltIn != nil { if config.Modules.BuiltIn.ChangePasswords.ValueBool() == true { if config.Modules.BuiltIn.ResetPasswords.IsNull() { resp.Diagnostics.AddAttributeError( path.Root("modules").AtName("built_in").AtName("reset_passwords"), "'reset_passwords' must be specified if 'change_passwords' is enabled", "", ) } } else { if config.Modules.BuiltIn.ResetPasswords.IsNull() != true { resp.Diagnostics.AddAttributeError( path.Root("modules").AtName("built_in").AtName("reset_passwords"), "'reset_passwords' cannot be specified if 'change_passwords' is disabled", "", ) } } } if config.Modules.Google != nil { if config.Modules.Google.AllDomains.ValueBool() == true { if config.Modules.Google.Domains.IsNull() != true { resp.Diagnostics.AddAttributeError( path.Root("modules").AtName("google").AtName("domains"), "'domains' cannot be specified if 'all_domains' is enabled", "", ) } } else { if config.Modules.Google.Domains.IsNull() == true { resp.Diagnostics.AddAttributeError( path.Root("modules").AtName("google").AtName("domains"), "'domains' must be specified if 'all_domains' is disabled", "", ) } } } } func (r *authResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan authResourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } newState, err := r.update(plan) if err != nil { resp.Diagnostics.AddError( "Error setting authentication settings", err.Error(), ) return } diags = resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *authResource) Read(ctx context.Context, _ resource.ReadRequest, resp *resource.ReadResponse) { result, err := r.client.GetAuthSettings() if err != nil { resp.Diagnostics.AddError( "Unable to read authentication settings", err.Error(), ) return } newState, err := r.readState(result) if err != nil { resp.Diagnostics.AddError( "Unable to read authentication settings", err.Error(), ) return } diags := resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *authResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var plan authResourceModel diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } newState, err := r.update(plan) if err != nil { resp.Diagnostics.AddError( "Error setting authentication settings", err.Error(), ) return } diags = resp.State.Set(ctx, newState) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } } func (r *authResource) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) { } func (r *authResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resp.State.Set(ctx, authResourceModel{}) } func (r *authResource) update(plan authResourceModel) (authResourceModel, error) { settings := client.AuthSettings{ AllowGuest: plan.AllowGuest.ValueBool(), GuestUsername: plan.GuestUsername.ValueString(), WelcomeText: plan.WelcomeText.ValueString(), CollapseLoginForm: plan.CollapseLoginForm.ValueBool(), //TwoFactorMode: plan.TwoFactorMode.ValueString(), PerProjectPermissions: plan.PerProjectPermissions.ValueBool(), EmailVerification: plan.EmailVerification.ValueBool(), } if plan.Modules.Token != nil { settings.Modules.Module = append(settings.Modules.Module, client.Module{Name: "Token-Auth"}) } if plan.Modules.BuiltIn != nil { settings.Modules.Module = append(settings.Modules.Module, client.Module{ Name: "Default", Properties: &models.Properties{ Property: plan.Modules.BuiltIn.getProperties(), }, }) } if plan.Modules.BasicHTTP != nil { settings.Modules.Module = append(settings.Modules.Module, client.Module{Name: "HTTP-Basic"}) } if plan.Modules.Google != nil { settings.Modules.Module = append(settings.Modules.Module, client.Module{ Name: "Google-oauth", Properties: &models.Properties{ Property: plan.Modules.Google.getProperties(), }, }) } if plan.Modules.GithubApp != nil { settings.Modules.Module = append(settings.Modules.Module, client.Module{ Name: "GitHubApp-oauth", Properties: &models.Properties{ Property: plan.Modules.GithubApp.getProperties(), }, }) } if plan.Modules.GithubCom != nil { settings.Modules.Module = append(settings.Modules.Module, client.Module{ Name: "GitHub-oauth", Properties: &models.Properties{ Property: plan.Modules.GithubCom.getProperties(), }, }) } if plan.Modules.GithubEnterprise != nil { settings.Modules.Module = append(settings.Modules.Module, client.Module{ Name: "GHE-oauth", Properties: &models.Properties{ Property: plan.Modules.GithubEnterprise.getProperties(), }, }) } if plan.Modules.Space != nil { settings.Modules.Module = append(settings.Modules.Module, client.Module{ Name: "JetbrainsSpace-oauth", Properties: &models.Properties{ Property: plan.Modules.Space.getProperties(), }, }) } if plan.Modules.LDAP != nil { settings.Modules.Module = append(settings.Modules.Module, client.Module{ Name: "LDAP", Properties: &models.Properties{ Property: plan.Modules.LDAP.getProperties(), }, }) } result, err := r.client.SetAuthSettings(settings) if err != nil { return authResourceModel{}, err } return r.readState(result) } func (r *authResource) readState(result client.AuthSettings) (authResourceModel, error) { var state authResourceModel state.AllowGuest = types.BoolValue(result.AllowGuest) state.GuestUsername = types.StringValue(result.GuestUsername) state.WelcomeText = types.StringValue(result.WelcomeText) state.CollapseLoginForm = types.BoolValue(result.CollapseLoginForm) //state.TwoFactorMode = types.StringValue(result.TwoFactorMode) state.PerProjectPermissions = types.BoolValue(result.PerProjectPermissions) state.EmailVerification = types.BoolValue(result.EmailVerification) for _, module := range result.Modules.Module { props := make(map[string]string) for _, p := range module.Properties.Property { props[p.Name] = p.Value } if module.Name == "Token-Auth" { state.Modules.Token = &authModuleTokenModel{} continue } if module.Name == "Default" { state.Modules.BuiltIn = &authModuleBuiltInModel{} err := state.Modules.BuiltIn.setFields(props) if err != nil { return authResourceModel{}, err } continue } if module.Name == "HTTP-Basic" { state.Modules.BasicHTTP = &authModuleBasicHTTPModel{} continue } if module.Name == "Google-oauth" { state.Modules.Google = &authModuleGoogleModel{} err := state.Modules.Google.setFields(props) if err != nil { return authResourceModel{}, err } continue } if module.Name == "GitHubApp-oauth" { state.Modules.GithubApp = &authModuleGithubAppModel{} err := state.Modules.GithubApp.setFields(props) if err != nil { return authResourceModel{}, err } continue } if module.Name == "GitHub-oauth" { state.Modules.GithubCom = &authModuleGithubModel{} err := state.Modules.GithubCom.setFields(props) if err != nil { return authResourceModel{}, err } continue } if module.Name == "GHE-oauth" { state.Modules.GithubEnterprise = &authModuleGithubModel{} err := state.Modules.GithubEnterprise.setFields(props) if err != nil { return authResourceModel{}, err } continue } if module.Name == "JetbrainsSpace-oauth" { state.Modules.Space = &authModuleSpaceModel{} err := state.Modules.Space.setFields(props) if err != nil { return authResourceModel{}, err } continue } if module.Name == "LDAP" { state.Modules.LDAP = &authModuleLDAPModel{} err := state.Modules.LDAP.setFields(props) if err != nil { return authResourceModel{}, err } continue } } return state, nil } type authModuleTokenModel struct { } type authModuleBuiltInModel struct { Registration types.Bool `tfsdk:"registration"` ChangePasswords types.Bool `tfsdk:"change_passwords"` ResetPasswords types.Bool `tfsdk:"reset_passwords"` } type authModuleBasicHTTPModel struct { } type authModuleGoogleModel struct { CreateNewUsers types.Bool `tfsdk:"create_new_users"` AllDomains types.Bool `tfsdk:"all_domains"` Domains types.String `tfsdk:"domains"` } type authModuleGithubModel struct { CreateNewUsers types.Bool `tfsdk:"create_new_users"` Organizations types.String `tfsdk:"organizations"` } type authModuleGithubAppModel struct { CreateNewUsers types.Bool `tfsdk:"create_new_users"` Organizations types.String `tfsdk:"organizations"` } type authModuleLDAPModel struct { CreateNewUsers types.Bool `tfsdk:"create_new_users"` } type authModuleSpaceModel struct { CreateNewUsers types.Bool `tfsdk:"create_new_users"` } func (m *authModuleBuiltInModel) getProperties() []models.Property { props := []models.Property{ {Name: "freeRegistrationAllowed", Value: strconv.FormatBool(m.Registration.ValueBool())}, {Name: "usersCanChangeOwnPasswords", Value: strconv.FormatBool(m.ChangePasswords.ValueBool())}, } if m.ChangePasswords.ValueBool() == true { props = append(props, models.Property{ Name: "usersCanResetOwnPasswords", Value: strconv.FormatBool(m.ResetPasswords.ValueBool()), }) } return props } func (m *authModuleBuiltInModel) setFields(props map[string]string) error { registration, err := strconv.ParseBool(props["freeRegistrationAllowed"]) if err != nil { return err } change, err := strconv.ParseBool(props["usersCanChangeOwnPasswords"]) if err != nil { return err } m.Registration = types.BoolValue(registration) m.ChangePasswords = types.BoolValue(change) if change == true { reset, err := strconv.ParseBool(props["usersCanResetOwnPasswords"]) if err != nil { return err } m.ResetPasswords = types.BoolValue(reset) } return nil } func (m *authModuleGoogleModel) getProperties() []models.Property { props := []models.Property{ {Name: "allowCreatingNewUsersByLogin", Value: strconv.FormatBool(m.CreateNewUsers.ValueBool())}, {Name: "allowAllUsersToLogin", Value: strconv.FormatBool(m.AllDomains.ValueBool())}, } if m.AllDomains.ValueBool() == false { props = append(props, models.Property{ Name: "domains", Value: m.Domains.ValueString(), }) } return props } func (m *authModuleGoogleModel) setFields(props map[string]string) error { creating, err := strconv.ParseBool(props["allowCreatingNewUsersByLogin"]) if err != nil { return err } all, err := strconv.ParseBool(props["allowAllUsersToLogin"]) if err != nil { return err } m.CreateNewUsers = types.BoolValue(creating) m.AllDomains = types.BoolValue(all) if all == false { m.Domains = types.StringValue(props["domains"]) } return nil } func (m *authModuleGithubAppModel) getProperties() []models.Property { return []models.Property{ {Name: "allowCreatingNewUsersByLogin", Value: strconv.FormatBool(m.CreateNewUsers.ValueBool())}, {Name: "organisation", Value: m.Organizations.ValueString()}, } } func (m *authModuleGithubAppModel) setFields(props map[string]string) error { creating, err := strconv.ParseBool(props["allowCreatingNewUsersByLogin"]) if err != nil { return err } m.CreateNewUsers = types.BoolValue(creating) m.Organizations = types.StringValue(props["organisation"]) return nil } func (m *authModuleGithubModel) getProperties() []models.Property { return []models.Property{ {Name: "allowCreatingNewUsersByLogin", Value: strconv.FormatBool(m.CreateNewUsers.ValueBool())}, {Name: "organization", Value: m.Organizations.ValueString()}, } } func (m *authModuleGithubModel) setFields(props map[string]string) error { creating, err := strconv.ParseBool(props["allowCreatingNewUsersByLogin"]) if err != nil { return err } m.CreateNewUsers = types.BoolValue(creating) m.Organizations = types.StringValue(props["organization"]) return nil } func (m *authModuleLDAPModel) getProperties() []models.Property { return []models.Property{ {Name: "allowCreatingNewUsersByLogin", Value: strconv.FormatBool(m.CreateNewUsers.ValueBool())}, } } func (m *authModuleLDAPModel) setFields(props map[string]string) error { creating, err := strconv.ParseBool(props["allowCreatingNewUsersByLogin"]) if err != nil { return err } m.CreateNewUsers = types.BoolValue(creating) return nil } func (m *authModuleSpaceModel) getProperties() []models.Property { return []models.Property{ {Name: "allowCreatingNewUsersByLogin", Value: strconv.FormatBool(m.CreateNewUsers.ValueBool())}, } } func (m *authModuleSpaceModel) setFields(props map[string]string) error { creating, err := strconv.ParseBool(props["allowCreatingNewUsersByLogin"]) if err != nil { return err } m.CreateNewUsers = types.BoolValue(creating) return nil }