saml.go (104 lines of code) (raw):
package saml2alibabacloud
import (
"fmt"
"strconv"
"github.com/beevik/etree"
)
const (
assertionTag = "Assertion"
attributeStatementTag = "AttributeStatement"
attributeTag = "Attribute"
attributeValueTag = "AttributeValue"
responseTag = "Response"
)
//ErrMissingElement is the error type that indicates an element and/or attribute is
//missing. It provides a structured error that can be more appropriately acted
//upon.
type ErrMissingElement struct {
Tag, Attribute string
}
//ErrMissingAssertion indicates that an appropriate assertion element could not
//be found in the SAML Response
var (
ErrMissingAssertion = ErrMissingElement{Tag: assertionTag}
)
func (e ErrMissingElement) Error() string {
if e.Attribute != "" {
return fmt.Sprintf("missing %s attribute on %s element", e.Attribute, e.Tag)
}
return fmt.Sprintf("missing %s element", e.Tag)
}
// ExtractSessionDuration this will attempt to extract a session duration from the assertion
// see https://www.alibabacloud.com/help/doc-detail/110614.htm
func ExtractSessionDuration(data []byte) (int64, error) {
doc := etree.NewDocument()
if err := doc.ReadFromBytes(data); err != nil {
return 0, err
}
assertionElement := doc.FindElement(".//Assertion")
if assertionElement == nil {
return 0, ErrMissingAssertion
}
// log.Printf("tag: %s", assertionElement.Tag)
//Get the actual assertion attributes
attributeStatement := assertionElement.FindElement(childPath(assertionElement.Space, attributeStatementTag))
if attributeStatement == nil {
return 0, ErrMissingElement{Tag: attributeStatementTag}
}
attributes := attributeStatement.FindElements(childPath(assertionElement.Space, attributeTag))
for _, attribute := range attributes {
if attribute.SelectAttrValue("Name", "") != "https://www.aliyun.com/SAML-Role/Attributes/SessionDuration" {
continue
}
atributeValues := attribute.FindElements(childPath(assertionElement.Space, attributeValueTag))
for _, attrValue := range atributeValues {
return strconv.ParseInt(attrValue.Text(), 10, 64)
}
}
// TODO need to check `SessionNotOnOrAfter` attribute
return 0, nil
}
// ExtractDestinationURL will find the Destination URL to POST the SAML assertion to.
// This is necessary to support custom endpoints such as AlibabaCloud International without requiring
// hardcoded endpoints on the saml2alibabacloud side.
func ExtractDestinationURL(data []byte) (string, error) {
doc := etree.NewDocument()
if err := doc.ReadFromBytes(data); err != nil {
return "", err
}
rootElement := doc.Root()
if rootElement == nil {
return "", ErrMissingElement{Tag: responseTag}
}
destination := rootElement.SelectAttrValue("Destination", "none")
if destination == "none" {
// If Destination attribute is not found in Response (root) element,
// get the Recipient attribute from the SubjectConfirmationData element.
subjectConfirmationDataElement := doc.FindElement(".//SubjectConfirmationData")
if subjectConfirmationDataElement == nil {
return "", ErrMissingElement{Tag: responseTag}
}
destination = subjectConfirmationDataElement.SelectAttrValue("Recipient", "none")
if destination == "none" {
return "", ErrMissingElement{Tag: responseTag}
}
}
return destination, nil
}
// ExtractRamRoles given an assertion document extract the AlibabaCloud RAM roles
func ExtractRamRoles(data []byte) ([]string, error) {
ramRoles := []string{}
doc := etree.NewDocument()
if err := doc.ReadFromBytes(data); err != nil {
return ramRoles, err
}
// log.Printf("root tag: %s", doc.Root().Tag)
assertionElement := doc.FindElement(".//Assertion")
if assertionElement == nil {
return nil, ErrMissingAssertion
}
// log.Printf("tag: %s", assertionElement.Tag)
//Get the actual assertion attributes
attributeStatement := assertionElement.FindElement(childPath(assertionElement.Space, attributeStatementTag))
if attributeStatement == nil {
return nil, ErrMissingElement{Tag: attributeStatementTag}
}
// log.Printf("tag: %s", attributeStatement.Tag)
attributes := attributeStatement.FindElements(childPath(assertionElement.Space, attributeTag))
for _, attribute := range attributes {
if attribute.SelectAttrValue("Name", "") != "https://www.aliyun.com/SAML-Role/Attributes/Role" {
continue
}
atributeValues := attribute.FindElements(childPath(assertionElement.Space, attributeValueTag))
for _, attrValue := range atributeValues {
ramRoles = append(ramRoles, attrValue.Text())
}
}
return ramRoles, nil
}
func childPath(space, tag string) string {
if space == "" {
return "./" + tag
}
//log.Printf("query = %s", "./"+space+":"+tag)
return "./" + space + ":" + tag
}