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
}