Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add extra_config and new required attributes for keycloak_saml_client resource #589

Merged
merged 11 commits into from
Sep 16, 2021
19 changes: 11 additions & 8 deletions docs/resources/saml_client.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,18 @@ resource "keycloak_saml_client" "saml_client" {
- `enabled` - (Optional) When false, this client will not be able to initiate a login or obtain access tokens. Defaults to `true`.
- `description` - (Optional) The description of this client in the GUI.
- `login_theme` - (Optional) The login theme of this client.
- `include_authn_statement` - (Optional) When `true`, an `AuthnStatement` will be included in the SAML response.
- `sign_documents` - (Optional) When `true`, the SAML document will be signed by Keycloak using the realm's private key.
- `sign_assertions` - (Optional) When `true`, the SAML assertions will be signed by Keycloak using the realm's private key, and embedded within the SAML XML Auth response.
- `encrypt_assertions` - (Optional) When `true`, the SAML assertions will be encrypted by Keycloak using the client's public key.
- `client_signature_required` - (Optional) When `true`, Keycloak will expect that documents originating from a client will be signed using the certificate and/or key configured via `signing_certificate` and `signing_private_key`.
- `force_post_binding` - (Optional) When `true`, Keycloak will always respond to an authentication request via the SAML POST Binding.
- `front_channel_logout` - (Optional) When `true`, this client will require a browser redirect in order to perform a logout.
- `include_authn_statement` - (Optional) When `true`, an `AuthnStatement` will be included in the SAML response. Defaults to `true`.
- `sign_documents` - (Optional) When `true`, the SAML document will be signed by Keycloak using the realm's private key. Defaults to `true`.
- `sign_assertions` - (Optional) When `true`, the SAML assertions will be signed by Keycloak using the realm's private key, and embedded within the SAML XML Auth response. Defaults to `false`.
- `encrypt_assertions` - (Optional) When `true`, the SAML assertions will be encrypted by Keycloak using the client's public key. Defaults to `false`.
- `client_signature_required` - (Optional) When `true`, Keycloak will expect that documents originating from a client will be signed using the certificate and/or key configured via `signing_certificate` and `signing_private_key`. Defaults to `true`.
- `force_post_binding` - (Optional) When `true`, Keycloak will always respond to an authentication request via the SAML POST Binding. Defaults to `true`.
- `front_channel_logout` - (Optional) When `true`, this client will require a browser redirect in order to perform a logout. Defaults to `true`.
- `name_id_format` - (Optional) Sets the Name ID format for the subject.
- `force_name_id_format` - (Optional) Ignore requested NameID subject format and use the one defined in `name_id_format` instead.
- `force_name_id_format` - (Optional) Ignore requested NameID subject format and use the one defined in `name_id_format` instead. Defaults to `false`.
- `signature_algorithm` - (Optional) The signature algorithm used to sign documents. Should be one of "RSA_SHA1", "RSA_SHA256", "RSA_SHA512", or "DSA_SHA1".
- `signature_key_name` - (Optional) The value of the `KeyName` element within the signed SAML document. Should be one of "NONE", "KEY_ID", or "CERT_SUBJECT". Defaults to "KEY_ID".
- `canonicalization_method` - (Optional) The Canonicalization Method for XML signatures. Should be one of "EXCLUSIVE", "EXCLUSIVE_WITH_COMMENTS", "INCLUSIVE", or "INCLUSIVE_WITH_COMMENTS". Defaults to "EXCLUSIVE".
- `root_url` - (Optional) When specified, this value is prepended to all relative URLs.
- `valid_redirect_uris` - (Optional) When specified, Keycloak will use this list to validate given Assertion Consumer URLs specified in the authentication request.
- `base_url` - (Optional) When specified, this URL will be used whenever Keycloak needs to link to this client.
Expand All @@ -66,6 +68,7 @@ resource "keycloak_saml_client" "saml_client" {
- `authentication_flow_binding_overrides` - (Optional) Override realm authentication flow bindings
- `browser_id` - (Optional) Browser flow id, (flow needs to exist)
- `direct_grant_id` - (Optional) Direct grant flow id (flow needs to exist)
- `extra_config` - (Optional) A map of key/value pairs to add extra configuration attributes to this client. This can be used for custom attributes, or to add configuration attributes that is not yet supported by this Terraform provider. Use this attribute at your own risk, as s may conflict with top-level configuration attributes in future provider updates.

## Import

Expand Down
53 changes: 32 additions & 21 deletions keycloak/saml_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,33 @@ package keycloak

import (
"fmt"
"reflect"
)

type SamlClientAttributes struct {
IncludeAuthnStatement *string `json:"saml.authnstatement"`
SignDocuments *string `json:"saml.server.signature"`
SignAssertions *string `json:"saml.assertion.signature"`
EncryptAssertions *string `json:"saml.encrypt"`
ClientSignatureRequired *string `json:"saml.client.signature"`
ForcePostBinding *string `json:"saml.force.post.binding"`
ForceNameIdFormat *string `json:"saml_force_name_id_format"`
// attributes above are actually booleans, but the Keycloak API expects strings
SignatureAlgorithm string `json:"saml.signature.algorithm"`
SignatureKeyName string `json:"saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer"`
NameIdFormat string `json:"saml_name_id_format"`
SigningCertificate *string `json:"saml.signing.certificate,omitempty"`
SigningPrivateKey *string `json:"saml.signing.private.key"`
EncryptionCertificate *string `json:"saml.encryption.certificate"`
IDPInitiatedSSOURLName string `json:"saml_idp_initiated_sso_url_name"`
IDPInitiatedSSORelayState string `json:"saml_idp_initiated_sso_relay_state"`
AssertionConsumerPostURL string `json:"saml_assertion_consumer_url_post"`
AssertionConsumerRedirectURL string `json:"saml_assertion_consumer_url_redirect"`
LogoutServicePostBindingURL string `json:"saml_single_logout_service_url_post"`
LogoutServiceRedirectBindingURL string `json:"saml_single_logout_service_url_redirect"`
LoginTheme string `json:"login_theme"`
IncludeAuthnStatement KeycloakBoolQuoted `json:"saml.authnstatement"`
SignDocuments KeycloakBoolQuoted `json:"saml.server.signature"`
SignAssertions KeycloakBoolQuoted `json:"saml.assertion.signature"`
EncryptAssertions KeycloakBoolQuoted `json:"saml.encrypt"`
ClientSignatureRequired KeycloakBoolQuoted `json:"saml.client.signature"`
ForcePostBinding KeycloakBoolQuoted `json:"saml.force.post.binding"`
ForceNameIdFormat KeycloakBoolQuoted `json:"saml_force_name_id_format"`
SignatureAlgorithm string `json:"saml.signature.algorithm"`
SignatureKeyName string `json:"saml.server.signature.keyinfo.xmlSigKeyInfoKeyNameTransformer"`
CanonicalizationMethod string `json:"saml_signature_canonicalization_method"`
NameIdFormat string `json:"saml_name_id_format"`
SigningCertificate string `json:"saml.signing.certificate,omitempty"`
SigningPrivateKey string `json:"saml.signing.private.key"`
EncryptionCertificate string `json:"saml.encryption.certificate"`
IDPInitiatedSSOURLName string `json:"saml_idp_initiated_sso_url_name"`
IDPInitiatedSSORelayState string `json:"saml_idp_initiated_sso_relay_state"`
AssertionConsumerPostURL string `json:"saml_assertion_consumer_url_post"`
AssertionConsumerRedirectURL string `json:"saml_assertion_consumer_url_redirect"`
LogoutServicePostBindingURL string `json:"saml_single_logout_service_url_post"`
LogoutServiceRedirectBindingURL string `json:"saml_single_logout_service_url_redirect"`
LoginTheme string `json:"login_theme"`

ExtraConfig map[string]interface{} `json:"-"`
}

type SamlAuthenticationFlowBindingOverrides struct {
Expand Down Expand Up @@ -185,3 +188,11 @@ func (keycloakClient *KeycloakClient) detachSamlClientScopes(realmId, clientId,
func (keycloakClient *KeycloakClient) DetachSamlClientDefaultScopes(realmId, clientId string, scopeNames []string) error {
return keycloakClient.detachSamlClientScopes(realmId, clientId, "default", scopeNames)
}

func (f *SamlClientAttributes) UnmarshalJSON(data []byte) error {
return unmarshalExtraConfig(data, reflect.ValueOf(f).Elem(), &f.ExtraConfig)
}

func (f *SamlClientAttributes) MarshalJSON() ([]byte, error) {
return marshalExtraConfig(reflect.ValueOf(f).Elem(), f.ExtraConfig)
}
24 changes: 24 additions & 0 deletions provider/data_source_keycloak_saml_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ func dataSourceKeycloakSamlClient() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"saml_signature_key_name": {
Type: schema.TypeString,
Computed: true,
},
"canonicalization_method": {
Type: schema.TypeString,
Computed: true,
},
"name_id_format": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -104,6 +112,18 @@ func dataSourceKeycloakSamlClient() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"encryption_certificate_sha1": {
Type: schema.TypeString,
Computed: true,
},
"signing_certificate_sha1": {
Type: schema.TypeString,
Computed: true,
},
"signing_private_key_sha1": {
Type: schema.TypeString,
Computed: true,
},
"idp_initiated_sso_url_name": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -148,6 +168,10 @@ func dataSourceKeycloakSamlClient() *schema.Resource {
},
},
},
"extra_config": {
Type: schema.TypeMap,
Computed: true,
},
"login_theme": {
Type: schema.TypeString,
Computed: true,
Expand Down
14 changes: 10 additions & 4 deletions provider/extra_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,24 @@ func getExtraConfigFromData(data *schema.ResourceData) map[string]interface{} {
}

func setExtraConfigData(data *schema.ResourceData, extraConfig map[string]interface{}) {
c := map[string]interface{}{}
newExtraConfig := map[string]interface{}{}
extraConfigFromState := getExtraConfigFromData(data)

// when saving back to state, don't persist empty attributes that we're trying to remove from Keycloak
for k, v := range extraConfig {
// when saving back to state, don't persist empty attributes that we're trying to remove from Keycloak
if s, ok := v.(string); ok && s == "" {
continue
}

c[k] = v
// also, we don't want extra_config to be computed, so don't set anything that wasn't originally set by the user in the first place
if _, ok := extraConfigFromState[k]; !ok {
continue
}

newExtraConfig[k] = v
}

data.Set("extra_config", c)
data.Set("extra_config", newExtraConfig)
}

// validateExtraConfig takes a reflect value type to check its JSON schema in order to validate that extra_config
Expand Down
Loading