response.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package samlutils
  15. import (
  16. "encoding/xml"
  17. "time"
  18. "yunion.io/x/pkg/errors"
  19. "yunion.io/x/pkg/util/timeutils"
  20. )
  21. type SSAMLResponseAttribute struct {
  22. Name string
  23. NameFormat string
  24. FriendlyName string
  25. Values []string
  26. }
  27. type SSAMLSpInitiatedLoginData struct {
  28. NameId string
  29. NameIdFormat string
  30. AudienceRestriction string
  31. Attributes []SSAMLResponseAttribute
  32. Form string
  33. }
  34. type SSAMLIdpInitiatedLoginData struct {
  35. SSAMLSpInitiatedLoginData
  36. RelayState string
  37. }
  38. type SSAMLResponseInput struct {
  39. IssuerEntityId string
  40. RequestEntityId string
  41. RequestID string
  42. AssertionConsumerServiceURL string
  43. IssuerCertString string
  44. SSAMLSpInitiatedLoginData
  45. }
  46. func NewResponse(input SSAMLResponseInput) Response {
  47. // since := timeutils.IsoTime(time.Now().UTC().Add(-time.Minute * 60 * 24))
  48. until := timeutils.IsoTime(time.Now().UTC().Add(time.Minute * 5))
  49. respId := GenerateSAMLId()
  50. assertId := GenerateSAMLId()
  51. now := timeutils.IsoTime(time.Now().UTC())
  52. issuerFormat := NAME_ID_FORMAT_ENTITY
  53. issuer := Issuer{
  54. XMLName: xml.Name{
  55. Space: XMLNS_ASSERT,
  56. Local: "Issuer",
  57. },
  58. Format: &issuerFormat,
  59. Issuer: input.IssuerEntityId,
  60. }
  61. var responseTo *string
  62. if len(input.RequestID) > 0 {
  63. responseTo = &input.RequestID
  64. }
  65. resp := Response{
  66. XMLName: xml.Name{
  67. Space: XMLNS_PROTO,
  68. Local: "Response",
  69. },
  70. ID: respId,
  71. InResponseTo: responseTo,
  72. Version: SAML2_VERSION,
  73. IssueInstant: now,
  74. Destination: input.AssertionConsumerServiceURL,
  75. Issuer: issuer,
  76. Status: Status{
  77. XMLName: xml.Name{
  78. Space: XMLNS_PROTO,
  79. Local: "Status",
  80. },
  81. StatusCode: StatusCode{
  82. XMLName: xml.Name{
  83. Space: XMLNS_PROTO,
  84. Local: "StatusCode",
  85. },
  86. Value: STATUS_SUCCESS,
  87. },
  88. StatusMessage: &StatusMessage{
  89. XMLName: xml.Name{
  90. Space: XMLNS_PROTO,
  91. Local: "StatusMessage",
  92. },
  93. Message: STATUS_SUCCESS,
  94. },
  95. },
  96. Assertion: &Assertion{
  97. XMLName: xml.Name{
  98. Space: XMLNS_ASSERT,
  99. Local: "Assertion",
  100. },
  101. ID: assertId,
  102. Version: SAML2_VERSION,
  103. IssueInstant: now,
  104. Issuer: issuer,
  105. Signature: &Signature{
  106. XMLName: xml.Name{
  107. Space: XMLNS_DS,
  108. Local: "Signature",
  109. },
  110. SignedInfo: SignedInfo{
  111. XMLName: xml.Name{
  112. Space: XMLNS_DS,
  113. Local: "SignedInfo",
  114. },
  115. CanonicalizationMethod: EncryptionMethod{
  116. XMLName: xml.Name{
  117. Space: XMLNS_DS,
  118. Local: "CanonicalizationMethod",
  119. },
  120. Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#",
  121. },
  122. SignatureMethod: EncryptionMethod{
  123. XMLName: xml.Name{
  124. Space: XMLNS_DS,
  125. Local: "SignatureMethod",
  126. },
  127. Algorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
  128. },
  129. Reference: Reference{
  130. XMLName: xml.Name{
  131. Space: XMLNS_DS,
  132. Local: "Reference",
  133. },
  134. URI: "#" + assertId,
  135. Transforms: Transforms{
  136. XMLName: xml.Name{
  137. Space: XMLNS_DS,
  138. Local: "Transforms",
  139. },
  140. Transforms: []EncryptionMethod{
  141. {
  142. XMLName: xml.Name{
  143. Space: XMLNS_DS,
  144. Local: "Transform",
  145. },
  146. Algorithm: "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
  147. },
  148. {
  149. XMLName: xml.Name{
  150. Space: XMLNS_DS,
  151. Local: "Transform",
  152. },
  153. Algorithm: "http://www.w3.org/2001/10/xml-exc-c14n#",
  154. },
  155. },
  156. },
  157. DigestMethod: EncryptionMethod{
  158. XMLName: xml.Name{
  159. Space: XMLNS_DS,
  160. Local: "DigestMethod",
  161. },
  162. Algorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
  163. },
  164. DigestValue: SSAMLValue{
  165. XMLName: xml.Name{
  166. Space: XMLNS_DS,
  167. Local: "DigestValue",
  168. },
  169. Value: "",
  170. },
  171. },
  172. },
  173. SignatureValue: SSAMLValue{
  174. XMLName: xml.Name{
  175. Space: XMLNS_DS,
  176. Local: "SignatureValue",
  177. },
  178. Value: "",
  179. },
  180. KeyInfo: KeyInfo{
  181. XMLName: xml.Name{
  182. Space: XMLNS_DS,
  183. Local: "KeyInfo",
  184. },
  185. X509Data: &X509Data{
  186. XMLName: xml.Name{
  187. Space: XMLNS_DS,
  188. Local: "X509Data",
  189. },
  190. X509Certificate: X509Certificate{
  191. XMLName: xml.Name{
  192. Space: XMLNS_DS,
  193. Local: "X509Certificate",
  194. },
  195. Cert: input.IssuerCertString,
  196. },
  197. },
  198. },
  199. },
  200. Subject: Subject{
  201. XMLName: xml.Name{
  202. Space: XMLNS_ASSERT,
  203. Local: "Subject",
  204. },
  205. NameID: NameID{
  206. XMLName: xml.Name{
  207. Space: XMLNS_ASSERT,
  208. Local: "NameID",
  209. },
  210. Format: input.NameIdFormat,
  211. NameQualifier: &input.RequestEntityId,
  212. Value: input.NameId,
  213. },
  214. SubjectConfirmation: SubjectConfirmation{
  215. XMLName: xml.Name{
  216. Space: XMLNS_ASSERT,
  217. Local: "SubjectConfirmation",
  218. },
  219. Method: "urn:oasis:names:tc:SAML:2.0:cm:bearer",
  220. SubjectConfirmationData: SubjectConfirmationData{
  221. XMLName: xml.Name{
  222. Space: XMLNS_ASSERT,
  223. Local: "SubjectConfirmationData",
  224. },
  225. InResponseTo: responseTo,
  226. Recipient: input.AssertionConsumerServiceURL,
  227. NotOnOrAfter: until,
  228. },
  229. },
  230. },
  231. Conditions: Conditions{
  232. XMLName: xml.Name{
  233. Space: XMLNS_ASSERT,
  234. Local: "Conditions",
  235. },
  236. NotBefore: &now,
  237. NotOnOrAfter: until,
  238. AudienceRestrictions: []AudienceRestriction{},
  239. },
  240. AttributeStatement: &AttributeStatement{
  241. XMLName: xml.Name{
  242. Space: XMLNS_ASSERT,
  243. Local: "AttributeStatement",
  244. },
  245. Attributes: []Attribute{},
  246. },
  247. AuthnStatement: AuthnStatement{
  248. XMLName: xml.Name{
  249. Space: XMLNS_ASSERT,
  250. Local: "AuthnStatement",
  251. },
  252. AuthnInstant: now,
  253. SessionIndex: assertId,
  254. SubjectLocality: &SubjectLocality{
  255. XMLName: xml.Name{
  256. Space: XMLNS_ASSERT,
  257. Local: "SubjectLocality",
  258. },
  259. Address: input.RequestEntityId,
  260. },
  261. AuthnContext: AuthnContext{
  262. XMLName: xml.Name{
  263. Space: XMLNS_ASSERT,
  264. Local: "AuthnContext",
  265. },
  266. AuthnContextClassRef: AuthnContextClassRef{
  267. XMLName: xml.Name{
  268. Space: XMLNS_ASSERT,
  269. Local: "AuthnContextClassRef",
  270. },
  271. // Value: "urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified",
  272. Value: "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
  273. },
  274. },
  275. },
  276. },
  277. }
  278. if len(input.AudienceRestriction) > 0 {
  279. resp.AddAudienceRestriction(input.AudienceRestriction)
  280. }
  281. for _, attr := range input.Attributes {
  282. resp.AddAttribute(attr.Name, attr.FriendlyName, attr.NameFormat, attr.Values)
  283. }
  284. return resp
  285. }
  286. // AddAttribute add strong attribute to the Response
  287. func (r *Response) AddAttribute(name string, friendlyName string, nameFormat string, values []string) {
  288. attr := Attribute{
  289. XMLName: xml.Name{
  290. Space: XMLNS_ASSERT,
  291. Local: "Attribute",
  292. },
  293. Name: name,
  294. AttributeValues: []AttributeValue{},
  295. }
  296. if len(friendlyName) > 0 {
  297. attr.FriendlyName = &friendlyName
  298. }
  299. if len(nameFormat) > 0 {
  300. attr.NameFormat = &nameFormat
  301. }
  302. for _, value := range values {
  303. attrValue := AttributeValue{
  304. XMLName: xml.Name{
  305. Space: XMLNS_ASSERT,
  306. Local: "AttributeValue",
  307. },
  308. Type: "xs:string",
  309. Value: value,
  310. }
  311. attr.AttributeValues = append(attr.AttributeValues, attrValue)
  312. }
  313. r.Assertion.AttributeStatement.Attributes = append(r.Assertion.AttributeStatement.Attributes, attr)
  314. }
  315. func (r *Response) AddAudienceRestriction(value string) {
  316. restrict := AudienceRestriction{
  317. XMLName: xml.Name{
  318. Space: XMLNS_ASSERT,
  319. Local: "AudienceRestriction",
  320. },
  321. Audience: Audience{
  322. XMLName: xml.Name{
  323. Space: XMLNS_ASSERT,
  324. Local: "Audience",
  325. },
  326. Value: value,
  327. },
  328. }
  329. r.Assertion.Conditions.AudienceRestrictions = append(r.Assertion.Conditions.AudienceRestrictions, restrict)
  330. }
  331. func (saml *SSAMLInstance) UnmarshalResponse(xmlText []byte) (*Response, error) {
  332. resp := Response{}
  333. err := xml.Unmarshal(xmlText, &resp)
  334. if err != nil {
  335. return nil, errors.Wrap(err, "xml.Unmarshal response")
  336. }
  337. if resp.EncryptedAssertion != nil {
  338. asserText, err := resp.EncryptedAssertion.EncryptedData.decryptData(saml.privateKey)
  339. if err != nil {
  340. return nil, errors.Wrap(err, "EncryptedAssertion.EncryptedData.decryptData")
  341. }
  342. assertion := Assertion{}
  343. err = xml.Unmarshal(asserText, &assertion)
  344. if err != nil {
  345. return nil, errors.Wrap(err, "xml.Unmarshal assertion")
  346. }
  347. resp.Assertion = &assertion
  348. }
  349. return &resp, nil
  350. }
  351. func (samlResp Response) FetchAttribtues() map[string][]string {
  352. ret := make(map[string][]string)
  353. if samlResp.Assertion != nil && samlResp.Assertion.AttributeStatement != nil {
  354. for _, attr := range samlResp.Assertion.AttributeStatement.Attributes {
  355. values := make([]string, len(attr.AttributeValues))
  356. for i := range values {
  357. values[i] = attr.AttributeValues[i].Value
  358. }
  359. ret[attr.Name] = values
  360. }
  361. }
  362. return ret
  363. }
  364. func (samlResp Response) IsSuccess() bool {
  365. return samlResp.Status.StatusCode.Value == STATUS_SUCCESS
  366. }