diff --git a/docs/resources/realm.md b/docs/resources/realm.md index ec1ed82f5..8fbaad1dd 100644 --- a/docs/resources/realm.md +++ b/docs/resources/realm.md @@ -158,9 +158,16 @@ This block supports the following arguments: - `envelope_from` - (Optional) The email address uses for bounces. - `starttls` - (Optional) When `true`, enables StartTLS. Defaults to `false`. - `ssl` - (Optional) When `true`, enables SSL. Defaults to `false`. -- `auth` - (Optional) Enables authentication to the SMTP server. This block supports the following arguments: +- `auth` - (Optional) Enables authentication to the SMTP server. Cannot be set alongside `token_auth`. This block supports the following arguments: - `username` - (Required) The SMTP server username. - `password` - (Required) The SMTP server password. +- `token_auth` - (Optional) Enables authentication to the SMTP server through OAUTH2. Cannot be set alongside `auth`. This block supports the following arguments: + - `username` - (Required) The SMTP server username. + - `url` - (Required) The auth token URL. + - `client_id` - (Required) The auth token client ID. + - `client_secret` - (Required) The auth token client secret. + - `scope` - (Required) The auth token scope. + ### Internationalization diff --git a/keycloak/realm.go b/keycloak/realm.go index aa635187c..bef5c1091 100644 --- a/keycloak/realm.go +++ b/keycloak/realm.go @@ -158,18 +158,23 @@ type BrowserSecurityHeaders struct { } type SmtpServer struct { - StartTls types.KeycloakBoolQuoted `json:"starttls,omitempty"` - Auth types.KeycloakBoolQuoted `json:"auth,omitempty"` - Port string `json:"port,omitempty"` - Host string `json:"host,omitempty"` - ReplyTo string `json:"replyTo,omitempty"` - ReplyToDisplayName string `json:"replyToDisplayName,omitempty"` - From string `json:"from,omitempty"` - FromDisplayName string `json:"fromDisplayName,omitempty"` - EnvelopeFrom string `json:"envelopeFrom,omitempty"` - Ssl types.KeycloakBoolQuoted `json:"ssl,omitempty"` - User string `json:"user,omitempty"` - Password string `json:"password,omitempty"` + StartTls types.KeycloakBoolQuoted `json:"starttls,omitempty"` + Auth types.KeycloakBoolQuoted `json:"auth,omitempty"` + Port string `json:"port,omitempty"` + Host string `json:"host,omitempty"` + ReplyTo string `json:"replyTo,omitempty"` + ReplyToDisplayName string `json:"replyToDisplayName,omitempty"` + From string `json:"from,omitempty"` + FromDisplayName string `json:"fromDisplayName,omitempty"` + EnvelopeFrom string `json:"envelopeFrom,omitempty"` + Ssl types.KeycloakBoolQuoted `json:"ssl,omitempty"` + User string `json:"user,omitempty"` + Password string `json:"password,omitempty"` + AuthType string `json:"authType,omitempty"` + AuthTokenUrl string `json:"authTokenUrl,omitempty"` + AuthTokenClientId string `json:"authTokenClientId,omitempty"` + AuthTokenClientSecret string `json:"authTokenClientSecret,omitempty"` + AuthTokenScope string `json:"authTokenScope,omitempty"` } func (keycloakClient *KeycloakClient) NewRealm(ctx context.Context, realm *Realm) error { diff --git a/provider/data_source_keycloak_realm.go b/provider/data_source_keycloak_realm.go index c7e3e7a68..1dd2a18ad 100644 --- a/provider/data_source_keycloak_realm.go +++ b/provider/data_source_keycloak_realm.go @@ -229,6 +229,36 @@ func dataSourceKeycloakRealm() *schema.Resource { }, }, }, + "token_auth": { + Type: schema.TypeList, + Computed: true, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "username": { + Type: schema.TypeString, + Computed: true, + }, + "url": { + Type: schema.TypeString, + Computed: true, + }, + "client_id": { + Type: schema.TypeString, + Computed: true, + }, + "client_secret": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + "scope": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, }, }, }, diff --git a/provider/resource_keycloak_realm.go b/provider/resource_keycloak_realm.go index 240782c61..226e75b02 100644 --- a/provider/resource_keycloak_realm.go +++ b/provider/resource_keycloak_realm.go @@ -286,9 +286,10 @@ func resourceKeycloakRealm() *schema.Resource { Optional: true, }, "auth": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + ConflictsWith: []string{"smtp_server.0.token_auth"}, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "username": { @@ -306,6 +307,40 @@ func resourceKeycloakRealm() *schema.Resource { }, }, }, + "token_auth": { + Type: schema.TypeList, + Optional: true, + ConflictsWith: []string{"smtp_server.0.auth"}, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "username": { + Type: schema.TypeString, + Required: true, + }, + "url": { + Type: schema.TypeString, + Required: true, + }, + "client_id": { + Type: schema.TypeString, + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + DiffSuppressFunc: func(_, authTokenClientSecret, _ string, _ *schema.ResourceData) bool { + return authTokenClientSecret == "**********" + }, + }, + "scope": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, }, }, }, @@ -809,12 +844,25 @@ func getRealmFromData(data *schema.ResourceData, keycloakVersion *version.Versio } authConfig := smtpSettings["auth"].([]interface{}) + tokenAuthConfig := smtpSettings["token_auth"].([]interface{}) + if len(authConfig) == 1 { auth := authConfig[0].(map[string]interface{}) smtpServer.Auth = true + smtpServer.AuthType = "basic" smtpServer.User = auth["username"].(string) smtpServer.Password = auth["password"].(string) + } else if len(tokenAuthConfig) == 1 { + tokenAuth := tokenAuthConfig[0].(map[string]interface{}) + + smtpServer.Auth = true + smtpServer.AuthType = "token" + smtpServer.User = tokenAuth["username"].(string) + smtpServer.AuthTokenUrl = tokenAuth["url"].(string) + smtpServer.AuthTokenClientId = tokenAuth["client_id"].(string) + smtpServer.AuthTokenClientSecret = tokenAuth["client_secret"].(string) + smtpServer.AuthTokenScope = tokenAuth["scope"].(string) } else { smtpServer.Auth = false } @@ -1241,12 +1289,24 @@ func setRealmData(data *schema.ResourceData, realm *keycloak.Realm, keycloakVers smtpSettings["ssl"] = realm.SmtpServer.Ssl if realm.SmtpServer.Auth { - auth := make(map[string]interface{}) + if realm.SmtpServer.AuthType == "token" { + token_auth := make(map[string]interface{}) + + token_auth["username"] = realm.SmtpServer.User + token_auth["url"] = realm.SmtpServer.AuthTokenUrl + token_auth["client_id"] = realm.SmtpServer.AuthTokenClientId + token_auth["client_secret"] = realm.SmtpServer.AuthTokenClientSecret + token_auth["scope"] = realm.SmtpServer.AuthTokenScope - auth["username"] = realm.SmtpServer.User - auth["password"] = realm.SmtpServer.Password + smtpSettings["token_auth"] = []interface{}{token_auth} + } else { + auth := make(map[string]interface{}) - smtpSettings["auth"] = []interface{}{auth} + auth["username"] = realm.SmtpServer.User + auth["password"] = realm.SmtpServer.Password + + smtpSettings["auth"] = []interface{}{auth} + } } data.Set("smtp_server", []interface{}{smtpSettings}) diff --git a/provider/resource_keycloak_realm_test.go b/provider/resource_keycloak_realm_test.go index 454ad8e51..6b274a3b1 100644 --- a/provider/resource_keycloak_realm_test.go +++ b/provider/resource_keycloak_realm_test.go @@ -151,6 +151,46 @@ func TestAccKeycloakRealm_SmtpServerUpdate(t *testing.T) { }, }) } +func TestAccKeycloakRealm_SmtpServerOauth(t *testing.T) { + realm := acctest.RandomWithPrefix("tf-acc") + realmDisplayNameHtml := acctest.RandomWithPrefix("tf-acc") + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakRealmDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakRealm_WithSmtpServerWithOauth(realm, "myhost.com", "My Host", "user"), + Check: testAccCheckKeycloakRealmSmtp("keycloak_realm.realm", "myhost.com", "My Host", "user"), + }, + { + Config: testKeycloakRealm_basic(realm, realm, realmDisplayNameHtml), + Check: testAccCheckKeycloakRealmSmtp("keycloak_realm.realm", "", "", ""), + }, + }, + }) +} + +func TestAccKeycloakRealm_SmtpServerOauthUpdate(t *testing.T) { + realm := acctest.RandomWithPrefix("tf-acc") + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakRealmDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakRealm_WithSmtpServerWithOauth(realm, "myhost.com", "My Host", "user"), + Check: testAccCheckKeycloakRealmSmtp("keycloak_realm.realm", "myhost.com", "My Host", "user"), + }, + { + Config: testKeycloakRealm_WithSmtpServerWithOauth(realm, "myhost2.com", "My Host2", "user2"), + Check: testAccCheckKeycloakRealmSmtp("keycloak_realm.realm", "myhost2.com", "My Host2", "user2"), + }, + }, + }) +} func TestAccKeycloakRealm_SmtpServerInvalid(t *testing.T) { realm := acctest.RandomWithPrefix("tf-acc") @@ -1344,6 +1384,34 @@ resource "keycloak_realm" "realm" { `, realm, realm, host, from, user) } +func testKeycloakRealm_WithSmtpServerWithOauth(realm, host, from, user string) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" + enabled = true + display_name = "%s" + smtp_server { + host = "%s" + port = 25 + from_display_name = "Tom" + from = "%s" + reply_to_display_name = "Tom" + reply_to = "tom@myhost.com" + ssl = true + starttls = true + envelope_from = "nottom@myhost.com" + token_auth { + username = "%s" + url = "wibble.com" + client_id = "wibble" + client_secret = "wobble" + scope = "wiggle" + } + } +} + `, realm, realm, host, from, user) +} + func testKeycloakRealm_WithOTP(realm, otpType, algorithm string, period int) string { return fmt.Sprintf(` resource "keycloak_realm" "realm" {