mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:07:31 +00:00
fix(api): return typed saml form post data in idp intent (#10136)
<!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. Important: By submitting a contribution you agree to the terms from our Licensing Policy as described here: https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions. --> # Which Problems Are Solved The current user V2 API returns a `[]byte` containing a whole HTML document including the form on `StartIdentifyProviderIntent` for intents based on form post (e.g. SAML POST bindings). This is not usable for most clients as they cannot handle that and render a whole page inside their app. For redirect based intents, the url to which the client needs to redirect is returned. # How the Problems Are Solved - Changed the returned type to a new `FormData` message containing the url and a `fields` map. - internal changes: - Session.GetAuth now returns an `Auth` interfacce and error instead of (content string, redirect bool) - Auth interface has two implementations: `RedirectAuth` and `FormAuth` - All use of the GetAuth function now type switch on the returned auth object - A template has been added to the login UI to execute the form post automatically (as is). # Additional Changes - Some intent integration test did not check the redirect url and were wrongly configured. # Additional Context - relates to zitadel/typescript#410
This commit is contained in:
@@ -2057,7 +2057,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
|||||||
ChangeDate: timestamppb.Now(),
|
ChangeDate: timestamppb.Now(),
|
||||||
ResourceOwner: Instance.ID(),
|
ResourceOwner: Instance.ID(),
|
||||||
},
|
},
|
||||||
url: "http://" + Instance.Domain + ":8000/sso",
|
url: "http://localhost:8000/sso",
|
||||||
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -2081,7 +2081,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
|||||||
ChangeDate: timestamppb.Now(),
|
ChangeDate: timestamppb.Now(),
|
||||||
ResourceOwner: Instance.ID(),
|
ResourceOwner: Instance.ID(),
|
||||||
},
|
},
|
||||||
url: "http://" + Instance.Domain + ":8000/sso",
|
url: "http://localhost:8000/sso",
|
||||||
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -2105,7 +2105,9 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
|||||||
ChangeDate: timestamppb.Now(),
|
ChangeDate: timestamppb.Now(),
|
||||||
ResourceOwner: Instance.ID(),
|
ResourceOwner: Instance.ID(),
|
||||||
},
|
},
|
||||||
postForm: true,
|
url: "http://localhost:8000/sso",
|
||||||
|
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
||||||
|
postForm: true,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
@@ -2143,9 +2145,11 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if tt.want.url != "" {
|
if tt.want.url != "" && !tt.want.postForm {
|
||||||
authUrl, err := url.Parse(got.GetAuthUrl())
|
authUrl, err := url.Parse(got.GetAuthUrl())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want.url, authUrl.Scheme+"://"+authUrl.Host+authUrl.Path)
|
||||||
require.Len(t, authUrl.Query(), len(tt.want.parametersEqual)+len(tt.want.parametersExisting))
|
require.Len(t, authUrl.Query(), len(tt.want.parametersEqual)+len(tt.want.parametersExisting))
|
||||||
|
|
||||||
for _, existing := range tt.want.parametersExisting {
|
for _, existing := range tt.want.parametersExisting {
|
||||||
@@ -2156,7 +2160,15 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tt.want.postForm {
|
if tt.want.postForm {
|
||||||
assert.NotEmpty(t, got.GetPostForm())
|
assert.Equal(t, tt.want.url, got.GetFormData().GetUrl())
|
||||||
|
|
||||||
|
require.Len(t, got.GetFormData().GetFields(), len(tt.want.parametersEqual)+len(tt.want.parametersExisting))
|
||||||
|
for _, existing := range tt.want.parametersExisting {
|
||||||
|
assert.Contains(t, got.GetFormData().GetFields(), existing)
|
||||||
|
}
|
||||||
|
for key, equal := range tt.want.parametersEqual {
|
||||||
|
assert.Equal(t, got.GetFormData().GetFields()[key], equal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
integration.AssertDetails(t, &user.StartIdentityProviderIntentResponse{
|
integration.AssertDetails(t, &user.StartIdentityProviderIntentResponse{
|
||||||
Details: tt.want.details,
|
Details: tt.want.details,
|
||||||
|
@@ -52,19 +52,28 @@ func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.Re
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
content, redirect := session.GetAuth(ctx)
|
auth, err := session.GetAuth(ctx)
|
||||||
if redirect {
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch a := auth.(type) {
|
||||||
|
case *idp.RedirectAuth:
|
||||||
return &user.StartIdentityProviderIntentResponse{
|
return &user.StartIdentityProviderIntentResponse{
|
||||||
Details: object.DomainToDetailsPb(details),
|
Details: object.DomainToDetailsPb(details),
|
||||||
NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: content},
|
NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: a.RedirectURL},
|
||||||
|
}, nil
|
||||||
|
case *idp.FormAuth:
|
||||||
|
return &user.StartIdentityProviderIntentResponse{
|
||||||
|
Details: object.DomainToDetailsPb(details),
|
||||||
|
NextStep: &user.StartIdentityProviderIntentResponse_FormData{
|
||||||
|
FormData: &user.FormData{
|
||||||
|
Url: a.URL,
|
||||||
|
Fields: a.Fields,
|
||||||
|
},
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return &user.StartIdentityProviderIntentResponse{
|
return nil, zerrors.ThrowInvalidArgumentf(nil, "USERv2-3g2j3", "type oneOf %T in method StartIdentityProviderIntent not implemented", auth)
|
||||||
Details: object.DomainToDetailsPb(details),
|
|
||||||
NextStep: &user.StartIdentityProviderIntentResponse_PostForm{
|
|
||||||
PostForm: []byte(content),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*user.StartIdentityProviderIntentResponse, error) {
|
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*user.StartIdentityProviderIntentResponse, error) {
|
||||||
|
@@ -2058,7 +2058,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
|||||||
ChangeDate: timestamppb.Now(),
|
ChangeDate: timestamppb.Now(),
|
||||||
ResourceOwner: Instance.ID(),
|
ResourceOwner: Instance.ID(),
|
||||||
},
|
},
|
||||||
url: "http://" + Instance.Domain + ":8000/sso",
|
url: "http://localhost:8000/sso",
|
||||||
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -2082,7 +2082,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
|||||||
ChangeDate: timestamppb.Now(),
|
ChangeDate: timestamppb.Now(),
|
||||||
ResourceOwner: Instance.ID(),
|
ResourceOwner: Instance.ID(),
|
||||||
},
|
},
|
||||||
url: "http://" + Instance.Domain + ":8000/sso",
|
url: "http://localhost:8000/sso",
|
||||||
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
@@ -2106,7 +2106,9 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
|||||||
ChangeDate: timestamppb.Now(),
|
ChangeDate: timestamppb.Now(),
|
||||||
ResourceOwner: Instance.ID(),
|
ResourceOwner: Instance.ID(),
|
||||||
},
|
},
|
||||||
postForm: true,
|
url: "http://localhost:8000/sso",
|
||||||
|
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
||||||
|
postForm: true,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
@@ -2120,9 +2122,11 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if tt.want.url != "" {
|
if tt.want.url != "" && !tt.want.postForm {
|
||||||
authUrl, err := url.Parse(got.GetAuthUrl())
|
authUrl, err := url.Parse(got.GetAuthUrl())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want.url, authUrl.Scheme+"://"+authUrl.Host+authUrl.Path)
|
||||||
require.Len(t, authUrl.Query(), len(tt.want.parametersEqual)+len(tt.want.parametersExisting))
|
require.Len(t, authUrl.Query(), len(tt.want.parametersEqual)+len(tt.want.parametersExisting))
|
||||||
|
|
||||||
for _, existing := range tt.want.parametersExisting {
|
for _, existing := range tt.want.parametersExisting {
|
||||||
@@ -2133,7 +2137,15 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tt.want.postForm {
|
if tt.want.postForm {
|
||||||
assert.NotEmpty(t, got.GetPostForm())
|
assert.Equal(t, tt.want.url, got.GetFormData().GetUrl())
|
||||||
|
|
||||||
|
require.Len(t, got.GetFormData().GetFields(), len(tt.want.parametersEqual)+len(tt.want.parametersExisting))
|
||||||
|
for _, existing := range tt.want.parametersExisting {
|
||||||
|
assert.Contains(t, got.GetFormData().GetFields(), existing)
|
||||||
|
}
|
||||||
|
for key, equal := range tt.want.parametersEqual {
|
||||||
|
assert.Equal(t, got.GetFormData().GetFields()[key], equal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
integration.AssertDetails(t, &user.StartIdentityProviderIntentResponse{
|
integration.AssertDetails(t, &user.StartIdentityProviderIntentResponse{
|
||||||
Details: tt.want.details,
|
Details: tt.want.details,
|
||||||
|
@@ -380,19 +380,28 @@ func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.Re
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
content, redirect := session.GetAuth(ctx)
|
auth, err := session.GetAuth(ctx)
|
||||||
if redirect {
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch a := auth.(type) {
|
||||||
|
case *idp.RedirectAuth:
|
||||||
return &user.StartIdentityProviderIntentResponse{
|
return &user.StartIdentityProviderIntentResponse{
|
||||||
Details: object.DomainToDetailsPb(details),
|
Details: object.DomainToDetailsPb(details),
|
||||||
NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: content},
|
NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: a.RedirectURL},
|
||||||
|
}, nil
|
||||||
|
case *idp.FormAuth:
|
||||||
|
return &user.StartIdentityProviderIntentResponse{
|
||||||
|
Details: object.DomainToDetailsPb(details),
|
||||||
|
NextStep: &user.StartIdentityProviderIntentResponse_FormData{
|
||||||
|
FormData: &user.FormData{
|
||||||
|
Url: a.URL,
|
||||||
|
Fields: a.Fields,
|
||||||
|
},
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return &user.StartIdentityProviderIntentResponse{
|
return nil, zerrors.ThrowInvalidArgumentf(nil, "USERv2-3g2j3", "type oneOf %T in method StartIdentityProviderIntent not implemented", auth)
|
||||||
Details: object.DomainToDetailsPb(details),
|
|
||||||
NextStep: &user.StartIdentityProviderIntentResponse_PostForm{
|
|
||||||
PostForm: []byte(content),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*user.StartIdentityProviderIntentResponse, error) {
|
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*user.StartIdentityProviderIntentResponse, error) {
|
||||||
|
@@ -48,6 +48,18 @@ const (
|
|||||||
tmplExternalNotFoundOption = "externalnotfoundoption"
|
tmplExternalNotFoundOption = "externalnotfoundoption"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
samlFormPost = template.Must(template.New("saml-post-form").Parse(`<!DOCTYPE html><html><body>
|
||||||
|
<form method="post" action="{{.URL}}" id="SAMLRequestForm">
|
||||||
|
{{range $key, $value := .Fields}}
|
||||||
|
<input type="hidden" name="{{$key}}" value="{{$value}}" />
|
||||||
|
{{end}}
|
||||||
|
<input id="SAMLSubmitButton" type="submit" value="Submit" />
|
||||||
|
</form>
|
||||||
|
<script>document.getElementById('SAMLSubmitButton').style.visibility="hidden";document.getElementById('SAMLRequestForm').submit();</script>
|
||||||
|
</body></html>`))
|
||||||
|
)
|
||||||
|
|
||||||
type externalIDPData struct {
|
type externalIDPData struct {
|
||||||
IDPConfigID string `schema:"idpConfigID"`
|
IDPConfigID string `schema:"idpConfigID"`
|
||||||
}
|
}
|
||||||
@@ -201,15 +213,21 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
|
|||||||
l.externalAuthFailed(w, r, authReq, err)
|
l.externalAuthFailed(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
auth, err := session.GetAuth(r.Context())
|
||||||
content, redirect := session.GetAuth(r.Context())
|
if err != nil {
|
||||||
if redirect {
|
l.renderInternalError(w, r, authReq, err)
|
||||||
http.Redirect(w, r, content, http.StatusFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = w.Write([]byte(content))
|
switch a := auth.(type) {
|
||||||
if err != nil {
|
case *idp.RedirectAuth:
|
||||||
l.renderError(w, r, authReq, err)
|
http.Redirect(w, r, a.RedirectURL, http.StatusFound)
|
||||||
|
return
|
||||||
|
case *idp.FormAuth:
|
||||||
|
err = samlFormPost.Execute(w, a)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -432,9 +432,8 @@ func TestCommands_AuthFromProvider(t *testing.T) {
|
|||||||
samlRootURL string
|
samlRootURL string
|
||||||
}
|
}
|
||||||
type res struct {
|
type res struct {
|
||||||
content string
|
auth idp.Auth
|
||||||
redirect bool
|
err error
|
||||||
err error
|
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -579,8 +578,7 @@ func TestCommands_AuthFromProvider(t *testing.T) {
|
|||||||
callbackURL: "url",
|
callbackURL: "url",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
content: "auth?client_id=clientID&prompt=select_account&redirect_uri=url&response_type=code&state=id",
|
auth: &idp.RedirectAuth{RedirectURL: "auth?client_id=clientID&prompt=select_account&redirect_uri=url&response_type=code&state=id"},
|
||||||
redirect: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -671,8 +669,7 @@ func TestCommands_AuthFromProvider(t *testing.T) {
|
|||||||
callbackURL: "url",
|
callbackURL: "url",
|
||||||
},
|
},
|
||||||
res{
|
res{
|
||||||
content: "https://login.microsoftonline.com/tenant/oauth2/v2.0/authorize?client_id=clientID&prompt=select_account&redirect_uri=url&response_type=code&scope=openid+profile+User.Read&state=id",
|
auth: &idp.RedirectAuth{RedirectURL: "https://login.microsoftonline.com/tenant/oauth2/v2.0/authorize?client_id=clientID&prompt=select_account&redirect_uri=url&response_type=code&scope=openid+profile+User.Read&state=id"},
|
||||||
redirect: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -686,13 +683,12 @@ func TestCommands_AuthFromProvider(t *testing.T) {
|
|||||||
_, session, err := c.AuthFromProvider(tt.args.ctx, tt.args.idpID, tt.args.callbackURL, tt.args.samlRootURL)
|
_, session, err := c.AuthFromProvider(tt.args.ctx, tt.args.idpID, tt.args.callbackURL, tt.args.samlRootURL)
|
||||||
require.ErrorIs(t, err, tt.res.err)
|
require.ErrorIs(t, err, tt.res.err)
|
||||||
|
|
||||||
var content string
|
var got idp.Auth
|
||||||
var redirect bool
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
content, redirect = session.GetAuth(tt.args.ctx)
|
got, err = session.GetAuth(tt.args.ctx)
|
||||||
|
assert.Equal(t, tt.res.auth, got)
|
||||||
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, tt.res.redirect, redirect)
|
|
||||||
assert.Equal(t, tt.res.content, content)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -811,6 +807,97 @@ func TestCommands_AuthFromProvider_SAML(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"saml post auth",
|
||||||
|
fields{
|
||||||
|
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusherWithInstanceID(
|
||||||
|
"instance",
|
||||||
|
instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance").Aggregate,
|
||||||
|
"idp",
|
||||||
|
"name",
|
||||||
|
[]byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-08-27T12:40:58.803Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("key"),
|
||||||
|
},
|
||||||
|
[]byte("certificate"),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
gu.Ptr(domain.SAMLNameIDFormatUnspecified),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
rep_idp.Options{},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusherWithInstanceID(
|
||||||
|
"instance",
|
||||||
|
instance.NewSAMLIDPAddedEvent(context.Background(), &instance.NewAggregate("instance").Aggregate,
|
||||||
|
"idp",
|
||||||
|
"name",
|
||||||
|
[]byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-08-27T12:40:58.803Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"),
|
||||||
|
}, []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
gu.Ptr(domain.SAMLNameIDFormatUnspecified),
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
rep_idp.Options{},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusherWithInstanceID(
|
||||||
|
"instance",
|
||||||
|
func() eventstore.Command {
|
||||||
|
success, _ := url.Parse("https://success.url")
|
||||||
|
failure, _ := url.Parse("https://failure.url")
|
||||||
|
return idpintent.NewStartedEvent(
|
||||||
|
context.Background(),
|
||||||
|
&idpintent.NewAggregate("id", "instance").Aggregate,
|
||||||
|
success,
|
||||||
|
failure,
|
||||||
|
"idp",
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
}(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectRandomPush(
|
||||||
|
[]eventstore.Command{
|
||||||
|
idpintent.NewSAMLRequestEvent(
|
||||||
|
context.Background(),
|
||||||
|
&idpintent.NewAggregate("id", "instance").Aggregate,
|
||||||
|
"request",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
idGenerator: mock.ExpectID(t, "id"),
|
||||||
|
},
|
||||||
|
args{
|
||||||
|
ctx: authz.SetCtxData(context.Background(), authz.CtxData{OrgID: "ro"}),
|
||||||
|
idpID: "idp",
|
||||||
|
callbackURL: "url",
|
||||||
|
samlRootURL: "samlurl",
|
||||||
|
},
|
||||||
|
res{
|
||||||
|
url: "http://localhost:8000/sso",
|
||||||
|
values: map[string]string{
|
||||||
|
"SAMLRequest": "", // generated IDs so not assertable
|
||||||
|
"RelayState": "id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@@ -822,16 +909,30 @@ func TestCommands_AuthFromProvider_SAML(t *testing.T) {
|
|||||||
_, session, err := c.AuthFromProvider(tt.args.ctx, tt.args.idpID, tt.args.callbackURL, tt.args.samlRootURL)
|
_, session, err := c.AuthFromProvider(tt.args.ctx, tt.args.idpID, tt.args.callbackURL, tt.args.samlRootURL)
|
||||||
require.ErrorIs(t, err, tt.res.err)
|
require.ErrorIs(t, err, tt.res.err)
|
||||||
|
|
||||||
content, _ := session.GetAuth(tt.args.ctx)
|
auth, err := session.GetAuth(tt.args.ctx)
|
||||||
authURL, err := url.Parse(content)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var authURL *url.URL
|
||||||
|
authFields := make(map[string]string)
|
||||||
|
|
||||||
|
switch a := auth.(type) {
|
||||||
|
case *idp.RedirectAuth:
|
||||||
|
authURL, err = url.Parse(a.RedirectURL)
|
||||||
|
for key, values := range authURL.Query() {
|
||||||
|
authFields[key] = values[0]
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
case *idp.FormAuth:
|
||||||
|
authURL, err = url.Parse(a.URL)
|
||||||
|
require.NoError(t, err)
|
||||||
|
authFields = a.Fields
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(t, tt.res.url, authURL.Scheme+"://"+authURL.Host+authURL.Path)
|
assert.Equal(t, tt.res.url, authURL.Scheme+"://"+authURL.Host+authURL.Path)
|
||||||
query := authURL.Query()
|
|
||||||
for k, v := range tt.res.values {
|
for k, v := range tt.res.values {
|
||||||
assert.True(t, query.Has(k))
|
assert.Contains(t, authFields, k)
|
||||||
if v != "" {
|
if v != "" {
|
||||||
assert.Equal(t, v, query.Get(k))
|
assert.Equal(t, v, authFields[k])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -62,10 +62,10 @@ func TestProvider_BeginAuth(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
session, err := provider.BeginAuth(ctx, "testState")
|
session, err := provider.BeginAuth(ctx, "testState")
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
content, redirect := session.GetAuth(ctx)
|
auth, err := session.GetAuth(ctx)
|
||||||
contentExpected, redirectExpected := tt.want.GetAuth(ctx)
|
authExpected, errExpected := tt.want.GetAuth(ctx)
|
||||||
a.Equal(redirectExpected, redirect)
|
a.ErrorIs(err, errExpected)
|
||||||
a.Equal(contentExpected, content)
|
a.Equal(authExpected, auth)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -81,10 +81,10 @@ func TestProvider_BeginAuth(t *testing.T) {
|
|||||||
session, err := provider.BeginAuth(ctx, "testState")
|
session, err := provider.BeginAuth(ctx, "testState")
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
wantHeaders, wantContent := tt.want.GetAuth(ctx)
|
wantAuth, wantErr := tt.want.GetAuth(ctx)
|
||||||
gotHeaders, gotContent := session.GetAuth(ctx)
|
gotAuth, gotErr := session.GetAuth(ctx)
|
||||||
a.Equal(wantHeaders, gotHeaders)
|
a.Equal(wantAuth, gotAuth)
|
||||||
a.Equal(wantContent, gotContent)
|
a.ErrorIs(gotErr, wantErr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,7 +28,7 @@ func NewSession(provider *Provider, code string) *Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAuth implements the [idp.Provider] interface by calling the wrapped [oauth.Session].
|
// GetAuth implements the [idp.Provider] interface by calling the wrapped [oauth.Session].
|
||||||
func (s *Session) GetAuth(ctx context.Context) (content string, redirect bool) {
|
func (s *Session) GetAuth(ctx context.Context) (idp.Auth, error) {
|
||||||
return s.oauth().GetAuth(ctx)
|
return s.oauth().GetAuth(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -48,10 +48,10 @@ func TestProvider_BeginAuth(t *testing.T) {
|
|||||||
session, err := provider.BeginAuth(ctx, "testState")
|
session, err := provider.BeginAuth(ctx, "testState")
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
wantHeaders, wantContent := tt.want.GetAuth(ctx)
|
wantAuth, wantErr := tt.want.GetAuth(ctx)
|
||||||
gotHeaders, gotContent := session.GetAuth(ctx)
|
gotAuth, gotErr := session.GetAuth(ctx)
|
||||||
a.Equal(wantHeaders, gotHeaders)
|
a.Equal(wantAuth, gotAuth)
|
||||||
a.Equal(wantContent, gotContent)
|
a.ErrorIs(gotErr, wantErr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -59,10 +59,10 @@ func TestProvider_BeginAuth(t *testing.T) {
|
|||||||
session, err := provider.BeginAuth(ctx, "testState")
|
session, err := provider.BeginAuth(ctx, "testState")
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
wantHeaders, wantContent := tt.want.GetAuth(ctx)
|
wantAuth, wantErr := tt.want.GetAuth(ctx)
|
||||||
gotHeaders, gotContent := session.GetAuth(ctx)
|
gotAuth, gotErr := session.GetAuth(ctx)
|
||||||
a.Equal(wantHeaders, gotHeaders)
|
a.Equal(wantAuth, gotAuth)
|
||||||
a.Equal(wantContent, gotContent)
|
a.ErrorIs(gotErr, wantErr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -48,10 +48,10 @@ func TestProvider_BeginAuth(t *testing.T) {
|
|||||||
session, err := provider.BeginAuth(ctx, "testState")
|
session, err := provider.BeginAuth(ctx, "testState")
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
wantHeaders, wantContent := tt.want.GetAuth(ctx)
|
wantAuth, wantErr := tt.want.GetAuth(ctx)
|
||||||
gotHeaders, gotContent := session.GetAuth(ctx)
|
gotAuth, gotErr := session.GetAuth(ctx)
|
||||||
a.Equal(wantHeaders, gotHeaders)
|
a.Equal(wantAuth, gotAuth)
|
||||||
a.Equal(wantContent, gotContent)
|
a.ErrorIs(gotErr, wantErr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -119,10 +119,10 @@ func TestProvider_BeginAuth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
if tt.want.err == nil {
|
if tt.want.err == nil {
|
||||||
a.NoError(err)
|
a.NoError(err)
|
||||||
wantHeaders, wantContent := tt.want.session.GetAuth(ctx)
|
wantAuth, wantErr := tt.want.session.GetAuth(ctx)
|
||||||
gotHeaders, gotContent := session.GetAuth(ctx)
|
gotAuth, gotErr := session.GetAuth(ctx)
|
||||||
a.Equal(wantHeaders, gotHeaders)
|
a.Equal(wantAuth, gotAuth)
|
||||||
a.Equal(wantContent, gotContent)
|
a.ErrorIs(gotErr, wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -42,7 +42,7 @@ func NewSessionFromRequest(provider *Provider, r *http.Request) *Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAuth implements the [idp.Session] interface.
|
// GetAuth implements the [idp.Session] interface.
|
||||||
func (s *Session) GetAuth(ctx context.Context) (string, bool) {
|
func (s *Session) GetAuth(ctx context.Context) (idp.Auth, error) {
|
||||||
return idp.Redirect(s.AuthURL)
|
return idp.Redirect(s.AuthURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -39,7 +39,7 @@ func NewSession(provider *Provider, username, password string) *Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAuth implements the [idp.Session] interface.
|
// GetAuth implements the [idp.Session] interface.
|
||||||
func (s *Session) GetAuth(ctx context.Context) (string, bool) {
|
func (s *Session) GetAuth(ctx context.Context) (idp.Auth, error) {
|
||||||
return idp.Redirect(s.loginUrl)
|
return idp.Redirect(s.loginUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -80,10 +80,10 @@ func TestProvider_BeginAuth(t *testing.T) {
|
|||||||
session, err := provider.BeginAuth(ctx, "testState")
|
session, err := provider.BeginAuth(ctx, "testState")
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
wantHeaders, wantContent := tt.want.GetAuth(ctx)
|
wantAuth, wantErr := tt.want.GetAuth(ctx)
|
||||||
gotHeaders, gotContent := session.GetAuth(ctx)
|
gotAuth, gotErr := session.GetAuth(ctx)
|
||||||
a.Equal(wantHeaders, gotHeaders)
|
a.Equal(wantAuth, gotAuth)
|
||||||
a.Equal(wantContent, gotContent)
|
a.ErrorIs(gotErr, wantErr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -37,7 +37,7 @@ func NewSession(provider *Provider, code string, idpArguments map[string]any) *S
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAuth implements the [idp.Session] interface.
|
// GetAuth implements the [idp.Session] interface.
|
||||||
func (s *Session) GetAuth(ctx context.Context) (string, bool) {
|
func (s *Session) GetAuth(ctx context.Context) (idp.Auth, error) {
|
||||||
return idp.Redirect(s.AuthURL)
|
return idp.Redirect(s.AuthURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -98,10 +98,10 @@ func TestProvider_BeginAuth(t *testing.T) {
|
|||||||
session, err := provider.BeginAuth(ctx, "testState")
|
session, err := provider.BeginAuth(ctx, "testState")
|
||||||
r.NoError(err)
|
r.NoError(err)
|
||||||
|
|
||||||
wantHeaders, wantContent := tt.want.GetAuth(ctx)
|
wantAuth, wantErr := tt.want.GetAuth(ctx)
|
||||||
gotHeaders, gotContent := session.GetAuth(ctx)
|
gotAuth, gotErr := session.GetAuth(ctx)
|
||||||
a.Equal(wantHeaders, gotHeaders)
|
a.Equal(wantAuth, gotAuth)
|
||||||
a.Equal(wantContent, gotContent)
|
a.ErrorIs(gotErr, wantErr)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -33,7 +33,7 @@ func NewSession(provider *Provider, code string, idpArguments map[string]any) *S
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAuth implements the [idp.Session] interface.
|
// GetAuth implements the [idp.Session] interface.
|
||||||
func (s *Session) GetAuth(ctx context.Context) (string, bool) {
|
func (s *Session) GetAuth(ctx context.Context) (idp.Auth, error) {
|
||||||
return idp.Redirect(s.AuthURL)
|
return idp.Redirect(s.AuthURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
package saml
|
package saml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,10 +13,138 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/idp"
|
||||||
"github.com/zitadel/zitadel/internal/idp/providers/saml/requesttracker"
|
"github.com/zitadel/zitadel/internal/idp/providers/saml/requesttracker"
|
||||||
"github.com/zitadel/zitadel/internal/zerrors"
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestProvider_BeginAuth(t *testing.T) {
|
||||||
|
requestTracker := requesttracker.New(
|
||||||
|
func(ctx context.Context, authRequestID, samlRequestID string) error {
|
||||||
|
assert.Equal(t, "state", authRequestID)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
func(ctx context.Context, authRequestID string) (*samlsp.TrackedRequest, error) {
|
||||||
|
return &samlsp.TrackedRequest{
|
||||||
|
SAMLRequestID: "state",
|
||||||
|
Index: authRequestID,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
type fields struct {
|
||||||
|
name string
|
||||||
|
rootURL string
|
||||||
|
metadata []byte
|
||||||
|
certificate []byte
|
||||||
|
key []byte
|
||||||
|
options []ProviderOpts
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
state string
|
||||||
|
}
|
||||||
|
type want struct {
|
||||||
|
err func(error) bool
|
||||||
|
authType idp.Auth
|
||||||
|
ssoURL string
|
||||||
|
relayState string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want want
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "redirect binding, success",
|
||||||
|
fields: fields{
|
||||||
|
name: "saml",
|
||||||
|
rootURL: "https://localhost:8080",
|
||||||
|
metadata: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-08-27T12:40:58.803Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||||
|
certificate: []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"),
|
||||||
|
key: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"),
|
||||||
|
options: []ProviderOpts{
|
||||||
|
WithCustomRequestTracker(requestTracker),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
state: "state",
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
authType: &idp.RedirectAuth{},
|
||||||
|
ssoURL: "http://localhost:8000/sso",
|
||||||
|
relayState: "state",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post binding, success",
|
||||||
|
fields: fields{
|
||||||
|
name: "saml",
|
||||||
|
rootURL: "https://localhost:8080",
|
||||||
|
metadata: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-08-27T12:40:58.803Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||||
|
certificate: []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"),
|
||||||
|
key: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"),
|
||||||
|
options: []ProviderOpts{
|
||||||
|
WithCustomRequestTracker(requestTracker),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
state: "state",
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
authType: &idp.FormAuth{},
|
||||||
|
ssoURL: "http://localhost:8000/sso",
|
||||||
|
relayState: "state",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
a := assert.New(t)
|
||||||
|
|
||||||
|
provider, err := New(
|
||||||
|
tt.fields.name,
|
||||||
|
tt.fields.rootURL,
|
||||||
|
tt.fields.metadata,
|
||||||
|
tt.fields.certificate,
|
||||||
|
tt.fields.key,
|
||||||
|
tt.fields.options...,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
session, err := provider.BeginAuth(ctx, tt.args.state, nil)
|
||||||
|
if tt.want.err != nil && !tt.want.err(err) {
|
||||||
|
a.Fail("invalid error", err)
|
||||||
|
}
|
||||||
|
if tt.want.err == nil {
|
||||||
|
a.NoError(err)
|
||||||
|
gotAuth, gotErr := session.GetAuth(ctx)
|
||||||
|
a.NoError(gotErr)
|
||||||
|
a.IsType(tt.want.authType, gotAuth)
|
||||||
|
|
||||||
|
var ssoURL, relayState, samlRequest string
|
||||||
|
switch auth := gotAuth.(type) {
|
||||||
|
case *idp.RedirectAuth:
|
||||||
|
gotRedirect, err := url.Parse(auth.RedirectURL)
|
||||||
|
a.NoError(err)
|
||||||
|
gotQuery := gotRedirect.Query()
|
||||||
|
|
||||||
|
ssoURL = gotRedirect.Scheme + "://" + gotRedirect.Host + gotRedirect.Path
|
||||||
|
relayState = gotQuery.Get("RelayState")
|
||||||
|
samlRequest = gotQuery.Get("SAMLRequest")
|
||||||
|
case *idp.FormAuth:
|
||||||
|
ssoURL = auth.URL
|
||||||
|
relayState = auth.Fields["RelayState"]
|
||||||
|
samlRequest = auth.Fields["SAMLRequest"]
|
||||||
|
}
|
||||||
|
a.Equal(tt.want.ssoURL, ssoURL)
|
||||||
|
a.Equal(tt.want.relayState, relayState)
|
||||||
|
a.NotEmpty(samlRequest)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProvider_Options(t *testing.T) {
|
func TestProvider_Options(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
name string
|
name string
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
package saml
|
package saml
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/beevik/etree"
|
||||||
"github.com/crewjam/saml"
|
"github.com/crewjam/saml"
|
||||||
"github.com/crewjam/saml/samlsp"
|
"github.com/crewjam/saml/samlsp"
|
||||||
|
|
||||||
@@ -43,22 +44,15 @@ func NewSession(provider *Provider, requestID string, request *http.Request) (*S
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetAuth implements the [idp.Session] interface.
|
// GetAuth implements the [idp.Session] interface.
|
||||||
func (s *Session) GetAuth(ctx context.Context) (string, bool) {
|
func (s *Session) GetAuth(ctx context.Context) (idp.Auth, error) {
|
||||||
url, _ := url.Parse(s.state)
|
url, err := url.Parse(s.state)
|
||||||
resp := NewTempResponseWriter()
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
request := &http.Request{
|
request := &http.Request{
|
||||||
URL: url,
|
URL: url,
|
||||||
}
|
}
|
||||||
s.ServiceProvider.HandleStartAuthFlow(
|
return s.auth(request.WithContext(ctx))
|
||||||
resp,
|
|
||||||
request.WithContext(ctx),
|
|
||||||
)
|
|
||||||
|
|
||||||
if location := resp.Header().Get("Location"); location != "" {
|
|
||||||
return idp.Redirect(location)
|
|
||||||
}
|
|
||||||
return idp.Form(resp.content.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PersistentParameters implements the [idp.Session] interface.
|
// PersistentParameters implements the [idp.Session] interface.
|
||||||
@@ -130,24 +124,57 @@ func (s *Session) transientMappingID() (string, error) {
|
|||||||
return "", zerrors.ThrowInvalidArgument(nil, "SAML-swwg2", "Errors.Intent.MissingSingleMappingAttribute")
|
return "", zerrors.ThrowInvalidArgument(nil, "SAML-swwg2", "Errors.Intent.MissingSingleMappingAttribute")
|
||||||
}
|
}
|
||||||
|
|
||||||
type TempResponseWriter struct {
|
// auth is a modified copy of the [samlsp.Middleware.HandleStartAuthFlow] method.
|
||||||
header http.Header
|
// Instead of writing the response to the http.ResponseWriter, it returns the auth request as an [idp.Auth].
|
||||||
content *bytes.Buffer
|
// In case of an error, it returns the error directly and does not write to the response.
|
||||||
}
|
func (s *Session) auth(r *http.Request) (idp.Auth, error) {
|
||||||
|
if r.URL.Path == s.ServiceProvider.ServiceProvider.AcsURL.Path {
|
||||||
func (w *TempResponseWriter) Header() http.Header {
|
// should never occur, but was handled in the original method, so we keep it here
|
||||||
return w.header
|
return nil, zerrors.ThrowInvalidArgument(nil, "SAML-Eoi24", "don't wrap Middleware with RequireAccount")
|
||||||
}
|
|
||||||
|
|
||||||
func (w *TempResponseWriter) Write(content []byte) (int, error) {
|
|
||||||
return w.content.Write(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *TempResponseWriter) WriteHeader(statusCode int) {}
|
|
||||||
|
|
||||||
func NewTempResponseWriter() *TempResponseWriter {
|
|
||||||
return &TempResponseWriter{
|
|
||||||
header: map[string][]string{},
|
|
||||||
content: bytes.NewBuffer([]byte{}),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var binding, bindingLocation string
|
||||||
|
if s.ServiceProvider.Binding != "" {
|
||||||
|
binding = s.ServiceProvider.Binding
|
||||||
|
bindingLocation = s.ServiceProvider.ServiceProvider.GetSSOBindingLocation(binding)
|
||||||
|
} else {
|
||||||
|
binding = saml.HTTPRedirectBinding
|
||||||
|
bindingLocation = s.ServiceProvider.ServiceProvider.GetSSOBindingLocation(binding)
|
||||||
|
if bindingLocation == "" {
|
||||||
|
binding = saml.HTTPPostBinding
|
||||||
|
bindingLocation = s.ServiceProvider.ServiceProvider.GetSSOBindingLocation(binding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
authReq, err := s.ServiceProvider.ServiceProvider.MakeAuthenticationRequest(bindingLocation, binding, s.ServiceProvider.ResponseBinding)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
relayState, err := s.ServiceProvider.RequestTracker.TrackRequest(nil, r, authReq.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if binding == saml.HTTPRedirectBinding {
|
||||||
|
redirectURL, err := authReq.Redirect(relayState, &s.ServiceProvider.ServiceProvider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return idp.Redirect(redirectURL.String())
|
||||||
|
}
|
||||||
|
if binding == saml.HTTPPostBinding {
|
||||||
|
doc := etree.NewDocument()
|
||||||
|
doc.SetRoot(authReq.Element())
|
||||||
|
reqBuf, err := doc.WriteToBytes()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
encodedReqBuf := base64.StdEncoding.EncodeToString(reqBuf)
|
||||||
|
return idp.Form(authReq.Destination,
|
||||||
|
map[string]string{
|
||||||
|
"SAMLRequest": encodedReqBuf,
|
||||||
|
"RelayState": relayState,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "SAML-Eoi24", "Errors.Intent.Invalid")
|
||||||
}
|
}
|
||||||
|
@@ -7,12 +7,29 @@ import (
|
|||||||
|
|
||||||
// Session is the minimal implementation for a session of a 3rd party authentication [Provider]
|
// Session is the minimal implementation for a session of a 3rd party authentication [Provider]
|
||||||
type Session interface {
|
type Session interface {
|
||||||
GetAuth(ctx context.Context) (content string, redirect bool)
|
GetAuth(ctx context.Context) (Auth, error)
|
||||||
PersistentParameters() map[string]any
|
PersistentParameters() map[string]any
|
||||||
FetchUser(ctx context.Context) (User, error)
|
FetchUser(ctx context.Context) (User, error)
|
||||||
ExpiresAt() time.Time
|
ExpiresAt() time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Auth interface {
|
||||||
|
auth()
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedirectAuth struct {
|
||||||
|
RedirectURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RedirectAuth) auth() {}
|
||||||
|
|
||||||
|
type FormAuth struct {
|
||||||
|
URL string
|
||||||
|
Fields map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FormAuth) auth() {}
|
||||||
|
|
||||||
// SessionSupportsMigration is an optional extension to the Session interface.
|
// SessionSupportsMigration is an optional extension to the Session interface.
|
||||||
// It can be implemented to support migrating users, were the initial external id has changed because of a migration of the Provider type.
|
// It can be implemented to support migrating users, were the initial external id has changed because of a migration of the Provider type.
|
||||||
// E.g. when a user was linked on a generic OIDC provider and this provider has now been migrated to an AzureAD provider.
|
// E.g. when a user was linked on a generic OIDC provider and this provider has now been migrated to an AzureAD provider.
|
||||||
@@ -22,10 +39,13 @@ type SessionSupportsMigration interface {
|
|||||||
RetrievePreviousID() (previousID string, err error)
|
RetrievePreviousID() (previousID string, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Redirect(redirectURL string) (string, bool) {
|
func Redirect(redirectURL string) (*RedirectAuth, error) {
|
||||||
return redirectURL, true
|
return &RedirectAuth{RedirectURL: redirectURL}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Form(html string) (string, bool) {
|
func Form(url string, fields map[string]string) (*FormAuth, error) {
|
||||||
return html, false
|
return &FormAuth{
|
||||||
|
URL: url,
|
||||||
|
Fields: fields,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
@@ -162,3 +162,21 @@ message IDPLink {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message FormData {
|
||||||
|
// The URL to which the form should be submitted using the POST method.
|
||||||
|
string url = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"https://idp.com/saml/v2/acs\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// The form fields to be submitted.
|
||||||
|
// Each field is represented as a key-value pair, where the key is the field / input name
|
||||||
|
// and the value is the field / input value.
|
||||||
|
// All fields need to be submitted as is and as input type "text".
|
||||||
|
map<string, string> fields = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "{\"relayState\":\"state\",\"SAMLRequest\":\"asjfkj3ir2fj248=\"}";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
@@ -2895,11 +2895,15 @@ message StartIdentityProviderIntentResponse{
|
|||||||
description: "IDP Intent information"
|
description: "IDP Intent information"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
// POST call information
|
||||||
|
// Deprecated: Use form_data instead
|
||||||
bytes post_form = 4 [
|
bytes post_form = 4 [
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
description: "POST call information"
|
description: "POST call information"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
// Data for a form POST call
|
||||||
|
FormData form_data = 5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -162,3 +162,21 @@ message IDPLink {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message FormData {
|
||||||
|
// The URL to which the form should be submitted using the POST method.
|
||||||
|
string url = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"https://idp.com/saml/v2/acs\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// The form fields to be submitted.
|
||||||
|
// Each field is represented as a key-value pair, where the key is the field / input name
|
||||||
|
// and the value is the field / input value.
|
||||||
|
// All fields need to be submitted as is and as input type "text".
|
||||||
|
map<string, string> fields = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "{\"relayState\":\"state\",\"SAMLRequest\":\"asjfkj3ir2fj248=\"}";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
@@ -1788,22 +1788,23 @@ message StartIdentityProviderIntentRequest{
|
|||||||
message StartIdentityProviderIntentResponse{
|
message StartIdentityProviderIntentResponse{
|
||||||
zitadel.object.v2beta.Details details = 1;
|
zitadel.object.v2beta.Details details = 1;
|
||||||
oneof next_step {
|
oneof next_step {
|
||||||
|
// URL to which the client should redirect
|
||||||
string auth_url = 2 [
|
string auth_url = 2 [
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
description: "URL to which the client should redirect"
|
|
||||||
example: "\"https://accounts.google.com/o/oauth2/v2/auth?client_id=clientID&callback=https%3A%2F%2Fzitadel.cloud%2Fidps%2Fcallback\"";
|
example: "\"https://accounts.google.com/o/oauth2/v2/auth?client_id=clientID&callback=https%3A%2F%2Fzitadel.cloud%2Fidps%2Fcallback\"";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
IDPIntent idp_intent = 3 [
|
// IDP Intent information
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
IDPIntent idp_intent = 3;
|
||||||
description: "IDP Intent information"
|
// POST call information
|
||||||
}
|
// Deprecated: Use form_data instead
|
||||||
];
|
|
||||||
bytes post_form = 4 [
|
bytes post_form = 4 [
|
||||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
description: "POST call information"
|
description: "POST call information"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
// Data for a form POST call
|
||||||
|
FormData form_data = 5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user