2022-04-08 23:06:48 +08:00
|
|
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package object
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"compress/flate"
|
|
|
|
"crypto"
|
|
|
|
"crypto/rsa"
|
|
|
|
"encoding/base64"
|
2022-04-11 21:11:31 +08:00
|
|
|
"encoding/json"
|
2022-04-08 23:06:48 +08:00
|
|
|
"encoding/pem"
|
|
|
|
"encoding/xml"
|
2023-06-04 01:25:18 +08:00
|
|
|
"errors"
|
2022-04-08 23:06:48 +08:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/RobotsAndPencils/go-saml"
|
|
|
|
"github.com/beevik/etree"
|
|
|
|
"github.com/golang-jwt/jwt/v4"
|
2023-08-21 13:58:15 +08:00
|
|
|
"github.com/google/uuid"
|
2022-04-08 23:06:48 +08:00
|
|
|
dsig "github.com/russellhaering/goxmldsig"
|
|
|
|
)
|
|
|
|
|
2022-08-09 16:50:49 +08:00
|
|
|
// NewSamlResponse
|
2022-08-07 12:26:14 +08:00
|
|
|
// returns a saml2 response
|
2023-10-17 15:40:41 +08:00
|
|
|
func NewSamlResponse(application *Application, user *User, host string, certificate string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
|
2022-04-08 23:06:48 +08:00
|
|
|
samlResponse := &etree.Element{
|
|
|
|
Space: "samlp",
|
|
|
|
Tag: "Response",
|
|
|
|
}
|
|
|
|
now := time.Now().UTC().Format(time.RFC3339)
|
|
|
|
expireTime := time.Now().UTC().Add(time.Hour * 24).Format(time.RFC3339)
|
|
|
|
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
|
|
|
|
samlResponse.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")
|
2023-08-21 13:58:15 +08:00
|
|
|
arId := uuid.New()
|
2022-04-08 23:06:48 +08:00
|
|
|
|
|
|
|
samlResponse.CreateAttr("ID", fmt.Sprintf("_%s", arId))
|
|
|
|
samlResponse.CreateAttr("Version", "2.0")
|
|
|
|
samlResponse.CreateAttr("IssueInstant", now)
|
|
|
|
samlResponse.CreateAttr("Destination", destination)
|
2022-06-28 22:05:02 +08:00
|
|
|
samlResponse.CreateAttr("InResponseTo", requestId)
|
2022-04-08 23:06:48 +08:00
|
|
|
samlResponse.CreateElement("saml:Issuer").SetText(host)
|
|
|
|
|
|
|
|
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "urn:oasis:names:tc:SAML:2.0:status:Success")
|
|
|
|
|
|
|
|
assertion := samlResponse.CreateElement("saml:Assertion")
|
|
|
|
assertion.CreateAttr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
|
|
|
assertion.CreateAttr("xmlns:xs", "http://www.w3.org/2001/XMLSchema")
|
2023-08-21 13:58:15 +08:00
|
|
|
assertion.CreateAttr("ID", fmt.Sprintf("_%s", uuid.New()))
|
2022-04-08 23:06:48 +08:00
|
|
|
assertion.CreateAttr("Version", "2.0")
|
|
|
|
assertion.CreateAttr("IssueInstant", now)
|
|
|
|
assertion.CreateElement("saml:Issuer").SetText(host)
|
|
|
|
subject := assertion.CreateElement("saml:Subject")
|
2023-02-18 16:42:45 +08:00
|
|
|
subject.CreateElement("saml:NameID").SetText(user.Name)
|
2022-04-08 23:06:48 +08:00
|
|
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
|
|
|
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
|
|
|
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
2022-06-28 22:05:02 +08:00
|
|
|
subjectConfirmationData.CreateAttr("InResponseTo", requestId)
|
2022-04-08 23:06:48 +08:00
|
|
|
subjectConfirmationData.CreateAttr("Recipient", destination)
|
|
|
|
subjectConfirmationData.CreateAttr("NotOnOrAfter", expireTime)
|
|
|
|
condition := assertion.CreateElement("saml:Conditions")
|
|
|
|
condition.CreateAttr("NotBefore", now)
|
|
|
|
condition.CreateAttr("NotOnOrAfter", expireTime)
|
|
|
|
audience := condition.CreateElement("saml:AudienceRestriction")
|
|
|
|
audience.CreateElement("saml:Audience").SetText(iss)
|
|
|
|
for _, value := range redirectUri {
|
|
|
|
audience.CreateElement("saml:Audience").SetText(value)
|
|
|
|
}
|
|
|
|
authnStatement := assertion.CreateElement("saml:AuthnStatement")
|
|
|
|
authnStatement.CreateAttr("AuthnInstant", now)
|
2023-08-21 13:58:15 +08:00
|
|
|
authnStatement.CreateAttr("SessionIndex", fmt.Sprintf("_%s", uuid.New()))
|
2022-04-08 23:06:48 +08:00
|
|
|
authnStatement.CreateAttr("SessionNotOnOrAfter", expireTime)
|
|
|
|
authnStatement.CreateElement("saml:AuthnContext").CreateElement("saml:AuthnContextClassRef").SetText("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")
|
|
|
|
|
|
|
|
attributes := assertion.CreateElement("saml:AttributeStatement")
|
2023-03-29 23:42:47 +08:00
|
|
|
|
2022-04-08 23:06:48 +08:00
|
|
|
email := attributes.CreateElement("saml:Attribute")
|
|
|
|
email.CreateAttr("Name", "Email")
|
|
|
|
email.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
|
|
|
email.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Email)
|
2023-03-29 23:42:47 +08:00
|
|
|
|
2022-04-08 23:06:48 +08:00
|
|
|
name := attributes.CreateElement("saml:Attribute")
|
|
|
|
name.CreateAttr("Name", "Name")
|
|
|
|
name.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
|
|
|
name.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Name)
|
2023-03-29 23:42:47 +08:00
|
|
|
|
2022-04-08 23:06:48 +08:00
|
|
|
displayName := attributes.CreateElement("saml:Attribute")
|
|
|
|
displayName.CreateAttr("Name", "DisplayName")
|
|
|
|
displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
|
|
|
displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName)
|
|
|
|
|
2023-10-17 15:40:41 +08:00
|
|
|
for _, item := range application.SamlAttributes {
|
|
|
|
role := attributes.CreateElement("saml:Attribute")
|
|
|
|
role.CreateAttr("Name", item.Name)
|
|
|
|
role.CreateAttr("NameFormat", item.NameFormat)
|
|
|
|
role.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(item.Value)
|
|
|
|
}
|
|
|
|
|
2023-03-29 23:42:47 +08:00
|
|
|
roles := attributes.CreateElement("saml:Attribute")
|
|
|
|
roles.CreateAttr("Name", "Roles")
|
|
|
|
roles.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
2023-05-30 15:49:39 +08:00
|
|
|
err := ExtendUserWithRolesAndPermissions(user)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-03-29 23:42:47 +08:00
|
|
|
for _, role := range user.Roles {
|
|
|
|
roles.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(role.Name)
|
|
|
|
}
|
|
|
|
|
2022-04-08 23:06:48 +08:00
|
|
|
return samlResponse, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type X509Key struct {
|
|
|
|
X509Certificate string
|
|
|
|
PrivateKey string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (x X509Key) GetKeyPair() (privateKey *rsa.PrivateKey, cert []byte, err error) {
|
|
|
|
cert, _ = base64.StdEncoding.DecodeString(x.X509Certificate)
|
|
|
|
privateKey, err = jwt.ParseRSAPrivateKeyFromPEM([]byte(x.PrivateKey))
|
|
|
|
return privateKey, cert, err
|
|
|
|
}
|
|
|
|
|
2022-08-09 16:50:49 +08:00
|
|
|
// IdpEntityDescriptor
|
2022-08-07 12:26:14 +08:00
|
|
|
// SAML METADATA
|
2022-04-08 23:06:48 +08:00
|
|
|
type IdpEntityDescriptor struct {
|
|
|
|
XMLName xml.Name `xml:"EntityDescriptor"`
|
|
|
|
DS string `xml:"xmlns:ds,attr"`
|
|
|
|
XMLNS string `xml:"xmlns,attr"`
|
|
|
|
MD string `xml:"xmlns:md,attr"`
|
|
|
|
EntityId string `xml:"entityID,attr"`
|
|
|
|
|
|
|
|
IdpSSODescriptor IdpSSODescriptor `xml:"IDPSSODescriptor"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type KeyInfo struct {
|
|
|
|
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"`
|
|
|
|
X509Data X509Data `xml:",innerxml"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type X509Data struct {
|
|
|
|
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"`
|
|
|
|
X509Certificate X509Certificate `xml:",innerxml"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type X509Certificate struct {
|
|
|
|
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Certificate"`
|
|
|
|
Cert string `xml:",innerxml"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type KeyDescriptor struct {
|
|
|
|
XMLName xml.Name `xml:"KeyDescriptor"`
|
|
|
|
Use string `xml:"use,attr"`
|
|
|
|
KeyInfo KeyInfo `xml:"KeyInfo"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type IdpSSODescriptor struct {
|
|
|
|
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata IDPSSODescriptor"`
|
|
|
|
ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"`
|
|
|
|
SigningKeyDescriptor KeyDescriptor
|
|
|
|
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
|
|
|
|
SingleSignOnService SingleSignOnService `xml:"SingleSignOnService"`
|
|
|
|
Attribute []Attribute `xml:"Attribute"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type NameIDFormat struct {
|
|
|
|
XMLName xml.Name
|
|
|
|
Value string `xml:",innerxml"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type SingleSignOnService struct {
|
|
|
|
XMLName xml.Name
|
|
|
|
Binding string `xml:"Binding,attr"`
|
|
|
|
Location string `xml:"Location,attr"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Attribute struct {
|
|
|
|
XMLName xml.Name
|
2023-10-17 15:40:41 +08:00
|
|
|
Name string `xml:"Name,attr"`
|
|
|
|
NameFormat string `xml:"NameFormat,attr"`
|
|
|
|
FriendlyName string `xml:"FriendlyName,attr"`
|
|
|
|
Xmlns string `xml:"xmlns,attr"`
|
|
|
|
Values []string `xml:"AttributeValue"`
|
2022-04-08 23:06:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
|
2023-05-30 15:49:39 +08:00
|
|
|
cert, err := getCertByApplication(application)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-06-04 01:25:18 +08:00
|
|
|
if cert == nil {
|
|
|
|
return nil, errors.New("please set a cert for the application first")
|
|
|
|
}
|
|
|
|
|
2023-10-10 19:19:20 +08:00
|
|
|
if cert.Certificate == "" {
|
|
|
|
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
|
|
|
}
|
|
|
|
|
2022-07-23 09:40:51 +08:00
|
|
|
block, _ := pem.Decode([]byte(cert.Certificate))
|
|
|
|
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
2022-04-08 23:06:48 +08:00
|
|
|
|
|
|
|
originFrontend, originBackend := getOriginFromHost(host)
|
2022-09-03 15:12:26 +08:00
|
|
|
|
2022-04-08 23:06:48 +08:00
|
|
|
d := IdpEntityDescriptor{
|
|
|
|
XMLName: xml.Name{
|
|
|
|
Local: "md:EntityDescriptor",
|
|
|
|
},
|
|
|
|
DS: "http://www.w3.org/2000/09/xmldsig#",
|
|
|
|
XMLNS: "urn:oasis:names:tc:SAML:2.0:metadata",
|
|
|
|
MD: "urn:oasis:names:tc:SAML:2.0:metadata",
|
|
|
|
EntityId: originBackend,
|
|
|
|
IdpSSODescriptor: IdpSSODescriptor{
|
|
|
|
SigningKeyDescriptor: KeyDescriptor{
|
|
|
|
Use: "signing",
|
|
|
|
KeyInfo: KeyInfo{
|
|
|
|
X509Data: X509Data{
|
|
|
|
X509Certificate: X509Certificate{
|
2022-07-23 09:40:51 +08:00
|
|
|
Cert: certificate,
|
2022-04-08 23:06:48 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
NameIDFormats: []NameIDFormat{
|
|
|
|
{Value: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"},
|
|
|
|
{Value: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"},
|
|
|
|
{Value: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"},
|
|
|
|
},
|
|
|
|
Attribute: []Attribute{
|
|
|
|
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Email", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "E-Mail"},
|
|
|
|
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "DisplayName", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "displayName"},
|
|
|
|
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Name", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "Name"},
|
|
|
|
},
|
|
|
|
SingleSignOnService: SingleSignOnService{
|
|
|
|
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
|
|
|
Location: fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name),
|
|
|
|
},
|
|
|
|
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return &d, nil
|
|
|
|
}
|
|
|
|
|
2022-06-28 22:05:02 +08:00
|
|
|
// GetSamlResponse generates a SAML2.0 response
|
|
|
|
// parameter samlRequest is saml request in base64 format
|
2023-01-03 19:42:12 +08:00
|
|
|
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, string, error) {
|
|
|
|
// request type
|
|
|
|
method := "GET"
|
|
|
|
|
2022-06-28 22:05:02 +08:00
|
|
|
// base64 decode
|
2022-04-08 23:06:48 +08:00
|
|
|
defated, err := base64.StdEncoding.DecodeString(samlRequest)
|
|
|
|
if err != nil {
|
2023-02-24 21:20:57 +08:00
|
|
|
return "", "", method, fmt.Errorf("err: Failed to decode SAML request , %s", err.Error())
|
2022-04-08 23:06:48 +08:00
|
|
|
}
|
2023-02-24 21:20:57 +08:00
|
|
|
|
2022-06-28 22:05:02 +08:00
|
|
|
// decompress
|
2022-04-08 23:06:48 +08:00
|
|
|
var buffer bytes.Buffer
|
|
|
|
rdr := flate.NewReader(bytes.NewReader(defated))
|
2023-06-21 13:55:20 +03:00
|
|
|
|
|
|
|
for {
|
|
|
|
_, err := io.CopyN(&buffer, rdr, 1024)
|
|
|
|
if err != nil {
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
return "", "", "", err
|
|
|
|
}
|
2023-02-24 21:20:57 +08:00
|
|
|
}
|
2023-06-21 13:55:20 +03:00
|
|
|
|
2022-04-08 23:06:48 +08:00
|
|
|
var authnRequest saml.AuthnRequest
|
|
|
|
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
|
|
|
|
if err != nil {
|
2023-02-24 21:20:57 +08:00
|
|
|
return "", "", method, fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request. %s", err.Error())
|
2022-04-08 23:06:48 +08:00
|
|
|
}
|
2022-06-28 22:05:02 +08:00
|
|
|
|
|
|
|
// verify samlRequest
|
2022-12-21 00:35:33 +08:00
|
|
|
if isValid := application.IsRedirectUriValid(authnRequest.Issuer.Url); !isValid {
|
2023-01-03 19:42:12 +08:00
|
|
|
return "", "", method, fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer.Url)
|
2022-04-08 23:06:48 +08:00
|
|
|
}
|
|
|
|
|
2022-07-23 09:40:51 +08:00
|
|
|
// get certificate string
|
2023-05-30 15:49:39 +08:00
|
|
|
cert, err := getCertByApplication(application)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", "", err
|
|
|
|
}
|
|
|
|
|
2023-10-10 19:19:20 +08:00
|
|
|
if cert.Certificate == "" {
|
|
|
|
return "", "", "", fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
|
|
|
}
|
|
|
|
|
2022-07-23 09:40:51 +08:00
|
|
|
block, _ := pem.Decode([]byte(cert.Certificate))
|
|
|
|
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
2022-04-08 23:06:48 +08:00
|
|
|
|
2022-12-13 22:32:45 +08:00
|
|
|
// redirect Url (Assertion Consumer Url)
|
|
|
|
if application.SamlReplyUrl != "" {
|
2023-01-03 19:42:12 +08:00
|
|
|
method = "POST"
|
2022-12-13 22:32:45 +08:00
|
|
|
authnRequest.AssertionConsumerServiceURL = application.SamlReplyUrl
|
2023-02-24 21:20:57 +08:00
|
|
|
} else if authnRequest.AssertionConsumerServiceURL == "" {
|
|
|
|
return "", "", "", fmt.Errorf("err: SAML request don't has attribute 'AssertionConsumerServiceURL' in <samlp:AuthnRequest>")
|
2022-12-13 22:32:45 +08:00
|
|
|
}
|
|
|
|
|
2023-02-24 21:20:57 +08:00
|
|
|
_, originBackend := getOriginFromHost(host)
|
2022-06-28 22:05:02 +08:00
|
|
|
// build signedResponse
|
2023-10-17 15:40:41 +08:00
|
|
|
samlResponse, _ := NewSamlResponse(application, user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
|
2022-04-08 23:06:48 +08:00
|
|
|
randomKeyStore := &X509Key{
|
|
|
|
PrivateKey: cert.PrivateKey,
|
2022-07-23 09:40:51 +08:00
|
|
|
X509Certificate: certificate,
|
2022-04-08 23:06:48 +08:00
|
|
|
}
|
|
|
|
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
|
|
|
ctx.Hash = crypto.SHA1
|
2022-06-17 18:35:44 +08:00
|
|
|
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
|
|
|
|
//if err != nil {
|
|
|
|
// return "", "", fmt.Errorf("err: %s", err.Error())
|
|
|
|
//}
|
|
|
|
sig, err := ctx.ConstructSignature(samlResponse, true)
|
|
|
|
samlResponse.InsertChildAt(1, sig)
|
2022-04-08 23:06:48 +08:00
|
|
|
|
|
|
|
doc := etree.NewDocument()
|
2022-06-17 18:35:44 +08:00
|
|
|
doc.SetRoot(samlResponse)
|
2022-06-28 22:05:02 +08:00
|
|
|
xmlBytes, err := doc.WriteToBytes()
|
2022-04-08 23:06:48 +08:00
|
|
|
if err != nil {
|
2023-02-24 21:20:57 +08:00
|
|
|
return "", "", method, fmt.Errorf("err: Failed to serializes the SAML request into bytes, %s", err.Error())
|
2022-04-08 23:06:48 +08:00
|
|
|
}
|
2022-06-28 22:05:02 +08:00
|
|
|
|
|
|
|
// compress
|
|
|
|
if application.EnableSamlCompress {
|
|
|
|
flated := bytes.NewBuffer(nil)
|
|
|
|
writer, err := flate.NewWriter(flated, flate.DefaultCompression)
|
|
|
|
if err != nil {
|
2023-02-24 21:20:57 +08:00
|
|
|
return "", "", method, err
|
|
|
|
}
|
|
|
|
_, err = writer.Write(xmlBytes)
|
|
|
|
if err != nil {
|
|
|
|
return "", "", "", err
|
|
|
|
}
|
|
|
|
err = writer.Close()
|
|
|
|
if err != nil {
|
|
|
|
return "", "", "", err
|
2022-06-28 22:05:02 +08:00
|
|
|
}
|
|
|
|
xmlBytes = flated.Bytes()
|
|
|
|
}
|
|
|
|
// base64 encode
|
|
|
|
res := base64.StdEncoding.EncodeToString(xmlBytes)
|
2023-02-24 21:20:57 +08:00
|
|
|
return res, authnRequest.AssertionConsumerServiceURL, method, err
|
2022-04-08 23:06:48 +08:00
|
|
|
}
|
2022-04-11 21:11:31 +08:00
|
|
|
|
2022-06-28 22:05:02 +08:00
|
|
|
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
2022-04-11 21:11:31 +08:00
|
|
|
func NewSamlResponse11(user *User, requestID string, host string) *etree.Element {
|
|
|
|
samlResponse := &etree.Element{
|
|
|
|
Space: "samlp",
|
|
|
|
Tag: "Response",
|
|
|
|
}
|
2022-08-07 12:26:14 +08:00
|
|
|
// create samlresponse
|
2022-04-11 21:11:31 +08:00
|
|
|
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:1.0:protocol")
|
|
|
|
samlResponse.CreateAttr("MajorVersion", "1")
|
|
|
|
samlResponse.CreateAttr("MinorVersion", "1")
|
|
|
|
|
2023-08-21 13:58:15 +08:00
|
|
|
responseID := uuid.New()
|
2022-04-11 21:11:31 +08:00
|
|
|
samlResponse.CreateAttr("ResponseID", fmt.Sprintf("_%s", responseID))
|
|
|
|
samlResponse.CreateAttr("InResponseTo", requestID)
|
|
|
|
|
|
|
|
now := time.Now().UTC().Format(time.RFC3339)
|
|
|
|
expireTime := time.Now().UTC().Add(time.Hour * 24).Format(time.RFC3339)
|
|
|
|
|
|
|
|
samlResponse.CreateAttr("IssueInstant", now)
|
|
|
|
|
|
|
|
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "samlp:Success")
|
|
|
|
|
2022-08-07 12:26:14 +08:00
|
|
|
// create assertion which is inside the response
|
2022-04-11 21:11:31 +08:00
|
|
|
assertion := samlResponse.CreateElement("saml:Assertion")
|
|
|
|
assertion.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:1.0:assertion")
|
|
|
|
assertion.CreateAttr("MajorVersion", "1")
|
|
|
|
assertion.CreateAttr("MinorVersion", "1")
|
2023-08-21 13:58:15 +08:00
|
|
|
assertion.CreateAttr("AssertionID", uuid.New().String())
|
2022-04-11 21:11:31 +08:00
|
|
|
assertion.CreateAttr("Issuer", host)
|
|
|
|
assertion.CreateAttr("IssueInstant", now)
|
|
|
|
|
|
|
|
condition := assertion.CreateElement("saml:Conditions")
|
|
|
|
condition.CreateAttr("NotBefore", now)
|
|
|
|
condition.CreateAttr("NotOnOrAfter", expireTime)
|
|
|
|
|
2022-08-07 12:26:14 +08:00
|
|
|
// AuthenticationStatement inside assertion
|
2022-04-11 21:11:31 +08:00
|
|
|
authenticationStatement := assertion.CreateElement("saml:AuthenticationStatement")
|
|
|
|
authenticationStatement.CreateAttr("AuthenticationMethod", "urn:oasis:names:tc:SAML:1.0:am:password")
|
|
|
|
authenticationStatement.CreateAttr("AuthenticationInstant", now)
|
|
|
|
|
2022-08-07 12:26:14 +08:00
|
|
|
// subject inside AuthenticationStatement
|
2022-04-11 21:11:31 +08:00
|
|
|
subject := assertion.CreateElement("saml:Subject")
|
2022-08-07 12:26:14 +08:00
|
|
|
// nameIdentifier inside subject
|
2022-04-11 21:11:31 +08:00
|
|
|
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
|
2022-08-07 12:26:14 +08:00
|
|
|
// nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
|
2022-04-11 21:11:31 +08:00
|
|
|
nameIdentifier.SetText(user.Name)
|
|
|
|
|
2022-08-07 12:26:14 +08:00
|
|
|
// subjectConfirmation inside subject
|
2022-04-11 21:11:31 +08:00
|
|
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
|
|
|
subjectConfirmation.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
|
|
|
|
|
|
|
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
|
|
|
|
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
|
|
|
|
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
|
|
|
|
nameIdentifierInAttribute.SetText(user.Name)
|
|
|
|
|
|
|
|
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
|
|
|
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
|
|
|
|
|
|
|
data, _ := json.Marshal(user)
|
|
|
|
tmp := map[string]string{}
|
2023-02-24 21:20:57 +08:00
|
|
|
err := json.Unmarshal(data, &tmp)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2022-04-11 21:11:31 +08:00
|
|
|
|
|
|
|
for k, v := range tmp {
|
|
|
|
if v != "" {
|
|
|
|
attr := attributeStatement.CreateElement("saml:Attribute")
|
|
|
|
attr.CreateAttr("saml:AttributeName", k)
|
|
|
|
attr.CreateAttr("saml:AttributeNamespace", "http://www.ja-sig.org/products/cas/")
|
|
|
|
attr.CreateElement("saml:AttributeValue").SetText(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return samlResponse
|
|
|
|
}
|