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

SAML Authn IdP Enhancements #703

Merged
merged 15 commits into from
Jul 15, 2022
Merged
13 changes: 13 additions & 0 deletions keycloak/extra_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,17 @@ func unmarshalExtraConfig(data []byte, reflectValue reflect.Value, extraConfig *
if err == nil {
field.Set(reflect.ValueOf(KeycloakBoolQuoted(boolVal)))
}
} else if field.Kind() == reflect.TypeOf([]string{}).Kind() {
var s KeycloakSliceQuoted

err = json.Unmarshal([]byte(configValue.(string)), &s)
if err != nil {

}

field.Set(reflect.ValueOf(s))
}

delete(*extraConfig, jsonKey)
}
}
Expand All @@ -54,6 +64,9 @@ func marshalExtraConfig(reflectValue reflect.Value, extraConfig map[string]inter
out[jsonKey] = field.String()
} else if field.Kind() == reflect.Bool {
out[jsonKey] = KeycloakBoolQuoted(field.Bool())
} else if field.Kind() == reflect.TypeOf([]string{}).Kind() {
s := field.Interface().(KeycloakSliceQuoted)
out[jsonKey] = s
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions keycloak/identity_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ type IdentityProviderConfig struct {
GuiOrder string `json:"guiOrder,omitempty"`
SyncMode string `json:"syncMode,omitempty"`
ExtraConfig map[string]interface{} `json:"-"`
AuthnContextClassRefs KeycloakSliceQuoted `json:"authnContextClassRefs,omitempty"`
AuthnContextComparisonType string `json:"authnContextComparisonType,omitempty"`
AuthnContextDeclRefs KeycloakSliceQuoted `json:"authnContextDeclRefs,omitempty"`
}

type IdentityProvider struct {
Expand Down
23 changes: 23 additions & 0 deletions keycloak/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package keycloak

import (
"bytes"
"encoding/json"
"strconv"
"strings"
"time"
)

type KeycloakBoolQuoted bool
type KeycloakSliceQuoted []string

func (c KeycloakBoolQuoted) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
Expand Down Expand Up @@ -35,6 +37,27 @@ func (c *KeycloakBoolQuoted) UnmarshalJSON(in []byte) error {
return nil
}

func (s KeycloakSliceQuoted) MarshalJSON() ([]byte, error) {
var buf bytes.Buffer
if s == nil || len(s) == 0 {
buf.WriteString(`""`)
} else {
sliceAsString := make([]string, len(s))
for i, v := range s {
sliceAsString[i] = v
}

stringAsJSON, err := json.Marshal(sliceAsString)
if err != nil {
return nil, err
}

buf.WriteString(strconv.Quote(string(stringAsJSON)))
}

return buf.Bytes(), nil
}

func getIdFromLocationHeader(locationHeader string) string {
parts := strings.Split(locationHeader, "/")

Expand Down
41 changes: 41 additions & 0 deletions provider/resource_keycloak_saml_identity_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ var principalTypes = []string{
"FRIENDLY_ATTRIBUTE",
}

var authnComparisonTypes = []string{
"exact",
"minimum",
"maximum",
"better",
}

func resourceKeycloakSamlIdentityProvider() *schema.Resource {
samlSchema := map[string]*schema.Schema{
"provider_id": {
Expand Down Expand Up @@ -146,6 +153,24 @@ func resourceKeycloakSamlIdentityProvider() *schema.Resource {
Default: "",
Description: "Principal Attribute",
},
"authn_context_class_refs": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
Description: "AuthnContext ClassRefs",
},
"authn_context_decl_refs": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
Description: "AuthnContext DeclRefs",
},
"authn_context_comparison_type": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(authnComparisonTypes, false),
Description: "AuthnContext Comparison",
},
}
samlResource := resourceKeycloakIdentityProvider()
samlResource.Schema = mergeSchemas(samlResource.Schema, samlSchema)
Expand All @@ -159,6 +184,16 @@ func getSamlIdentityProviderFromData(data *schema.ResourceData) (*keycloak.Ident
rec, defaultConfig := getIdentityProviderFromData(data)
rec.ProviderId = data.Get("provider_id").(string)

var authnContextClassRefs keycloak.KeycloakSliceQuoted
for _, v := range data.Get("authn_context_class_refs").([]interface{}) {
authnContextClassRefs = append(authnContextClassRefs, v.(string))
}

var authnContextDeclRefs keycloak.KeycloakSliceQuoted
for _, v := range data.Get("authn_context_decl_refs").([]interface{}) {
authnContextDeclRefs = append(authnContextDeclRefs, v.(string))
}

samlIdentityProviderConfig := &keycloak.IdentityProviderConfig{
ValidateSignature: keycloak.KeycloakBoolQuoted(data.Get("validate_signature").(bool)),
HideOnLoginPage: keycloak.KeycloakBoolQuoted(data.Get("hide_on_login_page").(bool)),
Expand All @@ -178,6 +213,9 @@ func getSamlIdentityProviderFromData(data *schema.ResourceData) (*keycloak.Ident
WantAssertionsEncrypted: keycloak.KeycloakBoolQuoted(data.Get("want_assertions_encrypted").(bool)),
PrincipalType: data.Get("principal_type").(string),
PrincipalAttribute: data.Get("principal_attribute").(string),
AuthnContextClassRefs: authnContextClassRefs,
AuthnContextComparisonType: data.Get("authn_context_comparison_type").(string),
AuthnContextDeclRefs: authnContextDeclRefs,
}

if _, ok := data.GetOk("signature_algorithm"); ok {
Expand Down Expand Up @@ -214,6 +252,9 @@ func setSamlIdentityProviderData(data *schema.ResourceData, identityProvider *ke
data.Set("want_assertions_encrypted", identityProvider.Config.WantAssertionsEncrypted)
data.Set("principal_type", identityProvider.Config.PrincipalType)
data.Set("principal_attribute", identityProvider.Config.PrincipalAttribute)
data.Set("authn_context_class_refs", identityProvider.Config.AuthnContextClassRefs)
data.Set("authn_context_comparison_type", identityProvider.Config.AuthnContextComparisonType)
data.Set("authn_context_decl_refs", identityProvider.Config.AuthnContextDeclRefs)

return nil
}
22 changes: 21 additions & 1 deletion provider/resource_keycloak_saml_identity_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ func TestAccKeycloakSamlIdentityProvider_basicUpdateAll(t *testing.T) {
WantAssertionsEncrypted: keycloak.KeycloakBoolQuoted(firstAssertionsEncrypted),
GuiOrder: strconv.Itoa(acctest.RandIntRange(1, 3)),
SyncMode: randomStringInSlice(syncModes),
AuthnContextClassRefs: keycloak.KeycloakSliceQuoted{"foo", "bar"},
AuthnContextDeclRefs: keycloak.KeycloakSliceQuoted{"foo"},
AuthnContextComparisonType: "exact",
},
}

Expand All @@ -198,6 +201,9 @@ func TestAccKeycloakSamlIdentityProvider_basicUpdateAll(t *testing.T) {
WantAssertionsEncrypted: keycloak.KeycloakBoolQuoted(!firstAssertionsEncrypted),
GuiOrder: strconv.Itoa(acctest.RandIntRange(1, 3)),
SyncMode: randomStringInSlice(syncModes),
AuthnContextClassRefs: keycloak.KeycloakSliceQuoted{"foo", "hello"},
AuthnContextDeclRefs: keycloak.KeycloakSliceQuoted{"baz"},
AuthnContextComparisonType: "exact",
},
}

Expand Down Expand Up @@ -377,6 +383,16 @@ resource "keycloak_saml_identity_provider" "saml" {
}

func testKeycloakSamlIdentityProvider_basicFromInterface(saml *keycloak.IdentityProvider) string {
var authnContextClassRefs []string
for _, v := range saml.Config.AuthnContextClassRefs {
authnContextClassRefs = append(authnContextClassRefs, v)
}

var authnContextDeclRefs []string
for _, v := range saml.Config.AuthnContextDeclRefs {
authnContextDeclRefs = append(authnContextDeclRefs, v)
}

return fmt.Sprintf(`
data "keycloak_realm" "realm" {
realm = "%s"
Expand Down Expand Up @@ -404,6 +420,10 @@ resource "keycloak_saml_identity_provider" "saml" {
want_assertions_encrypted = %t
gui_order = %s
sync_mode = "%s"

authn_context_class_refs = %v
authn_context_decl_refs = %v
authn_context_comparison_type = "%s"
}
`, testAccRealm.Realm, saml.Alias, saml.Enabled, saml.Config.EntityId, saml.Config.SingleSignOnServiceUrl, bool(saml.Config.BackchannelSupported), bool(saml.Config.ValidateSignature), bool(saml.Config.HideOnLoginPage), saml.Config.NameIDPolicyFormat, saml.Config.SingleLogoutServiceUrl, saml.Config.SigningCertificate, saml.Config.SignatureAlgorithm, saml.Config.XmlSigKeyInfoKeyNameTransformer, bool(saml.Config.PostBindingAuthnRequest), bool(saml.Config.PostBindingResponse), bool(saml.Config.PostBindingLogout), bool(saml.Config.ForceAuthn), bool(saml.Config.WantAssertionsSigned), bool(saml.Config.WantAssertionsEncrypted), saml.Config.GuiOrder, saml.Config.SyncMode)
`, testAccRealm.Realm, saml.Alias, saml.Enabled, saml.Config.EntityId, saml.Config.SingleSignOnServiceUrl, bool(saml.Config.BackchannelSupported), bool(saml.Config.ValidateSignature), bool(saml.Config.HideOnLoginPage), saml.Config.NameIDPolicyFormat, saml.Config.SingleLogoutServiceUrl, saml.Config.SigningCertificate, saml.Config.SignatureAlgorithm, saml.Config.XmlSigKeyInfoKeyNameTransformer, bool(saml.Config.PostBindingAuthnRequest), bool(saml.Config.PostBindingResponse), bool(saml.Config.PostBindingLogout), bool(saml.Config.ForceAuthn), bool(saml.Config.WantAssertionsSigned), bool(saml.Config.WantAssertionsEncrypted), saml.Config.GuiOrder, saml.Config.SyncMode, arrayOfStringsForTerraformResource(authnContextClassRefs), arrayOfStringsForTerraformResource(authnContextDeclRefs), saml.Config.AuthnContextComparisonType)
}