AI Gateway Go 0.10.0 源码快照 + 旗舰版需求规划报告
M0-M7 已完成:核心网关(身份/RBAC/TOTP/OIDC/SAML/Provider/配额/路由/内容策略/审计/定价)+ 资源市场(MCP/Skills/数字员工)。 含 22 个 PostgreSQL 迁移、管理端/门户端前端源码、OpenAPI 契约、部署 compose。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package identity
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
"github.com/crewjam/saml"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
)
|
||||
|
||||
func TestValidateSAMLMetadataRequiresLiveSigningCertificate(t *testing.T) {
|
||||
now := time.Unix(1_800_000_000, 0)
|
||||
certificate := makeTestCertificate(t, now.Add(-time.Hour), now.Add(time.Hour))
|
||||
metadata := testIDPMetadata(certificate)
|
||||
if err := validateSAMLMetadata(context.Background(), metadata, true, now); err != nil {
|
||||
t.Fatalf("valid metadata rejected: %v", err)
|
||||
}
|
||||
|
||||
metadata.IDPSSODescriptors[0].KeyDescriptors = nil
|
||||
if err := validateSAMLMetadata(context.Background(), metadata, true, now); err == nil {
|
||||
t.Fatal("metadata without signing certificate was accepted")
|
||||
}
|
||||
|
||||
metadata = testIDPMetadata(makeTestCertificate(t, now.Add(-2*time.Hour), now.Add(-time.Hour)))
|
||||
if err := validateSAMLMetadata(context.Background(), metadata, true, now); err == nil {
|
||||
t.Fatal("expired signing certificate was accepted")
|
||||
}
|
||||
|
||||
metadata = testIDPMetadata(certificate)
|
||||
metadata.IDPSSODescriptors[0].SingleSignOnServices = nil
|
||||
if err := validateSAMLMetadata(context.Background(), metadata, true, now); err == nil {
|
||||
t.Fatal("metadata without redirect SSO endpoint was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSAMLEntityIDAndAttributes(t *testing.T) {
|
||||
if got, err := validateSAMLEntityID(" urn:example:gateway "); err != nil || got != "urn:example:gateway" {
|
||||
t.Fatalf("valid URN entity ID rejected: %q %v", got, err)
|
||||
}
|
||||
for _, raw := range []string{"", "relative", "https://user:secret@example.com/sp", "urn:example:sp#fragment"} {
|
||||
if _, err := validateSAMLEntityID(raw); err == nil {
|
||||
t.Fatalf("invalid entity ID accepted: %q", raw)
|
||||
}
|
||||
}
|
||||
assertion := &saml.Assertion{AttributeStatements: []saml.AttributeStatement{{Attributes: []saml.Attribute{{
|
||||
FriendlyName: "mail", Name: "urn:oid:0.9.2342.19200300.100.1.3",
|
||||
Values: []saml.AttributeValue{{Value: " user@example.com "}},
|
||||
}}}}}
|
||||
if got := samlAttribute(assertion, "mail"); got != "user@example.com" {
|
||||
t.Fatalf("unexpected friendly-name attribute %q", got)
|
||||
}
|
||||
if got := samlAttribute(assertion, "urn:oid:0.9.2342.19200300.100.1.3"); got != "user@example.com" {
|
||||
t.Fatalf("unexpected named attribute %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSAMLSignatureAlgorithmPolicyRejectsSHA1(t *testing.T) {
|
||||
document := etree.NewDocument()
|
||||
if err := document.ReadFromString(`<Response><Signature><SignedInfo><SignatureMethod Algorithm="` + dsig.RSASHA256SignatureMethod + `"/><Reference><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/></Reference></SignedInfo></Signature></Response>`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := validateSAMLSignatureAlgorithms(document.Root()); err != nil {
|
||||
t.Fatalf("SHA-256 signature policy rejected: %v", err)
|
||||
}
|
||||
document.Root().FindElement("./Signature/SignedInfo/SignatureMethod").SelectAttr("Algorithm").Value = dsig.RSASHA1SignatureMethod
|
||||
if err := validateSAMLSignatureAlgorithms(document.Root()); err == nil {
|
||||
t.Fatal("SHA-1 signature algorithm was accepted")
|
||||
}
|
||||
document.Root().FindElement("./Signature/SignedInfo/SignatureMethod").SelectAttr("Algorithm").Value = dsig.RSASHA256SignatureMethod
|
||||
document.Root().FindElement("./Signature/SignedInfo/Reference/DigestMethod").SelectAttr("Algorithm").Value = "http://www.w3.org/2000/09/xmldsig#sha1"
|
||||
if err := validateSAMLSignatureAlgorithms(document.Root()); err == nil {
|
||||
t.Fatal("SHA-1 digest algorithm was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func testIDPMetadata(certificate *x509.Certificate) *saml.EntityDescriptor {
|
||||
return &saml.EntityDescriptor{
|
||||
EntityID: "https://idp.example/metadata",
|
||||
IDPSSODescriptors: []saml.IDPSSODescriptor{{
|
||||
SSODescriptor: saml.SSODescriptor{RoleDescriptor: saml.RoleDescriptor{KeyDescriptors: []saml.KeyDescriptor{{
|
||||
Use: "signing", KeyInfo: saml.KeyInfo{X509Data: saml.X509Data{X509Certificates: []saml.X509Certificate{{Data: base64.StdEncoding.EncodeToString(certificate.Raw)}}}},
|
||||
}}}},
|
||||
SingleSignOnServices: []saml.Endpoint{{Binding: saml.HTTPRedirectBinding, Location: "http://127.0.0.1:9091/sso"}},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestCertificate(t *testing.T, notBefore, notAfter time.Time) *x509.Certificate {
|
||||
t.Helper()
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1), Subject: pkix.Name{CommonName: "SAML test"},
|
||||
NotBefore: notBefore, NotAfter: notAfter, KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
}
|
||||
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
certificate, err := x509.ParseCertificate(der)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return certificate
|
||||
}
|
||||
Reference in New Issue
Block a user