mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-23 04:27:34 +00:00
fix(saml): improve error handling (#8928)
# Which Problems Are Solved There are multiple issues with the metadata and error handling of SAML: - When providing a SAML metadata for an IdP, which cannot be processed, the error will only be noticed once a user tries to use the IdP. - Parsing for metadata with any other encoding than UTF-8 fails. - Metadata containing an enclosing EntitiesDescriptor around EntityDescriptor cannot be parsed. - Metadata's `validUntil` value is always set to 48 hours, which causes issues on external providers, if processed from a manual down/upload. - If a SAML response cannot be parsed, only a generic "Authentication failed" error is returned, the cause is hidden to the user and also to actions. # How the Problems Are Solved - Return parsing errors after create / update and retrieval of an IdP in the API. - Prevent the creation and update of an IdP in case of a parsing failure. - Added decoders for encodings other than UTF-8 (including ASCII, windows and ISO, [currently supported](efd25daf28/encoding/ianaindex/ianaindex.go (L156)
)) - Updated parsing to handle both `EntitiesDescriptor` and `EntityDescriptor` as root element - `validUntil` will automatically set to the certificate's expiration time - Unwrapped the hidden error to be returned. The Login UI will still only provide a mostly generic error, but action can now access the underlying error. # Additional Changes None # Additional Context reported by a customer (cherry picked from commitffe9570776
)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package saml
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"testing"
|
||||
|
||||
"github.com/crewjam/saml"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/saml/requesttracker"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func TestProvider_Options(t *testing.T) {
|
||||
@@ -170,3 +172,111 @@ func TestProvider_Options(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
type args struct {
|
||||
metadata []byte
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *saml.EntityDescriptor
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
"invalid",
|
||||
args{
|
||||
metadata: []byte(`<Test></Test>`),
|
||||
},
|
||||
nil,
|
||||
xml.UnmarshalError("expected element type <EntityDescriptor> but have <Test>"),
|
||||
},
|
||||
{
|
||||
"valid entity descriptor",
|
||||
args{
|
||||
metadata: []byte(`<?xml version="1.0" encoding="UTF-8"?><EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8000/metadata"><IDPSSODescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"><SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8000/sso"></SingleSignOnService></IDPSSODescriptor></EntityDescriptor>`),
|
||||
},
|
||||
&saml.EntityDescriptor{
|
||||
EntityID: "http://localhost:8000/metadata",
|
||||
IDPSSODescriptors: []saml.IDPSSODescriptor{
|
||||
{
|
||||
XMLName: xml.Name{
|
||||
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
|
||||
Local: "IDPSSODescriptor",
|
||||
},
|
||||
SingleSignOnServices: []saml.Endpoint{
|
||||
{
|
||||
Binding: saml.HTTPRedirectBinding,
|
||||
Location: "http://localhost:8000/sso",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"valid entity descriptor, non utf-8",
|
||||
args{
|
||||
metadata: []byte(`<?xml version="1.0" encoding="windows-1252"?><EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8000/metadata"><IDPSSODescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"><SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8000/sso"></SingleSignOnService></IDPSSODescriptor></EntityDescriptor>`),
|
||||
},
|
||||
&saml.EntityDescriptor{
|
||||
EntityID: "http://localhost:8000/metadata",
|
||||
IDPSSODescriptors: []saml.IDPSSODescriptor{
|
||||
{
|
||||
XMLName: xml.Name{
|
||||
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
|
||||
Local: "IDPSSODescriptor",
|
||||
},
|
||||
SingleSignOnServices: []saml.Endpoint{
|
||||
{
|
||||
Binding: saml.HTTPRedirectBinding,
|
||||
Location: "http://localhost:8000/sso",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"entities descriptor without IDPSSODescriptor",
|
||||
args{
|
||||
metadata: []byte(`<?xml version="1.0" encoding="UTF-8"?><EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"><EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8000/metadata"></EntityDescriptor></EntitiesDescriptor>`),
|
||||
},
|
||||
nil,
|
||||
zerrors.ThrowInternal(nil, "SAML-Ejoi3r2", "no entity found with IDPSSODescriptor"),
|
||||
},
|
||||
{
|
||||
"valid entities descriptor",
|
||||
args{
|
||||
metadata: []byte(`<?xml version="1.0" encoding="UTF-8"?><EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"><EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8000/metadata"><IDPSSODescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"><SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8000/sso"></SingleSignOnService></IDPSSODescriptor></EntityDescriptor></EntitiesDescriptor>`),
|
||||
},
|
||||
&saml.EntityDescriptor{
|
||||
EntityID: "http://localhost:8000/metadata",
|
||||
IDPSSODescriptors: []saml.IDPSSODescriptor{
|
||||
{
|
||||
XMLName: xml.Name{
|
||||
Space: "urn:oasis:names:tc:SAML:2.0:metadata",
|
||||
Local: "IDPSSODescriptor",
|
||||
},
|
||||
SingleSignOnServices: []saml.Endpoint{
|
||||
{
|
||||
Binding: saml.HTTPRedirectBinding,
|
||||
Location: "http://localhost:8000/sso",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseMetadata(tt.args.metadata)
|
||||
assert.ErrorIs(t, err, tt.wantErr)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user