155 lines
3.2 KiB
Go
155 lines
3.2 KiB
Go
package saml
|
|
|
|
import (
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/crewjam/saml"
|
|
"github.com/crewjam/saml/samlsp"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const defaultNameIdentifier = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
|
|
type (
|
|
SamlSPService struct {
|
|
log *zap.Logger
|
|
|
|
Enabled bool
|
|
|
|
IdpURL url.URL
|
|
Host url.URL
|
|
IDPUserMeta *IdpIdentityPayload
|
|
IDPMeta *saml.EntityDescriptor
|
|
|
|
sp saml.ServiceProvider
|
|
handler *samlsp.Middleware
|
|
}
|
|
|
|
SamlSPArgs struct {
|
|
Enabled bool
|
|
|
|
AcsURL string
|
|
MetaURL string
|
|
SloURL string
|
|
|
|
SignRequests bool
|
|
SignatureMethod string
|
|
|
|
Binding string
|
|
|
|
// user meta from idp
|
|
IdentityPayload IdpIdentityPayload
|
|
|
|
IdpURL url.URL
|
|
Host url.URL
|
|
Certificate *x509.Certificate
|
|
PrivateKey *rsa.PrivateKey
|
|
IdpMeta *saml.EntityDescriptor
|
|
}
|
|
)
|
|
|
|
// NewSamlSPService loads the certificates and registers the
|
|
// already fetched IDP metadata into the SAML middleware
|
|
func NewSamlSPService(log *zap.Logger, args SamlSPArgs) (s *SamlSPService, err error) {
|
|
metadataURL, _ := url.Parse(args.MetaURL)
|
|
acsURL, _ := url.Parse(args.AcsURL)
|
|
logoutURL, _ := url.Parse(args.SloURL)
|
|
|
|
sp := saml.ServiceProvider{
|
|
Key: args.PrivateKey,
|
|
Certificate: args.Certificate,
|
|
IDPMetadata: args.IdpMeta,
|
|
|
|
MetadataURL: *args.Host.ResolveReference(metadataURL),
|
|
AcsURL: *args.Host.ResolveReference(acsURL),
|
|
SloURL: *args.Host.ResolveReference(logoutURL),
|
|
}
|
|
|
|
if args.SignRequests {
|
|
sp.SignatureMethod = args.SignatureMethod
|
|
}
|
|
|
|
// default to GET
|
|
if args.Binding == "" {
|
|
args.Binding = saml.HTTPRedirectBinding
|
|
}
|
|
|
|
opts := samlsp.Options{
|
|
URL: args.Host,
|
|
Key: sp.Key,
|
|
Certificate: sp.Certificate,
|
|
IDPMetadata: args.IdpMeta,
|
|
}
|
|
|
|
// internal samlsp service
|
|
handler, err := samlsp.New(opts)
|
|
if err != nil {
|
|
err = fmt.Errorf("could not init SAML SP handler: %w", err)
|
|
return
|
|
}
|
|
|
|
handler.RequestTracker = samlsp.DefaultRequestTracker(opts, &handler.ServiceProvider)
|
|
handler.ServiceProvider = sp
|
|
handler.Binding = args.Binding
|
|
|
|
s = &SamlSPService{
|
|
log: log,
|
|
|
|
Enabled: args.Enabled,
|
|
|
|
sp: sp,
|
|
handler: handler,
|
|
|
|
IdpURL: args.IdpURL,
|
|
Host: args.Host,
|
|
IDPUserMeta: &args.IdentityPayload,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (ssp *SamlSPService) NameIdentifier() string {
|
|
return strings.TrimPrefix(defaultNameIdentifier, "urn:oasis:names:tc:SAML:1.1:nameid-format:")
|
|
}
|
|
|
|
// GuessIdentifier tries to guess the necessary (email) key
|
|
// for external authentication
|
|
func (ssp *SamlSPService) GuessIdentifier(payload map[string][]string) string {
|
|
tryValues := []string{
|
|
ssp.IDPUserMeta.Identifier,
|
|
ssp.NameIdentifier(),
|
|
defaultNameIdentifier,
|
|
"urn:oasis:names:tc:SAML:attribute:subject-id",
|
|
"email",
|
|
"mail",
|
|
}
|
|
|
|
for _, v := range tryValues {
|
|
if _, ok := payload[v]; ok {
|
|
return payload[v][0]
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (ssp *SamlSPService) Handler() *samlsp.Middleware {
|
|
return ssp.handler
|
|
}
|
|
|
|
// ServeHTTP enables us to use the service directly
|
|
// in the router
|
|
func (ssp *SamlSPService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
if ssp.handler == nil {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
|
|
ssp.handler.ServeHTTP(w, r)
|
|
}
|