package api import ( tst "testing" "encoding/xml" "github.com/crewjam/saml" "github.com/stretchr/testify/require" "github.com/supabase/auth/internal/models" ) func TestSAMLAssertionUserID(t *tst.T) { type spec struct { xml string userID string } examples := []spec{ { xml: ` https://example.com/saml transient-name-id http://localhost:9999/saml/metadata urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport `, userID: "", }, { xml: ` https://example.com/saml persistent-name-id http://localhost:9999/saml/metadata urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport `, userID: "persistent-name-id", }, { xml: ` https://example.com/saml name-id@example.com http://localhost:9999/saml/metadata urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport `, userID: "name-id@example.com", }, { xml: ` https://example.com/saml name-id@example.com http://localhost:9999/saml/metadata urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport subject-id `, userID: "subject-id", }, } for i, example := range examples { rawAssertion := saml.Assertion{} require.NoError(t, xml.Unmarshal([]byte(example.xml), &rawAssertion)) assertion := SAMLAssertion{ &rawAssertion, } userID := assertion.UserID() require.Equal(t, userID, example.userID, "example %d had different user ID", i) } } func TestSAMLAssertionProcessing(t *tst.T) { type spec struct { desc string xml string mapping models.SAMLAttributeMapping expected map[string]interface{} } examples := []spec{ { desc: "valid attribute and mapping", xml: ` someone@example.com `, mapping: models.SAMLAttributeMapping{ Keys: map[string]models.SAMLAttribute{ "email": { Name: "mail", }, }, }, expected: map[string]interface{}{ "email": "someone@example.com", }, }, { desc: "valid attributes, use first attribute found in Names", xml: ` old-soap@example.com soap@example.com `, mapping: models.SAMLAttributeMapping{ Keys: map[string]models.SAMLAttribute{ "email": { Names: []string{ "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "http://schemas.xmlsoap.org/claims/EmailAddress", }, }, }, }, expected: map[string]interface{}{ "email": "soap@example.com", }, }, { desc: "valid groups attribute", xml: ` group1 group2 soap@example.com `, mapping: models.SAMLAttributeMapping{ Keys: map[string]models.SAMLAttribute{ "email": { Names: []string{ "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "http://schemas.xmlsoap.org/claims/EmailAddress", }, }, "groups": { Name: "groups", Array: true, }, }, }, expected: map[string]interface{}{ "email": "soap@example.com", "groups": []string{ "group1", "group2", }, }, }, { desc: "missing attribute use default value", xml: ` someone@example.com `, mapping: models.SAMLAttributeMapping{ Keys: map[string]models.SAMLAttribute{ "email": { Name: "email", }, "role": { Default: "member", }, }, }, expected: map[string]interface{}{ "email": "someone@example.com", "role": "member", }, }, { desc: "use default value even if attribute exists but is not specified in mapping", xml: ` someone@example.com admin `, mapping: models.SAMLAttributeMapping{ Keys: map[string]models.SAMLAttribute{ "email": { Name: "mail", }, "role": { Default: "member", }, }, }, expected: map[string]interface{}{ "email": "someone@example.com", "role": "member", }, }, { desc: "use value in XML when attribute exists and is specified in mapping", xml: ` someone@example.com admin `, mapping: models.SAMLAttributeMapping{ Keys: map[string]models.SAMLAttribute{ "email": { Name: "mail", }, "role": { Name: "role", Default: "member", }, }, }, expected: map[string]interface{}{ "email": "someone@example.com", "role": "admin", }, }, } for i, example := range examples { t.Run(example.desc, func(t *tst.T) { rawAssertion := saml.Assertion{} require.NoError(t, xml.Unmarshal([]byte(example.xml), &rawAssertion)) assertion := SAMLAssertion{ &rawAssertion, } result := assertion.Process(example.mapping) require.Equal(t, example.expected, result, "example %d had different processing", i) }) } }