diff --git a/pkg/provider/attribute_query.go b/pkg/provider/attribute_query.go
index 00ef547..a17f842 100644
--- a/pkg/provider/attribute_query.go
+++ b/pkg/provider/attribute_query.go
@@ -128,7 +128,7 @@ func (p *IdentityProvider) attributeQueryHandleFunc(w http.ResponseWriter, r *ht
queriedAttrs = append(queriedAttrs, queriedAttr)
}
}
- response = makeAttributeQueryResponse(attrQuery.Id, p.GetEntityID(r.Context()), sp.GetEntityID(), attrs, queriedAttrs, p.timeFormat)
+ response = makeAttributeQueryResponse(attrQuery.Id, p.GetEntityID(r.Context()), sp.GetEntityID(), attrs, queriedAttrs, p.TimeFormat, p.Expiration)
return nil
},
func() {
@@ -139,7 +139,11 @@ func (p *IdentityProvider) attributeQueryHandleFunc(w http.ResponseWriter, r *ht
// create enveloped signature
checkerInstance.WithLogicStep(
func() error {
- return createPostSignature(r.Context(), response, p)
+ cert, key, err := getResponseCert(r.Context(), p.storage)
+ if err != nil {
+ return err
+ }
+ return createPostSignature(response, key, cert, p.conf.SignatureAlgorithm)
},
func() {
http.Error(w, fmt.Errorf("failed to sign response: %w", err).Error(), http.StatusInternalServerError)
diff --git a/pkg/provider/identityprovider.go b/pkg/provider/identityprovider.go
index 3a7c024..13853da 100644
--- a/pkg/provider/identityprovider.go
+++ b/pkg/provider/identityprovider.go
@@ -68,7 +68,8 @@ type IdentityProvider struct {
metadataEndpoint *Endpoint
endpoints *Endpoints
- timeFormat string
+ TimeFormat string
+ Expiration time.Duration
}
type Endpoints struct {
@@ -97,7 +98,8 @@ func NewIdentityProvider(metadata Endpoint, conf *IdentityProviderConfig, storag
postTemplate: postTemplate,
logoutTemplate: logoutTemplate,
endpoints: endpointConfigToEndpoints(conf.Endpoints),
- timeFormat: DefaultTimeFormat,
+ TimeFormat: DefaultTimeFormat,
+ Expiration: DefaultExpiration,
}
if conf.MetadataIDPConfig == nil {
@@ -153,7 +155,7 @@ func (p *IdentityProvider) GetMetadata(ctx context.Context) (*md.IDPSSODescripto
return nil, nil, err
}
- metadata, aaMetadata := p.conf.getMetadata(ctx, p.GetEntityID(ctx), cert, p.timeFormat)
+ metadata, aaMetadata := p.conf.getMetadata(ctx, p.GetEntityID(ctx), cert, p.TimeFormat)
return metadata, aaMetadata, nil
}
diff --git a/pkg/provider/login.go b/pkg/provider/login.go
index 4b83a8a..435f951 100644
--- a/pkg/provider/login.go
+++ b/pkg/provider/login.go
@@ -1,10 +1,14 @@
package provider
import (
+ "context"
"fmt"
"net/http"
"github.com/zitadel/logging"
+
+ "github.com/zitadel/saml/pkg/provider/models"
+ "github.com/zitadel/saml/pkg/provider/xml/samlp"
)
func (p *IdentityProvider) callbackHandleFunc(w http.ResponseWriter, r *http.Request) {
@@ -16,7 +20,6 @@ func (p *IdentityProvider) callbackHandleFunc(w http.ResponseWriter, r *http.Req
Issuer: p.GetEntityID(r.Context()),
}
- ctx := r.Context()
if err := r.ParseForm(); err != nil {
logging.Error(err)
http.Error(w, fmt.Errorf("failed to parse form: %w", err).Error(), http.StatusInternalServerError)
@@ -34,7 +37,7 @@ func (p *IdentityProvider) callbackHandleFunc(w http.ResponseWriter, r *http.Req
authRequest, err := p.storage.AuthRequestByID(r.Context(), requestID)
if err != nil {
logging.Error(err)
- response.sendBackResponse(r, w, response.makeDeniedResponse(fmt.Errorf("failed to get request: %w", err).Error(), p.timeFormat))
+ response.sendBackResponse(r, w, p.errorResponse(response, StatusCodeRequestDenied, fmt.Errorf("failed to get request: %w", err).Error()))
return
}
response.RequestID = authRequest.GetAuthRequestID()
@@ -42,44 +45,50 @@ func (p *IdentityProvider) callbackHandleFunc(w http.ResponseWriter, r *http.Req
response.ProtocolBinding = authRequest.GetBindingType()
response.AcsUrl = authRequest.GetAccessConsumerServiceURL()
- if !authRequest.Done() {
+ entityID, err := p.storage.GetEntityIDByAppID(r.Context(), authRequest.GetApplicationID())
+ if err != nil {
logging.Error(err)
http.Error(w, fmt.Errorf("failed to get entityID: %w", err).Error(), http.StatusInternalServerError)
return
}
+ response.Audience = entityID
- entityID, err := p.storage.GetEntityIDByAppID(r.Context(), authRequest.GetApplicationID())
+ samlResponse, err := p.loginResponse(r.Context(), authRequest, response)
if err != nil {
- logging.Error(err)
- http.Error(w, fmt.Errorf("failed to get entityID: %w", err).Error(), http.StatusInternalServerError)
+ response.sendBackResponse(r, w, response.makeFailedResponse(err.Error(), "failed to create response", p.TimeFormat))
return
}
- response.Audience = entityID
+
+ response.sendBackResponse(r, w, samlResponse)
+ return
+}
+
+func (p *IdentityProvider) loginResponse(ctx context.Context, authRequest models.AuthRequestInt, response *Response) (*samlp.ResponseType, error) {
+ if !authRequest.Done() {
+ logging.Error(StatusCodeAuthNFailed)
+ return nil, fmt.Errorf(StatusCodeAuthNFailed)
+ }
attrs := &Attributes{}
if err := p.storage.SetUserinfoWithUserID(ctx, authRequest.GetApplicationID(), attrs, authRequest.GetUserID(), []int{}); err != nil {
logging.Error(err)
- http.Error(w, fmt.Errorf("failed to get userinfo: %w", err).Error(), http.StatusInternalServerError)
- return
+ return nil, fmt.Errorf(StatusCodeInvalidAttrNameOrValue)
}
- samlResponse := response.makeSuccessfulResponse(attrs, p.timeFormat)
+ cert, key, err := getResponseCert(ctx, p.storage)
+ if err != nil {
+ logging.Error(err)
+ return nil, fmt.Errorf(StatusCodeInvalidAttrNameOrValue)
+ }
- switch response.ProtocolBinding {
- case PostBinding:
- if err := createPostSignature(r.Context(), samlResponse, p); err != nil {
- logging.Error(err)
- response.sendBackResponse(r, w, response.makeResponderFailResponse(fmt.Errorf("failed to sign response: %w", err).Error(), p.timeFormat))
- return
- }
- case RedirectBinding:
- if err := createRedirectSignature(r.Context(), samlResponse, p, response); err != nil {
- logging.Error(err)
- response.sendBackResponse(r, w, response.makeResponderFailResponse(fmt.Errorf("failed to sign response: %w", err).Error(), p.timeFormat))
- return
- }
+ samlResponse := response.makeSuccessfulResponse(attrs, p.TimeFormat, p.Expiration)
+ if err := createSignature(response, samlResponse, key, cert, p.conf.SignatureAlgorithm); err != nil {
+ logging.Error(err)
+ return nil, fmt.Errorf(StatusCodeResponder)
}
+ return samlResponse, nil
+}
- response.sendBackResponse(r, w, samlResponse)
- return
+func (p *IdentityProvider) errorResponse(response *Response, reason string, description string) *samlp.ResponseType {
+ return response.makeFailedResponse(reason, description, p.TimeFormat)
}
diff --git a/pkg/provider/login_test.go b/pkg/provider/login_test.go
index 1bf9e81..c188ec4 100644
--- a/pkg/provider/login_test.go
+++ b/pkg/provider/login_test.go
@@ -1,9 +1,9 @@
package provider
import (
- "io/ioutil"
"net/http"
"net/http/httptest"
+ "net/url"
"testing"
"github.com/golang/mock/gomock"
@@ -23,9 +23,11 @@ func TestSSO_loginHandleFunc(t *testing.T) {
Done bool
}
type res struct {
- code int
- err bool
- state string
+ code int
+ err bool
+ state string
+ inflate bool
+ b64 bool
}
type sp struct {
appID string
@@ -235,7 +237,7 @@ func TestSSO_loginHandleFunc(t *testing.T) {
ID: "test",
AuthRequestID: "test",
Binding: RedirectBinding,
- AcsURL: "url",
+ AcsURL: "https://sp.example.com",
RelayState: "relaystate",
UserID: "userid",
Done: false,
@@ -247,9 +249,11 @@ func TestSSO_loginHandleFunc(t *testing.T) {
},
},
res{
- code: 500,
- state: "",
- err: false,
+ code: 302,
+ state: StatusCodeAuthNFailed,
+ err: false,
+ inflate: true,
+ b64: true,
}},
}
@@ -297,14 +301,15 @@ func TestSSO_loginHandleFunc(t *testing.T) {
defer func() {
_ = res.Body.Close()
}()
- response, err := ioutil.ReadAll(res.Body)
- if res.StatusCode != tt.res.code {
- t.Errorf("ssoHandleFunc() code got = %v, want %v", res.StatusCode, tt.res)
- return
- }
+ // currently only checked for redirect binding
if tt.res.state != "" {
- if err := parseForState(string(response), tt.res.state); err != nil {
+ responseURL, err := url.Parse(res.Header.Get("Location"))
+ if err != nil {
+ t.Errorf("error while parsing url")
+ }
+
+ if err := parseForState(tt.res.inflate, tt.res.b64, responseURL.Query().Get("SAMLResponse"), tt.res.state); err != nil {
t.Errorf("ssoHandleFunc() response state not: %v", tt.res.state)
return
}
diff --git a/pkg/provider/logout.go b/pkg/provider/logout.go
index 03eaef9..5c8614b 100644
--- a/pkg/provider/logout.go
+++ b/pkg/provider/logout.go
@@ -44,7 +44,7 @@ func (p *IdentityProvider) logoutHandleFunc(w http.ResponseWriter, r *http.Reque
return nil
},
func() {
- response.sendBackLogoutResponse(w, response.makeDeniedLogoutResponse(fmt.Errorf("failed to parse form: %w", err).Error(), p.timeFormat))
+ response.sendBackLogoutResponse(w, response.makeFailedLogoutResponse(StatusCodeRequestDenied, fmt.Errorf("failed to parse form: %w", err).Error(), p.TimeFormat))
},
)
@@ -60,7 +60,7 @@ func (p *IdentityProvider) logoutHandleFunc(w http.ResponseWriter, r *http.Reque
return nil
},
func() {
- response.sendBackLogoutResponse(w, response.makeDeniedLogoutResponse(fmt.Errorf("failed to decode request: %w", err).Error(), p.timeFormat))
+ response.sendBackLogoutResponse(w, response.makeFailedLogoutResponse(StatusCodeRequestDenied, fmt.Errorf("failed to decode request: %w", err).Error(), p.TimeFormat))
},
)
@@ -69,10 +69,10 @@ func (p *IdentityProvider) logoutHandleFunc(w http.ResponseWriter, r *http.Reque
checkIfRequestTimeIsStillValid(
func() string { return logoutRequest.IssueInstant },
func() string { return logoutRequest.NotOnOrAfter },
- p.timeFormat,
+ p.TimeFormat,
),
func() {
- response.sendBackLogoutResponse(w, response.makeDeniedLogoutResponse(fmt.Errorf("failed to validate request: %w", err).Error(), p.timeFormat))
+ response.sendBackLogoutResponse(w, response.makeFailedLogoutResponse(StatusCodeRequestDenied, fmt.Errorf("failed to validate request: %w", err).Error(), p.TimeFormat))
},
)
@@ -83,7 +83,7 @@ func (p *IdentityProvider) logoutHandleFunc(w http.ResponseWriter, r *http.Reque
return err
},
func() {
- response.sendBackLogoutResponse(w, response.makeDeniedLogoutResponse(fmt.Errorf("failed to find registered serviceprovider: %w", err).Error(), p.timeFormat))
+ response.sendBackLogoutResponse(w, response.makeFailedLogoutResponse(StatusCodeRequestDenied, fmt.Errorf("failed to find registered serviceprovider: %w", err).Error(), p.TimeFormat))
},
)
@@ -106,7 +106,7 @@ func (p *IdentityProvider) logoutHandleFunc(w http.ResponseWriter, r *http.Reque
response.sendBackLogoutResponse(
w,
- response.makeSuccessfulLogoutResponse(p.timeFormat),
+ response.makeSuccessfulLogoutResponse(p.TimeFormat),
)
logging.Info(fmt.Sprintf("logout request for user %s", logoutRequest.NameID.Text))
}
diff --git a/pkg/provider/logout_response.go b/pkg/provider/logout_response.go
index 63a5765..407ccd8 100644
--- a/pkg/provider/logout_response.go
+++ b/pkg/provider/logout_response.go
@@ -55,32 +55,8 @@ func (r *LogoutResponse) sendBackLogoutResponse(w http.ResponseWriter, resp *sam
}
}
-func (r *LogoutResponse) makeSuccessfulLogoutResponse(timeFormat string) *samlp.LogoutResponseType {
- return makeLogoutResponse(
- r.RequestID,
- r.LogoutURL,
- time.Now().UTC().Format(timeFormat),
- StatusCodeSuccess,
- "",
- getIssuer(r.Issuer),
- )
-}
-
-func (r *LogoutResponse) makeUnsupportedlLogoutResponse(
- message string,
- timeFormat string,
-) *samlp.LogoutResponseType {
- return makeLogoutResponse(
- r.RequestID,
- r.LogoutURL,
- time.Now().UTC().Format(timeFormat),
- StatusCodeRequestUnsupported,
- message,
- getIssuer(r.Issuer),
- )
-}
-
-func (r *LogoutResponse) makePartialLogoutResponse(
+func (r *LogoutResponse) makeFailedLogoutResponse(
+ reason string,
message string,
timeFormat string,
) *samlp.LogoutResponseType {
@@ -88,22 +64,19 @@ func (r *LogoutResponse) makePartialLogoutResponse(
r.RequestID,
r.LogoutURL,
time.Now().UTC().Format(timeFormat),
- StatusCodePartialLogout,
+ reason,
message,
getIssuer(r.Issuer),
)
}
-func (r *LogoutResponse) makeDeniedLogoutResponse(
- message string,
- timeFormat string,
-) *samlp.LogoutResponseType {
+func (r *LogoutResponse) makeSuccessfulLogoutResponse(timeFormat string) *samlp.LogoutResponseType {
return makeLogoutResponse(
r.RequestID,
r.LogoutURL,
time.Now().UTC().Format(timeFormat),
- StatusCodeRequestDenied,
- message,
+ StatusCodeSuccess,
+ "",
getIssuer(r.Issuer),
)
}
diff --git a/pkg/provider/post.go b/pkg/provider/post.go
index 82ef22e..38a44dc 100644
--- a/pkg/provider/post.go
+++ b/pkg/provider/post.go
@@ -1,7 +1,7 @@
package provider
import (
- "context"
+ "crypto/rsa"
"encoding/base64"
"reflect"
@@ -63,16 +63,12 @@ func verifyPostSignature(
}
func createPostSignature(
- ctx context.Context,
samlResponse *samlp.ResponseType,
- idp *IdentityProvider,
+ key *rsa.PrivateKey,
+ cert []byte,
+ signatureAlgorithm string,
) error {
- cert, key, err := getResponseCert(ctx, idp.storage)
- if err != nil {
- return err
- }
-
- signer, err := signature.GetSigner(cert, key, idp.conf.SignatureAlgorithm)
+ signer, err := signature.GetSigner(cert, key, signatureAlgorithm)
if err != nil {
return err
}
diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go
index 0577862..df0471d 100644
--- a/pkg/provider/provider.go
+++ b/pkg/provider/provider.go
@@ -5,20 +5,23 @@ import (
"crypto/rsa"
"fmt"
"net/http"
+ "time"
"github.com/google/uuid"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
+ "github.com/zitadel/saml/pkg/provider/models"
"github.com/zitadel/saml/pkg/provider/signature"
"github.com/zitadel/saml/pkg/provider/xml/md"
+ "github.com/zitadel/saml/pkg/provider/xml/samlp"
)
const (
DefaultTimeFormat = "2006-01-02T15:04:05.999999Z"
+ DefaultExpiration = 5 * time.Minute
PostBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
RedirectBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
- SOAPBinding = "urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
DefaultMetadataEndpoint = "/metadata"
)
@@ -80,7 +83,6 @@ type Provider struct {
conf *Config
issuerFromRequest IssuerFromRequest
identityProvider *IdentityProvider
- timeFormat string
}
func NewProvider(
@@ -217,12 +219,32 @@ var allowAllOrigins = func(_ string) bool {
}
// AuthCallbackURL builds the url for the redirect (with the requestID) after a successful login
-func AuthCallbackURL(p *Provider) func(context.Context, string) string {
+func (p *Provider) AuthCallbackURL() func(context.Context, string) string {
return func(ctx context.Context, requestID string) string {
return p.identityProvider.endpoints.callbackEndpoint.Absolute(IssuerFromContext(ctx)) + "?id=" + requestID
}
}
+// AuthCallbackResponse returns the SAMLResponse from as successful SAMLRequest
+func (p *Provider) AuthCallbackResponse(ctx context.Context, authRequest models.AuthRequestInt, response *Response) (*samlp.ResponseType, error) {
+ return p.identityProvider.loginResponse(ctx, authRequest, response)
+}
+
+// AuthCallbackErrorResponse returns the SAMLResponse from as failed SAMLRequest
+func (p *Provider) AuthCallbackErrorResponse(response *Response, reason string, description string) *samlp.ResponseType {
+ return p.identityProvider.errorResponse(response, reason, description)
+}
+
+// Timeformat return the used timeformat in messages
+func (p *Provider) Timeformat() string {
+ return p.identityProvider.TimeFormat
+}
+
+// Expiration return the used expiration in messages
+func (p *Provider) Expiration() time.Duration {
+ return p.identityProvider.Expiration
+}
+
func intercept(i IssuerFromRequest, interceptors ...HttpInterceptor) func(handler http.Handler) http.Handler {
cors := handlers.CORS(
handlers.AllowCredentials(),
@@ -250,7 +272,7 @@ func WithAllowInsecure() Option {
// WithCustomTimeFormat allows the use of a custom timeformat instead of the default
func WithCustomTimeFormat(timeFormat string) Option {
return func(p *Provider) error {
- p.identityProvider.timeFormat = timeFormat
+ p.identityProvider.TimeFormat = timeFormat
return nil
}
}
diff --git a/pkg/provider/redirect.go b/pkg/provider/redirect.go
index 45f76e5..a31bac9 100644
--- a/pkg/provider/redirect.go
+++ b/pkg/provider/redirect.go
@@ -1,7 +1,7 @@
package provider
import (
- "context"
+ "crypto/rsa"
"encoding/base64"
"fmt"
"net/url"
@@ -66,47 +66,41 @@ func verifyRedirectSignature(
}
func createRedirectSignature(
- ctx context.Context,
samlResponse *samlp.ResponseType,
- idp *IdentityProvider,
- response *Response,
-) error {
+ key *rsa.PrivateKey,
+ cert []byte,
+ signatureAlgorithm string,
+ relayState string,
+) (string, string, error) {
resp, err := xml.Marshal(samlResponse)
if err != nil {
- return err
+ return "", "", err
}
respData, err := xml.DeflateAndBase64(resp)
if err != nil {
- return err
- }
-
- cert, key, err := getResponseCert(ctx, idp.storage)
- if err != nil {
- return err
+ return "", "", err
}
tlsCert, err := signature.ParseTlsKeyPair(cert, key)
if err != nil {
- return err
+ return "", "", err
}
- signingContext, err := signature.GetSigningContext(tlsCert, idp.conf.SignatureAlgorithm)
+ signingContext, err := signature.GetSigningContext(tlsCert, signatureAlgorithm)
if err != nil {
- return err
+ return "", "", err
}
- sig, err := signature.CreateRedirect(signingContext, buildRedirectQuery(string(respData), response.RelayState, idp.conf.SignatureAlgorithm, ""))
+ sig, err := signature.CreateRedirect(signingContext, BuildRedirectQuery(string(respData), relayState, signatureAlgorithm, ""))
if err != nil {
- return err
+ return "", "", err
}
- response.Signature = url.QueryEscape(base64.StdEncoding.EncodeToString(sig))
- response.SigAlg = url.QueryEscape(base64.StdEncoding.EncodeToString([]byte(idp.conf.SignatureAlgorithm)))
- return nil
+ return url.QueryEscape(base64.StdEncoding.EncodeToString(sig)), url.QueryEscape(base64.StdEncoding.EncodeToString([]byte(signatureAlgorithm))), nil
}
-func buildRedirectQuery(
+func BuildRedirectQuery(
response string,
relayState string,
sigAlg string,
diff --git a/pkg/provider/redirect_test.go b/pkg/provider/redirect_test.go
index 18adeb7..87c689c 100644
--- a/pkg/provider/redirect_test.go
+++ b/pkg/provider/redirect_test.go
@@ -385,7 +385,7 @@ func TestRedirect_buildRedirectQuery(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got := buildRedirectQuery(tt.args.response, tt.args.relayState, tt.args.sigAlg, tt.args.sig)
+ got := BuildRedirectQuery(tt.args.response, tt.args.relayState, tt.args.sigAlg, tt.args.sig)
if got != tt.res {
t.Errorf("verifyRedirectSignature() got = %v, want %v", got, tt.res)
return
diff --git a/pkg/provider/response.go b/pkg/provider/response.go
index ddd4346..f868602 100644
--- a/pkg/provider/response.go
+++ b/pkg/provider/response.go
@@ -1,6 +1,7 @@
package provider
import (
+ "crypto/rsa"
"encoding/base64"
"fmt"
"html/template"
@@ -12,7 +13,7 @@ import (
"github.com/zitadel/saml/pkg/provider/xml/samlp"
)
-const (
+var (
StatusCodeSuccess = "urn:oasis:names:tc:SAML:2.0:status:Success"
StatusCodeVersionMissmatch = "urn:oasis:names:tc:SAML:2.0:status:VersionMismatch"
StatusCodeAuthNFailed = "urn:oasis:names:tc:SAML:2.0:status:AuthnFailed"
@@ -40,11 +41,7 @@ type Response struct {
SendIP string
}
-func (r *Response) doResponse(request *http.Request, w http.ResponseWriter, response string) {
-
-}
-
-type AuthResponseForm struct {
+type authResponseForm struct {
RelayState string
SAMLResponse string
AssertionConsumerServiceURL string
@@ -73,7 +70,7 @@ func (r *Response) sendBackResponse(
case PostBinding:
respData := base64.StdEncoding.EncodeToString(respData)
- data := AuthResponseForm{
+ data := authResponseForm{
r.RelayState,
respData,
r.AcsUrl,
@@ -90,76 +87,42 @@ func (r *Response) sendBackResponse(
return
}
- http.Redirect(w, req, fmt.Sprintf("%s?%s", r.AcsUrl, buildRedirectQuery(string(respData), r.RelayState, r.SigAlg, r.Signature)), http.StatusFound)
+ http.Redirect(w, req, fmt.Sprintf("%s?%s", r.AcsUrl, BuildRedirectQuery(string(respData), r.RelayState, r.SigAlg, r.Signature)), http.StatusFound)
return
default:
//TODO: no binding
}
}
-func (r *Response) makeUnsupportedBindingResponse(
- message string,
- timeFormat string,
-) *samlp.ResponseType {
- now := time.Now().UTC()
- nowStr := now.Format(timeFormat)
- return makeResponse(
- NewID(),
- r.RequestID,
- r.AcsUrl,
- nowStr,
- StatusCodeUnsupportedBinding,
- message,
- r.Issuer,
- )
-}
-
-func (r *Response) makeResponderFailResponse(
- message string,
- timeFormat string,
-) *samlp.ResponseType {
- now := time.Now().UTC()
- nowStr := now.Format(timeFormat)
- return makeResponse(
- NewID(),
- r.RequestID,
- r.AcsUrl,
- nowStr,
- StatusCodeResponder,
- message,
- r.Issuer,
- )
-}
-
-func (r *Response) makeDeniedResponse(
- message string,
- timeFormat string,
-) *samlp.ResponseType {
- now := time.Now().UTC()
- nowStr := now.Format(timeFormat)
- return makeResponse(
- NewID(),
- r.RequestID,
- r.AcsUrl,
- nowStr,
- StatusCodeRequestDenied,
- message,
- r.Issuer,
- )
+func createSignature(response *Response, samlResponse *samlp.ResponseType, key *rsa.PrivateKey, cert []byte, signatureAlgorithm string) error {
+ switch response.ProtocolBinding {
+ case PostBinding:
+ if err := createPostSignature(samlResponse, key, cert, signatureAlgorithm); err != nil {
+ return fmt.Errorf("failed to sign response: %w", err)
+ }
+ case RedirectBinding:
+ sig, sigAlg, err := createRedirectSignature(samlResponse, key, cert, signatureAlgorithm, response.RelayState)
+ if err != nil {
+ return fmt.Errorf("failed to sign response: %w", err)
+ }
+ response.Signature = sig
+ response.SigAlg = sigAlg
+ }
+ return nil
}
func (r *Response) makeFailedResponse(
+ reason string,
message string,
timeFormat string,
) *samlp.ResponseType {
now := time.Now().UTC()
- nowStr := now.Format(timeFormat)
return makeResponse(
NewID(),
r.RequestID,
r.AcsUrl,
- nowStr,
- StatusCodeAuthNFailed,
+ now.Format(timeFormat),
+ reason,
message,
r.Issuer,
)
@@ -168,14 +131,12 @@ func (r *Response) makeFailedResponse(
func (r *Response) makeSuccessfulResponse(
attributes *Attributes,
timeFormat string,
+ expiration time.Duration,
) *samlp.ResponseType {
now := time.Now().UTC()
- nowStr := now.Format(timeFormat)
- fiveFromNowStr := now.Add(5 * time.Minute).Format(timeFormat)
-
return r.makeAssertionResponse(
- nowStr,
- fiveFromNowStr,
+ now.Format(timeFormat),
+ now.Add(expiration).Format(timeFormat),
attributes,
)
}
@@ -206,13 +167,9 @@ func makeAttributeQueryResponse(
attributes *Attributes,
queriedAttrs []saml.AttributeType,
timeFormat string,
+ expiration time.Duration,
) *samlp.ResponseType {
now := time.Now().UTC()
- nowStr := now.Format(timeFormat)
- fiveMinutes, _ := time.ParseDuration("5m")
- fiveFromNow := now.Add(fiveMinutes)
- fiveFromNowStr := fiveFromNow.Format(timeFormat)
-
providedAttrs := []*saml.AttributeType{}
attrsSaml := attributes.GetSAML()
if queriedAttrs == nil || len(queriedAttrs) == 0 {
@@ -229,8 +186,8 @@ func makeAttributeQueryResponse(
}
}
- response := makeResponse(NewID(), requestID, "", nowStr, StatusCodeSuccess, "", issuer)
- assertion := makeAssertion(requestID, "", "", nowStr, fiveFromNowStr, issuer, attributes.GetNameID(), providedAttrs, entityID, false)
+ response := makeResponse(NewID(), requestID, "", now.Format(timeFormat), StatusCodeSuccess, "", issuer)
+ assertion := makeAssertion(requestID, "", "", now.Format(timeFormat), now.Add(expiration).Format(timeFormat), issuer, attributes.GetNameID(), providedAttrs, entityID, false)
response.Assertion = *assertion
return response
}
diff --git a/pkg/provider/response_test.go b/pkg/provider/response_test.go
index 693f71c..358cf6a 100644
--- a/pkg/provider/response_test.go
+++ b/pkg/provider/response_test.go
@@ -74,7 +74,7 @@ func TestResponse_sendBackResponse(t *testing.T) {
signature: "sig",
},
res{
- body: []byte("Found.\n\n"),
+ body: []byte("Found.\n\n"),
},
},
{
@@ -94,7 +94,7 @@ func TestResponse_sendBackResponse(t *testing.T) {
signature: "sig",
},
res{
- body: []byte("Found.\n\n"),
+ body: []byte("Found.\n\n"),
},
},
{
diff --git a/pkg/provider/sso.go b/pkg/provider/sso.go
index dafca50..451566b 100644
--- a/pkg/provider/sso.go
+++ b/pkg/provider/sso.go
@@ -61,7 +61,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
return nil
},
func() {
- response.sendBackResponse(r, w, response.makeDeniedResponse(fmt.Errorf("failed to parse form").Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeRequestDenied, fmt.Errorf("failed to parse form").Error(), p.TimeFormat))
},
)
@@ -70,7 +70,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
"SAMLRequest",
func() string { return authRequestForm.AuthRequest },
func() {
- response.sendBackResponse(r, w, response.makeDeniedResponse(fmt.Errorf("no auth request provided").Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeRequestDenied, fmt.Errorf("no auth request provided").Error(), p.TimeFormat))
},
)
@@ -80,7 +80,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
"Signature",
func() string { return authRequestForm.Sig },
func() {
- response.sendBackResponse(r, w, response.makeDeniedResponse(fmt.Errorf("signature algorith provided but no signature").Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeRequestDenied, fmt.Errorf("signature algorith provided but no signature").Error(), p.TimeFormat))
},
)
@@ -95,7 +95,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
return nil
},
func() {
- response.sendBackResponse(r, w, response.makeDeniedResponse(fmt.Errorf("failed to decode request").Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeRequestDenied, fmt.Errorf("failed to decode request").Error(), p.TimeFormat))
},
)
@@ -110,7 +110,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
return nil
},
func() {
- response.sendBackResponse(r, w, response.makeDeniedResponse(fmt.Errorf("failed to find registered serviceprovider: %w", err).Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeRequestDenied, fmt.Errorf("failed to find registered serviceprovider: %w", err).Error(), p.TimeFormat))
},
)
@@ -125,7 +125,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
func() *md.EntityDescriptorType { return sp.Metadata },
),
func() {
- response.sendBackResponse(r, w, response.makeDeniedResponse(fmt.Errorf("failed to validate certificate from request: %w", err).Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeRequestDenied, fmt.Errorf("failed to validate certificate from request: %w", err).Error(), p.TimeFormat))
},
)
@@ -146,7 +146,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
func(errF error) { err = errF },
),
func() {
- response.sendBackResponse(r, w, response.makeDeniedResponse(fmt.Errorf("failed to verify signature: %w", err).Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeRequestDenied, fmt.Errorf("failed to verify signature: %w", err).Error(), p.TimeFormat))
},
)
@@ -164,14 +164,14 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
func(errF error) { err = errF },
),
func() {
- response.sendBackResponse(r, w, response.makeDeniedResponse(fmt.Errorf("failed to verify signature: %w", err).Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeRequestDenied, fmt.Errorf("failed to verify signature: %w", err).Error(), p.TimeFormat))
},
)
// work out used acs url and protocolbinding for response
checkerInstance.WithValueStep(
func() {
- response.AcsUrl, response.ProtocolBinding = getAcsUrlAndBindingForResponse(sp, authNRequest.ProtocolBinding)
+ response.AcsUrl, response.ProtocolBinding = GetAcsUrlAndBindingForResponse(sp.Metadata.SPSSODescriptor.AssertionConsumerService, authNRequest.ProtocolBinding)
},
)
@@ -180,7 +180,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
"acsUrl",
func() string { return response.AcsUrl },
func() {
- response.sendBackResponse(r, w, response.makeUnsupportedBindingResponse(fmt.Errorf("missing usable assertion consumer url").Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeUnsupportedBinding, fmt.Errorf("missing usable assertion consumer url").Error(), p.TimeFormat))
},
)
@@ -189,7 +189,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
"protocol binding",
func() string { return response.ProtocolBinding },
func() {
- response.sendBackResponse(r, w, response.makeUnsupportedBindingResponse(fmt.Errorf("missing usable protocol binding").Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeUnsupportedBinding, fmt.Errorf("missing usable protocol binding").Error(), p.TimeFormat))
},
)
@@ -200,7 +200,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
func() *samlp.AuthnRequestType { return authNRequest },
),
func() {
- response.sendBackResponse(r, w, response.makeDeniedResponse(fmt.Errorf("failed to validate request content: %w", err).Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeRequestDenied, fmt.Errorf("failed to validate request content: %w", err).Error(), p.TimeFormat))
},
)
@@ -218,7 +218,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
return err
},
func() {
- response.sendBackResponse(r, w, response.makeResponderFailResponse(fmt.Errorf("failed to persist request: %w", err).Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeResponder, fmt.Errorf("failed to persist request: %w", err).Error(), p.TimeFormat))
},
)
@@ -232,7 +232,7 @@ func (p *IdentityProvider) ssoHandleFunc(w http.ResponseWriter, r *http.Request)
http.Redirect(w, r, sp.LoginURL(authRequest.GetID()), http.StatusSeeOther)
default:
logging.Error(err)
- response.sendBackResponse(r, w, response.makeUnsupportedBindingResponse(fmt.Errorf("unsupported binding: %s", response.ProtocolBinding).Error(), p.timeFormat))
+ response.sendBackResponse(r, w, response.makeFailedResponse(StatusCodeUnsupportedBinding, fmt.Errorf("unsupported binding: %s", response.ProtocolBinding).Error(), p.TimeFormat))
}
return
}
@@ -258,6 +258,9 @@ func getAuthRequestFromRequest(r *http.Request) (*AuthRequestForm, error) {
Sig: r.FormValue("Signature"),
Binding: binding,
}
+ if request.Encoding == "" && binding == RedirectBinding {
+ request.Encoding = xml.EncodingDeflate
+ }
return request, nil
}
@@ -348,14 +351,14 @@ func checkCertificate(
}
}
-func getAcsUrlAndBindingForResponse(
- sp *serviceprovider.ServiceProvider,
+func GetAcsUrlAndBindingForResponse(
+ acs []md.IndexedEndpointType,
requestProtocolBinding string,
) (string, string) {
acsUrl := ""
protocolBinding := ""
- for _, acs := range sp.Metadata.SPSSODescriptor.AssertionConsumerService {
+ for _, acs := range acs {
if acs.Binding == requestProtocolBinding {
acsUrl = acs.Location
protocolBinding = acs.Binding
@@ -364,7 +367,7 @@ func getAcsUrlAndBindingForResponse(
}
if acsUrl == "" {
isDefaultFound := false
- for _, acs := range sp.Metadata.SPSSODescriptor.AssertionConsumerService {
+ for _, acs := range acs {
if acs.IsDefault == "true" {
isDefaultFound = true
acsUrl = acs.Location
@@ -374,7 +377,7 @@ func getAcsUrlAndBindingForResponse(
}
if !isDefaultFound {
index := 0
- for _, acs := range sp.Metadata.SPSSODescriptor.AssertionConsumerService {
+ for _, acs := range acs {
i, _ := strconv.Atoi(acs.Index)
if index == 0 || i < index {
acsUrl = acs.Location
diff --git a/pkg/provider/sso_test.go b/pkg/provider/sso_test.go
index 4b68d8c..2b3d599 100644
--- a/pkg/provider/sso_test.go
+++ b/pkg/provider/sso_test.go
@@ -25,7 +25,7 @@ func TestSSO_getAcsUrlAndBindingForResponse(t *testing.T) {
binding string
}
type args struct {
- sp *serviceprovider.ServiceProvider
+ acs []md.IndexedEndpointType
requestBinding string
}
tests := []struct {
@@ -35,15 +35,9 @@ func TestSSO_getAcsUrlAndBindingForResponse(t *testing.T) {
}{{
"sp with post and redirect, default used",
args{
- &serviceprovider.ServiceProvider{
- Metadata: &md.EntityDescriptorType{
- SPSSODescriptor: &md.SPSSODescriptorType{
- AssertionConsumerService: []md.IndexedEndpointType{
- {Index: "1", IsDefault: "true", Binding: RedirectBinding, Location: "redirect"},
- {Index: "2", Binding: PostBinding, Location: "post"},
- },
- },
- },
+ []md.IndexedEndpointType{
+ {Index: "1", IsDefault: "true", Binding: RedirectBinding, Location: "redirect"},
+ {Index: "2", Binding: PostBinding, Location: "post"},
},
RedirectBinding,
},
@@ -55,15 +49,9 @@ func TestSSO_getAcsUrlAndBindingForResponse(t *testing.T) {
{
"sp with post and redirect, first index used",
args{
- &serviceprovider.ServiceProvider{
- Metadata: &md.EntityDescriptorType{
- SPSSODescriptor: &md.SPSSODescriptorType{
- AssertionConsumerService: []md.IndexedEndpointType{
- {Index: "1", Binding: RedirectBinding, Location: "redirect"},
- {Index: "2", Binding: PostBinding, Location: "post"},
- },
- },
- },
+ []md.IndexedEndpointType{
+ {Index: "1", Binding: RedirectBinding, Location: "redirect"},
+ {Index: "2", Binding: PostBinding, Location: "post"},
},
RedirectBinding,
},
@@ -75,15 +63,9 @@ func TestSSO_getAcsUrlAndBindingForResponse(t *testing.T) {
{
"sp with post and redirect, redirect used",
args{
- &serviceprovider.ServiceProvider{
- Metadata: &md.EntityDescriptorType{
- SPSSODescriptor: &md.SPSSODescriptorType{
- AssertionConsumerService: []md.IndexedEndpointType{
- {Binding: RedirectBinding, Location: "redirect"},
- {Binding: PostBinding, Location: "post"},
- },
- },
- },
+ []md.IndexedEndpointType{
+ {Binding: RedirectBinding, Location: "redirect"},
+ {Binding: PostBinding, Location: "post"},
},
RedirectBinding,
},
@@ -95,15 +77,9 @@ func TestSSO_getAcsUrlAndBindingForResponse(t *testing.T) {
{
"sp with post and redirect, post used",
args{
- &serviceprovider.ServiceProvider{
- Metadata: &md.EntityDescriptorType{
- SPSSODescriptor: &md.SPSSODescriptorType{
- AssertionConsumerService: []md.IndexedEndpointType{
- {Binding: RedirectBinding, Location: "redirect"},
- {Binding: PostBinding, Location: "post"},
- },
- },
- },
+ []md.IndexedEndpointType{
+ {Binding: RedirectBinding, Location: "redirect"},
+ {Binding: PostBinding, Location: "post"},
},
PostBinding,
},
@@ -114,16 +90,9 @@ func TestSSO_getAcsUrlAndBindingForResponse(t *testing.T) {
},
{
"sp with redirect, post used",
- args{
- &serviceprovider.ServiceProvider{
- Metadata: &md.EntityDescriptorType{
- SPSSODescriptor: &md.SPSSODescriptorType{
- AssertionConsumerService: []md.IndexedEndpointType{
- {Binding: RedirectBinding, Location: "redirect"},
- },
- },
- },
- },
+ args{[]md.IndexedEndpointType{
+ {Binding: RedirectBinding, Location: "redirect"},
+ },
PostBinding,
},
res{
@@ -134,14 +103,8 @@ func TestSSO_getAcsUrlAndBindingForResponse(t *testing.T) {
{
"sp with post, redirect used",
args{
- &serviceprovider.ServiceProvider{
- Metadata: &md.EntityDescriptorType{
- SPSSODescriptor: &md.SPSSODescriptorType{
- AssertionConsumerService: []md.IndexedEndpointType{
- {Binding: PostBinding, Location: "post"},
- },
- },
- },
+ []md.IndexedEndpointType{
+ {Binding: PostBinding, Location: "post"},
},
RedirectBinding,
},
@@ -153,9 +116,9 @@ func TestSSO_getAcsUrlAndBindingForResponse(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- acs, binding := getAcsUrlAndBindingForResponse(tt.args.sp, tt.args.requestBinding)
+ acs, binding := GetAcsUrlAndBindingForResponse(tt.args.acs, tt.args.requestBinding)
if acs != tt.res.acs && binding != tt.res.binding {
- t.Errorf("getAcsUrlAndBindingForResponse() got = %v/%v, want %v/%v", acs, binding, tt.res.acs, tt.res.binding)
+ t.Errorf("GetAcsUrlAndBindingForResponse() got = %v/%v, want %v/%v", acs, binding, tt.res.acs, tt.res.binding)
return
}
})
@@ -447,9 +410,11 @@ func TestSSO_ssoHandleFunc(t *testing.T) {
Binding string
}
type res struct {
- code int
- err bool
- state string
+ code int
+ err bool
+ state string
+ inflate bool
+ b64 bool
}
type sp struct {
entityID string
@@ -628,39 +593,6 @@ func TestSSO_ssoHandleFunc(t *testing.T) {
state: StatusCodeRequestDenied,
err: false,
}},
- {
- "redirect request unknown service provider",
- args{
- issuer: "http://localhost:50002",
- metadataEndpoint: "/saml/metadata",
- config: &IdentityProviderConfig{
- SignatureAlgorithm: dsig.RSASHA256SignatureMethod,
- MetadataIDPConfig: &MetadataIDPConfig{},
- Endpoints: &EndpointConfig{
- SingleSignOn: getEndpointPointer("/saml/SSO", "http://localhost:50002/saml/SSO"),
- },
- },
- certificate: "-----BEGIN CERTIFICATE-----\nMIICvDCCAaQCCQD6E8ZGsQ2usjANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVt\neXNlcnZpY2UuZXhhbXBsZS5jb20wHhcNMjIwMjE3MTQwNjM5WhcNMjMwMjE3MTQw\nNjM5WjAgMR4wHAYDVQQDDBVteXNlcnZpY2UuZXhhbXBsZS5jb20wggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7XKdCRxUZXjdqVqwwwOJqc1Ch0nOSmk+U\nerkUqlviWHdeLR+FolHKjqLzCBloAz4xVc0DFfR76gWcWAHJloqZ7GBS7NpDhzV8\nG+cXQ+bTU0Lu2e73zCQb30XUdKhWiGfDKaU+1xg9CD/2gIfsYPs3TTq1sq7oCs5q\nLdUHaVL5kcRaHKdnTi7cs5i9xzs3TsUnXcrJPwydjp+aEkyRh07oMpXBEobGisfF\n2p1MA6pVW2gjmywf7D5iYEFELQhM7poqPN3/kfBvU1n7Lfgq7oxmv/8LFi4Zopr5\nnyqsz26XPtUy1WqTzgznAmP+nN0oBTERFVbXXdRa3k2v4cxTNPn/AgMBAAEwDQYJ\nKoZIhvcNAQELBQADggEBAJYxROWSOZbOzXzafdGjQKsMgN948G/hHwVuZneyAcVo\nLMFTs1Weya9Z+snMp1u0AdDGmQTS9zGnD7syDYGOmgigOLcMvLMoWf5tCQBbEukW\n8O7DPjRR0XypChGSsHsqLGO0B0HaTel0HdP9Si827OCkc9Q+WbsFG/8/4ToGWL+u\nla1WuLawozoj8umPi9D8iXCoW35y2STU+WFQG7W+Kfdu+2CYz/0tGdwVqNG4Wsfa\nwWchrS00vGFKjm/fJc876gAfxiMH1I9fZvYSAxAZ3sVI//Ml2sUdgf067ywQ75oa\nLSS2NImmz5aos3vuWmOXhILd7iTU+BD8Uv6vWbI7I1M=\n-----END CERTIFICATE-----\n",
- key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7XKdCRxUZXjdq\nVqwwwOJqc1Ch0nOSmk+UerkUqlviWHdeLR+FolHKjqLzCBloAz4xVc0DFfR76gWc\nWAHJloqZ7GBS7NpDhzV8G+cXQ+bTU0Lu2e73zCQb30XUdKhWiGfDKaU+1xg9CD/2\ngIfsYPs3TTq1sq7oCs5qLdUHaVL5kcRaHKdnTi7cs5i9xzs3TsUnXcrJPwydjp+a\nEkyRh07oMpXBEobGisfF2p1MA6pVW2gjmywf7D5iYEFELQhM7poqPN3/kfBvU1n7\nLfgq7oxmv/8LFi4Zopr5nyqsz26XPtUy1WqTzgznAmP+nN0oBTERFVbXXdRa3k2v\n4cxTNPn/AgMBAAECggEAF+rV9yH30Ysza8GwrXCR9qDN1Dp3QmmsavnXkonEvPoq\nEr2T3o0//6mBp6CLDboMQGQBjblJwl+3Y6PgZolvHAMOsMdHfYNPEo7FSzUBzEw+\nqRrs5HkMyvoPgfV6X8F97W3tiD4Q/AmHkMILl+MxbnfPXM54gWqPuwIqxY1uaCk5\nREwyb7WBon3rd58ceOI1SLRjod6SbqWBMMSN3cJ+5VEPObFjw/RlhNQ5rBI8G5Kt\nso2zBU5C4BB2CvqlWy98WDKJkTvWHbiTjZCy8BQ+gQ6UJM2vaNELFOVpuMGQnMIi\noWiX10Jg2e1gP9j3TdrohlGF8M3+TXjSFKNmeX0DUQKBgQDx7UazUWS5RtkgnjH9\nw2xH2xkstJVD7nAS8VTxNwcrgjVXPvTJha9El904obUjyRX7ppb02tuH5ML/bZh6\n9lL4bP5+SHcJ10e4q8CK/KAGHD6BYAbaGXRq0CoSk5a3vv5XPdob4T5qKCIHFpnu\nMfbvdbEoameLOyRYOGu/yVZIiwKBgQDGQs7FRTisHV0xooiRmlvYF0dcd19qpLed\nqhgJNqBPOTEvvGvJNRoi39haEY3cuTqsxZ5FAlFlVFMUUozz+d0xBLLInoVY/Y4h\nhSdGmdw/A6oHodLqyEp3N5RZNdLlh8/nDS3xXzMotAl75bW5kc2ttcRhRdtyNJ9Z\nup0PgppO3QKBgEC45upAQz8iCiKkz+EA8C4FGqYQJcLHvmoC8GOcAioMqrKNoDVt\ns2cZbdChynEpcd0iQ058YrDnbZeiPWHgFnBp0Gf+gQI7+u8X2+oTDci0s7Au/YZJ\nuxB8YlUX8QF1clvqqzg8OVNzKy9UR5gm+9YyWVPjq5HfH6kOZx0nAxNjAoGAERt8\nqgsCC9/wxbKnpCC0oh3IG5N1WUdjTKh7sHfVN2DQ/LR+fHsniTDVg1gWbKBTDsty\nj7PWgC7ZiFxjKz45NtyX7LW4/efLFttdezsVhR500nnFMFseCdFy7Iu3afThHKfH\nehdj27RFSTqWBrAtFjsj+dzERcOCqIRwvwDe/cUCgYEA5+1mzVXDVjKsWylKJPk+\nZZA4LUfvmTj3VLNDZrlSAI/xEikCFio0QWEA2TQYTAwbXTrKwQSeHQRhv7OTc1h+\nMhpAgvs189ze5J4jiNmULEkkrO+Cxxnw8tyV+UFRZtzW9gUoVBwXiZ/Wbl9sfnlO\nwLJHc0j6OltPcPJmxHP8gQI=\n-----END PRIVATE KEY-----\n",
- request: request{
- ID: "test",
- Binding: RedirectBinding,
- SAMLRequest: url.QueryEscape("nJJBj9MwEIX/ijX3NG6a7DbWJlLZClFpYatN4cBt6k6oJccungmw/x61XaQioRy42vP5ved5D4yDP5nVKMfwQt9HYlG/Bh/YnC8aGFMwEdmxCTgQG7GmW318MsVMG2SmJC4GuEFO08wpRYk2elCbdQPukFlNd/c9LQpczPve6r3taVHWdbWoal3bfr7c03JJc1BfKLGLoYFipkFtmEfaBBYM0kChiyLTZVbc7XRtyntTVrOyrr6CWhOLCygX8ihyMnnuo0V/jCym0loX+dl33nXPoFZ/Ij3GwONAqaP0w1n6/PL0D3qptb7CaBnU9i3bOxcOLnyb/oj9dYjNh91um22fux20l2WYS7Kk3sc0oEw/cj5xh6y/jBoK4uQV2gmfAwkeUPAhv5Fq30rwCQfarLfRO/v6H/KSMLCjIKBW3sefj4lQqAFJI0HeXiX/rlr7OwAA//8="),
- RelayState: url.QueryEscape("K6LS7mdqUO4SGedbfa8nBIyX-7K8gGbrHMqIMwVn6zCKLLoADHjEHUAm"),
- Signature: url.QueryEscape("PWZ6JPNpAGE7mYLKD3dCUG9AZcThrMRQGtvdv31ewx3hms5Oglc677iAUEcbIBrvKtMrCPVwXPNxT6wQ0rg4qIgyKgoyS53ZTaxaFHPrB7wkkzqtK7GvWgdEqceT8iooK5SCLHFMJ3m30LqEbX7zFw62yE34+e7ypfZSM5Lrf0QFwPzX+LNCuYA+Ob9D5SKc132tn21J2vBRmNJ1zCY0ksRzQfyfErjAzcGVx8qK9jpaeyvsVBZSkH/I6+1hb8lQWE48xala9NbqfbMATGBCQj1UvpVMMfp6PE7KPk5Y1YDeSqPeRIEKH+Gnip6Hve5Ji1aiRp5bytVf1VHwTHSq8w=="),
- SigAlg: url.QueryEscape("http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
- },
- sp: sp{
- entityID: "http://localhost:8000/saml/metadata",
- metadata: "\n \n \n \n \n MIICvDCCAaQCCQD6E8ZGsQ2usjANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVteXNlcnZpY2UuZXhhbXBsZS5jb20wHhcNMjIwMjE3MTQwNjM5WhcNMjMwMjE3MTQwNjM5WjAgMR4wHAYDVQQDDBVteXNlcnZpY2UuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7XKdCRxUZXjdqVqwwwOJqc1Ch0nOSmk+UerkUqlviWHdeLR+FolHKjqLzCBloAz4xVc0DFfR76gWcWAHJloqZ7GBS7NpDhzV8G+cXQ+bTU0Lu2e73zCQb30XUdKhWiGfDKaU+1xg9CD/2gIfsYPs3TTq1sq7oCs5qLdUHaVL5kcRaHKdnTi7cs5i9xzs3TsUnXcrJPwydjp+aEkyRh07oMpXBEobGisfF2p1MA6pVW2gjmywf7D5iYEFELQhM7poqPN3/kfBvU1n7Lfgq7oxmv/8LFi4Zopr5nyqsz26XPtUy1WqTzgznAmP+nN0oBTERFVbXXdRa3k2v4cxTNPn/AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJYxROWSOZbOzXzafdGjQKsMgN948G/hHwVuZneyAcVoLMFTs1Weya9Z+snMp1u0AdDGmQTS9zGnD7syDYGOmgigOLcMvLMoWf5tCQBbEukW8O7DPjRR0XypChGSsHsqLGO0B0HaTel0HdP9Si827OCkc9Q+WbsFG/8/4ToGWL+ula1WuLawozoj8umPi9D8iXCoW35y2STU+WFQG7W+Kfdu+2CYz/0tGdwVqNG4WsfawWchrS00vGFKjm/fJc876gAfxiMH1I9fZvYSAxAZ3sVI//Ml2sUdgf067ywQ75oaLSS2NImmz5aos3vuWmOXhILd7iTU+BD8Uv6vWbI7I1M=\n \n \n \n \n \n \n \n \n \n \n MIICvDCCAaQCCQD6E8ZGsQ2usjANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDDBVteXNlcnZpY2UuZXhhbXBsZS5jb20wHhcNMjIwMjE3MTQwNjM5WhcNMjMwMjE3MTQwNjM5WjAgMR4wHAYDVQQDDBVteXNlcnZpY2UuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7XKdCRxUZXjdqVqwwwOJqc1Ch0nOSmk+UerkUqlviWHdeLR+FolHKjqLzCBloAz4xVc0DFfR76gWcWAHJloqZ7GBS7NpDhzV8G+cXQ+bTU0Lu2e73zCQb30XUdKhWiGfDKaU+1xg9CD/2gIfsYPs3TTq1sq7oCs5qLdUHaVL5kcRaHKdnTi7cs5i9xzs3TsUnXcrJPwydjp+aEkyRh07oMpXBEobGisfF2p1MA6pVW2gjmywf7D5iYEFELQhM7poqPN3/kfBvU1n7Lfgq7oxmv/8LFi4Zopr5nyqsz26XPtUy1WqTzgznAmP+nN0oBTERFVbXXdRa3k2v4cxTNPn/AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJYxROWSOZbOzXzafdGjQKsMgN948G/hHwVuZneyAcVoLMFTs1Weya9Z+snMp1u0AdDGmQTS9zGnD7syDYGOmgigOLcMvLMoWf5tCQBbEukW8O7DPjRR0XypChGSsHsqLGO0B0HaTel0HdP9Si827OCkc9Q+WbsFG/8/4ToGWL+ula1WuLawozoj8umPi9D8iXCoW35y2STU+WFQG7W+Kfdu+2CYz/0tGdwVqNG4WsfawWchrS00vGFKjm/fJc876gAfxiMH1I9fZvYSAxAZ3sVI//Ml2sUdgf067ywQ75oaLSS2NImmz5aos3vuWmOXhILd7iTU+BD8Uv6vWbI7I1M=\n \n \n \n \n \n \n \n",
- err: fmt.Errorf("unknown"),
- },
- },
- res{
- code: 200,
- state: StatusCodeRequestDenied,
- err: false,
- }},
{
"signed post request",
args{
@@ -693,7 +625,6 @@ func TestSSO_ssoHandleFunc(t *testing.T) {
err: false,
}},
}
-
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
endpoint := NewEndpoint(tt.args.metadataEndpoint)
@@ -766,7 +697,7 @@ func TestSSO_ssoHandleFunc(t *testing.T) {
}
if tt.res.state != "" {
- if err := parseForState(string(response), tt.res.state); err != nil {
+ if err := parseForState(tt.res.inflate, tt.res.b64, string(response), tt.res.state); err != nil {
t.Errorf("ssoHandleFunc() response state not: %v", tt.res.state)
return
}
@@ -775,8 +706,13 @@ func TestSSO_ssoHandleFunc(t *testing.T) {
}
}
-func parseForState(responseXML string, state string) error {
- response, err := xml.DecodeResponse("", responseXML)
+func parseForState(inflate bool, b64 bool, responseXML string, state string) error {
+ encoding := ""
+ if inflate {
+ encoding = xml.EncodingDeflate
+ }
+
+ response, err := xml.DecodeResponse(encoding, b64, responseXML)
if err != nil {
return err
}
diff --git a/pkg/provider/xml/xml.go b/pkg/provider/xml/xml.go
index ad4e976..f717b5a 100644
--- a/pkg/provider/xml/xml.go
+++ b/pkg/provider/xml/xml.go
@@ -7,6 +7,7 @@ import (
"encoding/base64"
"encoding/xml"
"fmt"
+ "io"
"net/http"
"strings"
@@ -43,25 +44,20 @@ func Marshal(data interface{}) ([]byte, error) {
}
func DeflateAndBase64(data []byte) ([]byte, error) {
- b := &bytes.Buffer{}
- w1 := base64.NewEncoder(base64.StdEncoding, b)
- defer w1.Close()
-
- w2, _ := flate.NewWriter(w1, 1)
- defer w2.Close()
-
- bw := bufio.NewWriter(w1)
- if _, err := bw.Write(data); err != nil {
+ buff := &bytes.Buffer{}
+ b64Encoder := base64.NewEncoder(base64.StdEncoding, buff)
+ // compression level is set at 9 as BestCompression, also used by other SAML application like crewjam/saml
+ flateWriter, _ := flate.NewWriter(b64Encoder, 9)
+ if _, err := flateWriter.Write(data); err != nil {
return nil, err
}
- if err := bw.Flush(); err != nil {
+ if err := flateWriter.Close(); err != nil {
return nil, err
}
-
- if err := w2.Flush(); err != nil {
+ if err := b64Encoder.Close(); err != nil {
return nil, err
}
- return b.Bytes(), nil
+ return buff.Bytes(), nil
}
func WriteXMLMarshalled(w http.ResponseWriter, body interface{}) error {
@@ -86,53 +82,26 @@ func Write(w http.ResponseWriter, body []byte) error {
}
func DecodeAuthNRequest(encoding string, message string) (*samlp.AuthnRequestType, error) {
- reqBytes, err := base64.StdEncoding.DecodeString(message)
+ data, err := InflateAndDecode(encoding, true, message)
if err != nil {
- return nil, fmt.Errorf("failed to base64 decode: %w", err)
+ return nil, err
}
-
req := &samlp.AuthnRequestType{}
- switch encoding {
- case EncodingDeflate:
- reader := flate.NewReader(bytes.NewReader(reqBytes))
- decoder := xml.NewDecoder(reader)
- if err = decoder.Decode(req); err != nil {
- return nil, fmt.Errorf("failed to defalte decode: %w", err)
- }
- default:
- reader := flate.NewReader(bytes.NewReader(reqBytes))
- decoder := xml.NewDecoder(reader)
- if err = decoder.Decode(req); err != nil {
- if err := xml.Unmarshal(reqBytes, req); err != nil {
- return nil, fmt.Errorf("failed to unmarshal: %w", err)
- }
- }
+ if err := xml.Unmarshal(data, req); err != nil {
+ return nil, err
}
-
return req, nil
}
-func DecodeSignature(encoding string, message string) (*xml_dsig.SignatureType, error) {
- retBytes := []byte(message)
-
+func DecodeSignature(encoding string, b64 bool, message string) (*xml_dsig.SignatureType, error) {
+ data, err := InflateAndDecode(encoding, b64, message)
+ if err != nil {
+ return nil, err
+ }
ret := &xml_dsig.SignatureType{}
- switch encoding {
- case EncodingDeflate:
- reader := flate.NewReader(bytes.NewReader(retBytes))
- decoder := xml.NewDecoder(reader)
- if err := decoder.Decode(ret); err != nil {
- return nil, fmt.Errorf("failed to defalte decode: %w", err)
- }
- default:
- reader := flate.NewReader(bytes.NewReader(retBytes))
- decoder := xml.NewDecoder(reader)
- if err := decoder.Decode(ret); err != nil {
- if err := xml.Unmarshal(retBytes, ret); err != nil {
- return nil, fmt.Errorf("failed to unmarshal: %w", err)
- }
- }
+ if err := xml.Unmarshal(data, ret); err != nil {
+ return nil, err
}
-
return ret, nil
}
@@ -148,50 +117,45 @@ func DecodeAttributeQuery(request string) (*samlp.AttributeQueryType, error) {
}
func DecodeLogoutRequest(encoding string, message string) (*samlp.LogoutRequestType, error) {
- reqBytes, err := base64.StdEncoding.DecodeString(message)
+ data, err := InflateAndDecode(encoding, true, message)
if err != nil {
return nil, err
}
-
req := &samlp.LogoutRequestType{}
- switch encoding {
- case "":
- reader := flate.NewReader(bytes.NewReader(reqBytes))
- decoder := xml.NewDecoder(reader)
- if err = decoder.Decode(req); err != nil {
- return nil, err
- }
- case EncodingDeflate:
- reader := flate.NewReader(bytes.NewReader(reqBytes))
- decoder := xml.NewDecoder(reader)
- if err = decoder.Decode(req); err != nil {
- return nil, err
- }
- default:
- return nil, fmt.Errorf("unknown encoding")
+ if err := xml.Unmarshal(data, req); err != nil {
+ return nil, err
}
-
return req, nil
}
-func DecodeResponse(encoding string, message string) (*samlp.ResponseType, error) {
-
+func DecodeResponse(encoding string, b64 bool, message string) (*samlp.ResponseType, error) {
+ data, err := InflateAndDecode(encoding, b64, message)
+ if err != nil {
+ return nil, err
+ }
req := &samlp.ResponseType{}
- switch encoding {
- case "":
- decoder := xml.NewDecoder(bytes.NewReader([]byte(message)))
- if err := decoder.Decode(req); err != nil {
+ if err := xml.Unmarshal(data, req); err != nil {
+ return nil, err
+ }
+ return req, nil
+}
+
+func InflateAndDecode(encoding string, b64 bool, message string) (_ []byte, err error) {
+ data := []byte(message)
+ if b64 {
+ data, err = base64.StdEncoding.DecodeString(message)
+ if err != nil {
return nil, err
}
+ }
+ switch encoding {
+ case "":
+ return data, nil
case EncodingDeflate:
- reader := flate.NewReader(bytes.NewReader([]byte(message)))
- decoder := xml.NewDecoder(reader)
- if err := decoder.Decode(req); err != nil {
- return nil, err
- }
+ r := flate.NewReader(bytes.NewBuffer(data))
+ defer r.Close()
+ return io.ReadAll(r)
default:
return nil, fmt.Errorf("unknown encoding")
}
-
- return req, nil
}