| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397 |
- // Copyright 2019 Yunion
- //
- // 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 samlutils
- import (
- "encoding/xml"
- "time"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/timeutils"
- )
- type SSAMLResponseAttribute struct {
- Name string
- NameFormat string
- FriendlyName string
- Values []string
- }
- type SSAMLSpInitiatedLoginData struct {
- NameId string
- NameIdFormat string
- AudienceRestriction string
- Attributes []SSAMLResponseAttribute
- Form string
- }
- type SSAMLIdpInitiatedLoginData struct {
- SSAMLSpInitiatedLoginData
- RelayState string
- }
- type SSAMLResponseInput struct {
- IssuerEntityId string
- RequestEntityId string
- RequestID string
- AssertionConsumerServiceURL string
- IssuerCertString string
- SSAMLSpInitiatedLoginData
- }
- func NewResponse(input SSAMLResponseInput) Response {
- // since := timeutils.IsoTime(time.Now().UTC().Add(-time.Minute * 60 * 24))
- until := timeutils.IsoTime(time.Now().UTC().Add(time.Minute * 5))
- respId := GenerateSAMLId()
- assertId := GenerateSAMLId()
- now := timeutils.IsoTime(time.Now().UTC())
- issuerFormat := NAME_ID_FORMAT_ENTITY
- issuer := Issuer{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "Issuer",
- },
- Format: &issuerFormat,
- Issuer: input.IssuerEntityId,
- }
- var responseTo *string
- if len(input.RequestID) > 0 {
- responseTo = &input.RequestID
- }
- resp := Response{
- XMLName: xml.Name{
- Space: XMLNS_PROTO,
- Local: "Response",
- },
- ID: respId,
- InResponseTo: responseTo,
- Version: SAML2_VERSION,
- IssueInstant: now,
- Destination: input.AssertionConsumerServiceURL,
- Issuer: issuer,
- Status: Status{
- XMLName: xml.Name{
- Space: XMLNS_PROTO,
- Local: "Status",
- },
- StatusCode: StatusCode{
- XMLName: xml.Name{
- Space: XMLNS_PROTO,
- Local: "StatusCode",
- },
- Value: STATUS_SUCCESS,
- },
- StatusMessage: &StatusMessage{
- XMLName: xml.Name{
- Space: XMLNS_PROTO,
- Local: "StatusMessage",
- },
- Message: STATUS_SUCCESS,
- },
- },
- Assertion: &Assertion{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "Assertion",
- },
- ID: assertId,
- Version: SAML2_VERSION,
- IssueInstant: now,
- Issuer: issuer,
- Signature: &Signature{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "Signature",
- },
- SignedInfo: SignedInfo{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "SignedInfo",
- },
- CanonicalizationMethod: EncryptionMethod{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "CanonicalizationMethod",
- },
- Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#",
- },
- SignatureMethod: EncryptionMethod{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "SignatureMethod",
- },
- Algorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
- },
- Reference: Reference{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "Reference",
- },
- URI: "#" + assertId,
- Transforms: Transforms{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "Transforms",
- },
- Transforms: []EncryptionMethod{
- {
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "Transform",
- },
- Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
- },
- {
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "Transform",
- },
- Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#",
- },
- },
- },
- DigestMethod: EncryptionMethod{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "DigestMethod",
- },
- Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
- },
- DigestValue: SSAMLValue{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "DigestValue",
- },
- Value: "",
- },
- },
- },
- SignatureValue: SSAMLValue{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "SignatureValue",
- },
- Value: "",
- },
- KeyInfo: KeyInfo{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "KeyInfo",
- },
- X509Data: &X509Data{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "X509Data",
- },
- X509Certificate: X509Certificate{
- XMLName: xml.Name{
- Space: XMLNS_DS,
- Local: "X509Certificate",
- },
- Cert: input.IssuerCertString,
- },
- },
- },
- },
- Subject: Subject{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "Subject",
- },
- NameID: NameID{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "NameID",
- },
- Format: input.NameIdFormat,
- NameQualifier: &input.RequestEntityId,
- Value: input.NameId,
- },
- SubjectConfirmation: SubjectConfirmation{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "SubjectConfirmation",
- },
- Method: "urn:oasis:names:tc:SAML:2.0:cm:bearer",
- SubjectConfirmationData: SubjectConfirmationData{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "SubjectConfirmationData",
- },
- InResponseTo: responseTo,
- Recipient: input.AssertionConsumerServiceURL,
- NotOnOrAfter: until,
- },
- },
- },
- Conditions: Conditions{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "Conditions",
- },
- NotBefore: &now,
- NotOnOrAfter: until,
- AudienceRestrictions: []AudienceRestriction{},
- },
- AttributeStatement: &AttributeStatement{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "AttributeStatement",
- },
- Attributes: []Attribute{},
- },
- AuthnStatement: AuthnStatement{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "AuthnStatement",
- },
- AuthnInstant: now,
- SessionIndex: assertId,
- SubjectLocality: &SubjectLocality{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "SubjectLocality",
- },
- Address: input.RequestEntityId,
- },
- AuthnContext: AuthnContext{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "AuthnContext",
- },
- AuthnContextClassRef: AuthnContextClassRef{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "AuthnContextClassRef",
- },
- // Value: "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified",
- Value: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
- },
- },
- },
- },
- }
- if len(input.AudienceRestriction) > 0 {
- resp.AddAudienceRestriction(input.AudienceRestriction)
- }
- for _, attr := range input.Attributes {
- resp.AddAttribute(attr.Name, attr.FriendlyName, attr.NameFormat, attr.Values)
- }
- return resp
- }
- // AddAttribute add strong attribute to the Response
- func (r *Response) AddAttribute(name string, friendlyName string, nameFormat string, values []string) {
- attr := Attribute{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "Attribute",
- },
- Name: name,
- AttributeValues: []AttributeValue{},
- }
- if len(friendlyName) > 0 {
- attr.FriendlyName = &friendlyName
- }
- if len(nameFormat) > 0 {
- attr.NameFormat = &nameFormat
- }
- for _, value := range values {
- attrValue := AttributeValue{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "AttributeValue",
- },
- Type: "xs:string",
- Value: value,
- }
- attr.AttributeValues = append(attr.AttributeValues, attrValue)
- }
- r.Assertion.AttributeStatement.Attributes = append(r.Assertion.AttributeStatement.Attributes, attr)
- }
- func (r *Response) AddAudienceRestriction(value string) {
- restrict := AudienceRestriction{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "AudienceRestriction",
- },
- Audience: Audience{
- XMLName: xml.Name{
- Space: XMLNS_ASSERT,
- Local: "Audience",
- },
- Value: value,
- },
- }
- r.Assertion.Conditions.AudienceRestrictions = append(r.Assertion.Conditions.AudienceRestrictions, restrict)
- }
- func (saml *SSAMLInstance) UnmarshalResponse(xmlText []byte) (*Response, error) {
- resp := Response{}
- err := xml.Unmarshal(xmlText, &resp)
- if err != nil {
- return nil, errors.Wrap(err, "xml.Unmarshal response")
- }
- if resp.EncryptedAssertion != nil {
- asserText, err := resp.EncryptedAssertion.EncryptedData.decryptData(saml.privateKey)
- if err != nil {
- return nil, errors.Wrap(err, "EncryptedAssertion.EncryptedData.decryptData")
- }
- assertion := Assertion{}
- err = xml.Unmarshal(asserText, &assertion)
- if err != nil {
- return nil, errors.Wrap(err, "xml.Unmarshal assertion")
- }
- resp.Assertion = &assertion
- }
- return &resp, nil
- }
- func (samlResp Response) FetchAttribtues() map[string][]string {
- ret := make(map[string][]string)
- if samlResp.Assertion != nil && samlResp.Assertion.AttributeStatement != nil {
- for _, attr := range samlResp.Assertion.AttributeStatement.Attributes {
- values := make([]string, len(attr.AttributeValues))
- for i := range values {
- values[i] = attr.AttributeValues[i].Value
- }
- ret[attr.Name] = values
- }
- }
- return ret
- }
- func (samlResp Response) IsSuccess() bool {
- return samlResp.Status.StatusCode.Value == STATUS_SUCCESS
- }
|