Add authserver module
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package sso
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
samlMetadataNS = "urn:oasis:names:tc:SAML:2.0:metadata"
|
||||
samlProtocolNS = "urn:oasis:names:tc:SAML:2.0:protocol"
|
||||
xmlDsigNS = "http://www.w3.org/2000/09/xmldsig#"
|
||||
)
|
||||
|
||||
type MetadataConfig struct {
|
||||
EntityID string
|
||||
ACSURL string
|
||||
CertFile string
|
||||
}
|
||||
|
||||
func BuildSPMetadata(cfg MetadataConfig) ([]byte, error) {
|
||||
if cfg.EntityID == "" {
|
||||
return nil, errors.New("saml entity id is required")
|
||||
}
|
||||
if cfg.ACSURL == "" {
|
||||
return nil, errors.New("saml acs url is required")
|
||||
}
|
||||
cert, err := readCertificate(cfg.CertFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
doc := entityDescriptor{
|
||||
XMLName: xml.Name{Local: "EntityDescriptor"},
|
||||
Xmlns: samlMetadataNS,
|
||||
EntityID: cfg.EntityID,
|
||||
SPSSODescriptor: spSSODescriptor{
|
||||
ProtocolSupportEnumeration: samlProtocolNS,
|
||||
AuthnRequestsSigned: true,
|
||||
WantAssertionsSigned: true,
|
||||
KeyDescriptors: []keyDescriptor{
|
||||
newKeyDescriptor("signing", cert),
|
||||
newKeyDescriptor("encryption", cert),
|
||||
},
|
||||
NameIDFormats: []string{
|
||||
"urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
|
||||
"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
||||
},
|
||||
AssertionConsumerServices: []assertionConsumerService{
|
||||
{
|
||||
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
||||
Location: cfg.ACSURL,
|
||||
Index: 0,
|
||||
IsDefault: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
body, err := xml.MarshalIndent(doc, "", " ")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := append([]byte(xml.Header), body...)
|
||||
out = append(out, '\n')
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func readCertificate(path string) (string, error) {
|
||||
if path == "" {
|
||||
return "", errors.New("saml sp cert file is required")
|
||||
}
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
block, _ := pem.Decode(raw)
|
||||
if block == nil {
|
||||
return stripPEMCertificate(string(raw)), nil
|
||||
}
|
||||
if _, err := x509.ParseCertificate(block.Bytes); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(block.Bytes), nil
|
||||
}
|
||||
|
||||
func stripPEMCertificate(value string) string {
|
||||
value = strings.ReplaceAll(value, "-----BEGIN CERTIFICATE-----", "")
|
||||
value = strings.ReplaceAll(value, "-----END CERTIFICATE-----", "")
|
||||
value = strings.ReplaceAll(value, "\n", "")
|
||||
value = strings.ReplaceAll(value, "\r", "")
|
||||
value = strings.ReplaceAll(value, " ", "")
|
||||
return value
|
||||
}
|
||||
|
||||
func newKeyDescriptor(use string, cert string) keyDescriptor {
|
||||
return keyDescriptor{
|
||||
Use: use,
|
||||
KeyInfo: keyInfo{
|
||||
Xmlns: xmlDsigNS,
|
||||
X509Data: x509Data{
|
||||
X509Certificate: cert,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type entityDescriptor struct {
|
||||
XMLName xml.Name `xml:"EntityDescriptor"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
EntityID string `xml:"entityID,attr"`
|
||||
SPSSODescriptor spSSODescriptor `xml:"SPSSODescriptor"`
|
||||
}
|
||||
|
||||
type spSSODescriptor struct {
|
||||
ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"`
|
||||
AuthnRequestsSigned bool `xml:"AuthnRequestsSigned,attr"`
|
||||
WantAssertionsSigned bool `xml:"WantAssertionsSigned,attr"`
|
||||
KeyDescriptors []keyDescriptor `xml:"KeyDescriptor"`
|
||||
NameIDFormats []string `xml:"NameIDFormat"`
|
||||
AssertionConsumerServices []assertionConsumerService `xml:"AssertionConsumerService"`
|
||||
}
|
||||
|
||||
type keyDescriptor struct {
|
||||
Use string `xml:"use,attr"`
|
||||
KeyInfo keyInfo `xml:"KeyInfo"`
|
||||
}
|
||||
|
||||
type keyInfo struct {
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
X509Data x509Data `xml:"X509Data"`
|
||||
}
|
||||
|
||||
type x509Data struct {
|
||||
X509Certificate string `xml:"X509Certificate"`
|
||||
}
|
||||
|
||||
type assertionConsumerService struct {
|
||||
Binding string `xml:"Binding,attr"`
|
||||
Location string `xml:"Location,attr"`
|
||||
Index int `xml:"index,attr"`
|
||||
IsDefault bool `xml:"isDefault,attr"`
|
||||
}
|
||||
Reference in New Issue
Block a user