saml2 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +13 -0
  3. data/app/views/saml2/http_post.html.erb +14 -0
  4. data/lib/saml2.rb +9 -0
  5. data/lib/saml2/assertion.rb +37 -0
  6. data/lib/saml2/attribute.rb +127 -0
  7. data/lib/saml2/attribute/x500.rb +79 -0
  8. data/lib/saml2/attribute_consuming_service.rb +76 -0
  9. data/lib/saml2/authn_request.rb +116 -0
  10. data/lib/saml2/authn_statement.rb +26 -0
  11. data/lib/saml2/base.rb +53 -0
  12. data/lib/saml2/contact.rb +50 -0
  13. data/lib/saml2/endpoint.rb +46 -0
  14. data/lib/saml2/engine.rb +4 -0
  15. data/lib/saml2/entity.rb +84 -0
  16. data/lib/saml2/identity_provider.rb +57 -0
  17. data/lib/saml2/indexed_object.rb +59 -0
  18. data/lib/saml2/key.rb +46 -0
  19. data/lib/saml2/name_id.rb +60 -0
  20. data/lib/saml2/namespaces.rb +21 -0
  21. data/lib/saml2/organization.rb +74 -0
  22. data/lib/saml2/organization_and_contacts.rb +35 -0
  23. data/lib/saml2/profiles.rb +7 -0
  24. data/lib/saml2/response.rb +92 -0
  25. data/lib/saml2/role.rb +53 -0
  26. data/lib/saml2/schemas.rb +18 -0
  27. data/lib/saml2/service_provider.rb +30 -0
  28. data/lib/saml2/sso.rb +36 -0
  29. data/lib/saml2/subject.rb +49 -0
  30. data/lib/saml2/version.rb +3 -0
  31. data/schemas/saml-schema-assertion-2.0.xsd +283 -0
  32. data/schemas/saml-schema-metadata-2.0.xsd +339 -0
  33. data/schemas/saml-schema-protocol-2.0.xsd +302 -0
  34. data/schemas/xenc-schema.xsd +136 -0
  35. data/schemas/xml.xsd +287 -0
  36. data/schemas/xmldsig-core-schema.xsd +309 -0
  37. data/spec/fixtures/authnrequest.xml +12 -0
  38. data/spec/fixtures/calculated.txt +1 -0
  39. data/spec/fixtures/certificate.pem +25 -0
  40. data/spec/fixtures/entities.xml +13 -0
  41. data/spec/fixtures/privatekey.key +27 -0
  42. data/spec/fixtures/response_signed.xml +47 -0
  43. data/spec/fixtures/response_with_attribute_signed.xml +47 -0
  44. data/spec/fixtures/service_provider.xml +79 -0
  45. data/spec/fixtures/xmlsec.txt +1 -0
  46. data/spec/lib/attribute_consuming_service_spec.rb +74 -0
  47. data/spec/lib/attribute_spec.rb +39 -0
  48. data/spec/lib/authn_request_spec.rb +52 -0
  49. data/spec/lib/entity_spec.rb +45 -0
  50. data/spec/lib/identity_provider_spec.rb +23 -0
  51. data/spec/lib/indexed_object_spec.rb +38 -0
  52. data/spec/lib/response_spec.rb +60 -0
  53. data/spec/lib/service_provider_spec.rb +30 -0
  54. data/spec/spec_helper.rb +6 -0
  55. metadata +191 -0
@@ -0,0 +1,12 @@
1
+ <samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_bec424fa5103428909a30ff1e31168327f79474984" Version="2.0" IssueInstant="2007-12-10T11:39:34Z" ForceAuthn="false" IsPassive="false" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="https://siteadmin.test.instructure.com/saml_consume">
2
+ <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
3
+ http://siteadmin.instructure.com/saml2
4
+ </saml:Issuer>
5
+ <samlp:NameIDPolicy xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" SPNameQualifier="moodle.bridge.feide.no" AllowCreate="true" />
6
+ <samlp:RequestedAuthnContext xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" Comparison="exact">
7
+ <saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
8
+ urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
9
+ </saml:AuthnContextClassRef>
10
+ </samlp:RequestedAuthnContext>
11
+ </samlp:AuthnRequest>
12
+
@@ -0,0 +1 @@
1
+ <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_cdfc3faf-90ad-462f-880d-677483210684" IssueInstant="2015-02-12T22:51:29Z" Version="2.0"><saml:Issuer>issuer</saml:Issuer><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">jacob</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData InResponseTo="_bec424fa5103428909a30ff1e31168327f79474984" NotBefore="2015-02-12T22:51:29Z" NotOnOrAfter="2015-02-12T22:54:29Z" Recipient="https://siteadmin.test.instructure.com/saml_consume"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:AuthnStatement AuthnInstant="2015-02-12T22:51:29Z"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500" FriendlyName="givenName" Name="urn:oid:2.5.4.42" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" x500:Encoding="LDAP"><saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">cody</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion>
@@ -0,0 +1,25 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIID+jCCAuKgAwIBAgIJAIz/He5UafnhMA0GCSqGSIb3DQEBBQUAMFsxCzAJBgNV
3
+ BAYTAlVTMQ0wCwYDVQQIEwRVdGFoMRcwFQYDVQQKEw5Db2R5IFNBTUwgVGVzdDEk
4
+ MCIGA1UEAxMbaHR0cDovL3Nzby5jYW52YXMuZGV2L1NBTUwyMB4XDTE1MDIwOTIy
5
+ MTkxOVoXDTE1MDMxMTIyMTkxOVowWzELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0
6
+ YWgxFzAVBgNVBAoTDkNvZHkgU0FNTCBUZXN0MSQwIgYDVQQDExtodHRwOi8vc3Nv
7
+ LmNhbnZhcy5kZXYvU0FNTDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
8
+ AQCwlbZhVkDi6YAHFpkxSoInc9Jrmezv0XKi8YzrDzO9Y7zHJlYygUQmgvD4I5fQ
9
+ tVW8sbp7gS5cCBmjbGJRXx3996qLq12//WLYMDkHktrbU1zZ6vsJ8ajHyQv4OFvq
10
+ qBnForSkuJbNi/QVTKiwbbBOZ75CbNBs1InoMN5MY2S5NhG9JhLjpktxNKfXFEi5
11
+ Wr0rc2T0lSbTHv7L6DUFKeKX7uK9bNREozZDwkyYUHZl1Vez88WiJw7CmzNEnm5v
12
+ 13M6e60788M7E5FZBkTDkFK5+RV10ycYYNN7E8l0HzWSaB4+aJhKsK/Q7Yv+MG/j
13
+ Oj8KMeZvEJfhxx1Dz8idgy8RAgMBAAGjgcAwgb0wHQYDVR0OBBYEFOvkP77RRJET
14
+ X//KwdohNVfZBSvbMIGNBgNVHSMEgYUwgYKAFOvkP77RRJETX//KwdohNVfZBSvb
15
+ oV+kXTBbMQswCQYDVQQGEwJVUzENMAsGA1UECBMEVXRhaDEXMBUGA1UEChMOQ29k
16
+ eSBTQU1MIFRlc3QxJDAiBgNVBAMTG2h0dHA6Ly9zc28uY2FudmFzLmRldi9TQU1M
17
+ MoIJAIz/He5UafnhMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAQ6
18
+ iucYVoOHBXHGybLUj8i3yZEI8C0mZQ/NBsihMGBP58vNSSKUJr4JPvYUIudwkLVH
19
+ T1FWdfMVVVUxqJvCFWfWcpCyKTe4FQ0WyTasq1F9LtCaeMczlkpK+E2XBlNyPGoo
20
+ 1fCDO6pXD7EIOprIFl3blspb5ROF8lCESjFKmyxVGHEOMs2GA0cX3xvW+AvCbYUC
21
+ Cg8Yo62X9vWW6PaKXHs3N+g1Ig16NwjdVIYvcxLc2KY0vrqu/R5c8RbmCxMZyss9
22
+ 5y5OLZblsTw3CPgxgMcCiBSYXnO0VTpT9ANW/SpeSE8XnfumxUjsUtxO4qN4O2es
23
+ ZtltN+yN40INHGRWnHc=
24
+ -----END CERTIFICATE-----
25
+ `
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0"?>
2
+ <EntitiesDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
3
+ <EntityDescriptor entityID="urn:entity1">
4
+ <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
5
+ <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://entity1.com/SAML2/Login" index="0"/>
6
+ </SPSSODescriptor>
7
+ </EntityDescriptor>
8
+ <EntityDescriptor entityID="urn:entity2">
9
+ <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
10
+ <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://entity2.com/SAML2/Login" index="0"/>
11
+ </SPSSODescriptor>
12
+ </EntityDescriptor>
13
+ </EntitiesDescriptor>
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEpAIBAAKCAQEAsJW2YVZA4umABxaZMUqCJ3PSa5ns79FyovGM6w8zvWO8xyZW
3
+ MoFEJoLw+COX0LVVvLG6e4EuXAgZo2xiUV8d/feqi6tdv/1i2DA5B5La21Nc2er7
4
+ CfGox8kL+Dhb6qgZxaK0pLiWzYv0FUyosG2wTme+QmzQbNSJ6DDeTGNkuTYRvSYS
5
+ 46ZLcTSn1xRIuVq9K3Nk9JUm0x7+y+g1BSnil+7ivWzURKM2Q8JMmFB2ZdVXs/PF
6
+ oicOwpszRJ5ub9dzOnutO/PDOxORWQZEw5BSufkVddMnGGDTexPJdB81kmgePmiY
7
+ SrCv0O2L/jBv4zo/CjHmbxCX4ccdQ8/InYMvEQIDAQABAoIBACp0xHXgtBcahwKt
8
+ R0XXoTV1Hnlqd9ItLH/KzdPSQuFdMo07RWw9MjKENwWiISU4BBYrMSfypj/QXsGx
9
+ FG52DRL70hBkslSvym0qIvwULfSftWpbmeIJLUhjqTIT8t2XbyLafM5B51giNxxL
10
+ 4x8QMFyZiuATo4UXENU3sqrxFs2EsRtPPp2MLXz/bCTjpm4nozwCN8hl7dxBVc6m
11
+ pJS2QmUyiYSxwuozVbl1mO2DeNrVZ235XUBNEwhxSHTHFRcfuHDOc/l+7e+oKK+r
12
+ cNhSS3BFKC66b3ekc6Ffw76Ixi/6ABa+uBBxdHMpeFfheJXLOCfcZdL1EiXK5kvg
13
+ mHf4kUECgYEA4CKC3wHKiaQ1MTMlYFPD30YZy7ANr81czAPADguiH9RN+LEQNh9U
14
+ c2wfnvz8u7nGU7n6AaFNDHd9nYVt56Bn1hpqTJY+UPZBJHbNpYkhTVEb9lfwXPia
15
+ FURy0wUnr5qCbDy5kEEi7W4CXLaV4WC0r+AioJ6VQAPAdipV1I8WMEkCgYEAybCY
16
+ 75m+Etpaq52ue/geHVhen1FWFjzU9lxFaKu6LDbiVAnOdw6CaSW6dQR8Wzet0r4g
17
+ QCJIOLIEb8b2hyHC5tQEweKJxKFcpacQkL1x9zePux6wLYEUVUMh4O8lMoucEGNY
18
+ lea6gS2dSiE/iOqqopgXAuNlDQ2g0RGIkd44mIkCgYEAkSBngwSC43rK4m+OnP/A
19
+ DVszqrr1MccUdhlbivynXlq2nffwWksRAKebFfQTpW4V5/K82b9ax167nHpf/qHT
20
+ ekOiXrLN6Nh6t6ShZbUUNh71rx2jyl9CTdEDVHW7C65NEs6DDM/QUNJxfxzOkZ9v
21
+ f43uffgRBeEoBptE9hwsLtkCgYBTCXgNvXh5/pgx69t4grCzWDyszynoQedT/q08
22
+ 6ObfaUFJZDgy4DBk5fmcT9p7G7Ne/pP8k6C0ZuZYtsus2wOJUWUcBg6+e8jPErdJ
23
+ QBX5uFBes8XJFkmuyNLb7tmbs8rvHFfOb439vS/y4zlrP2I9Suy+baye8StyqAtY
24
+ MuuOMQKBgQCZsjjCBOKVqK9k0NxRZAsx802X0Ux5aHHvtHs39Z1CoXSFB58x5jce
25
+ NQRs3vQhjmcoz3ABhnJtFLCVlhvo8M+NiwVPXWOqt2cQUTAOigUBiPV53Nx+azG5
26
+ t6wYGJtOFhhdM4pfmqcBzRYggYPLQnN6Evqqmvv0SdXPeiSlMsHZnA==
27
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,47 @@
1
+ <?xml version="1.0"?>
2
+ <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_9a15e699-2d04-4ba7-a521-cfa4dcd21f44" Version="2.0" IssueInstant="2015-02-12T22:51:29Z" Destination="https://siteadmin.test.instructure.com/saml_consume" InResponseTo="_bec424fa5103428909a30ff1e31168327f79474984"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">issuer</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status><saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_cdfc3faf-90ad-462f-880d-677483210684" Version="2.0" IssueInstant="2015-02-12T22:51:29Z"><saml:Issuer>issuer</saml:Issuer><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">jacob</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotBefore="2015-02-12T22:51:29Z" NotOnOrAfter="2015-02-12T22:54:29Z" Recipient="https://siteadmin.test.instructure.com/saml_consume" InResponseTo="_bec424fa5103428909a30ff1e31168327f79474984"/></saml:SubjectConfirmation></saml:Subject><saml:AuthnStatement AuthnInstant="2015-02-12T22:51:29Z"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
3
+ <SignedInfo>
4
+ <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5
+ <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6
+ <Reference URI="#_cdfc3faf-90ad-462f-880d-677483210684">
7
+ <Transforms>
8
+ <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
9
+ <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
10
+ </Transforms>
11
+ <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
12
+ <DigestValue>tcGklXG37kbAGysq5Z1xJFzWrqZy17G4EmjsUkM/aR8=</DigestValue>
13
+ </Reference>
14
+ </SignedInfo>
15
+ <SignatureValue>GCgO76VB/ZVbt9S5gxCr1d1UVjrwRc4ylJYw4qkQ30PCkfI6wTGNxzoKajJgJcdC
16
+ xuawi10veuIfubziijAFd1CHX33egXpqW+Q1+ddgQE4JSukgZg5TE8up/HxXwQ7r
17
+ nVsLFXFB2Q3maoFTA2zt/jjKThYNEq/KXY+fQHB+pbYwkXQXK0MwL2sdX2zxZSCM
18
+ DpTQDSCIMpaEPV8my6NnZI8qTHZGu8JvBJZUfQVi5ZGElIFQowrN9dRfe6Lbv4tM
19
+ 0jopAodYSarvQvBjjVQWe4ffqOOoSg5jGlma44WLwO05RQfo0VpLjNP06P9PePNc
20
+ 5sUzApqsEgsVa+WxGztlBA==</SignatureValue>
21
+ <KeyInfo>
22
+ <X509Data>
23
+ <X509Certificate>MIID+jCCAuKgAwIBAgIJAIz/He5UafnhMA0GCSqGSIb3DQEBBQUAMFsxCzAJBgNV
24
+ BAYTAlVTMQ0wCwYDVQQIEwRVdGFoMRcwFQYDVQQKEw5Db2R5IFNBTUwgVGVzdDEk
25
+ MCIGA1UEAxMbaHR0cDovL3Nzby5jYW52YXMuZGV2L1NBTUwyMB4XDTE1MDIwOTIy
26
+ MTkxOVoXDTE1MDMxMTIyMTkxOVowWzELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0
27
+ YWgxFzAVBgNVBAoTDkNvZHkgU0FNTCBUZXN0MSQwIgYDVQQDExtodHRwOi8vc3Nv
28
+ LmNhbnZhcy5kZXYvU0FNTDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
29
+ AQCwlbZhVkDi6YAHFpkxSoInc9Jrmezv0XKi8YzrDzO9Y7zHJlYygUQmgvD4I5fQ
30
+ tVW8sbp7gS5cCBmjbGJRXx3996qLq12//WLYMDkHktrbU1zZ6vsJ8ajHyQv4OFvq
31
+ qBnForSkuJbNi/QVTKiwbbBOZ75CbNBs1InoMN5MY2S5NhG9JhLjpktxNKfXFEi5
32
+ Wr0rc2T0lSbTHv7L6DUFKeKX7uK9bNREozZDwkyYUHZl1Vez88WiJw7CmzNEnm5v
33
+ 13M6e60788M7E5FZBkTDkFK5+RV10ycYYNN7E8l0HzWSaB4+aJhKsK/Q7Yv+MG/j
34
+ Oj8KMeZvEJfhxx1Dz8idgy8RAgMBAAGjgcAwgb0wHQYDVR0OBBYEFOvkP77RRJET
35
+ X//KwdohNVfZBSvbMIGNBgNVHSMEgYUwgYKAFOvkP77RRJETX//KwdohNVfZBSvb
36
+ oV+kXTBbMQswCQYDVQQGEwJVUzENMAsGA1UECBMEVXRhaDEXMBUGA1UEChMOQ29k
37
+ eSBTQU1MIFRlc3QxJDAiBgNVBAMTG2h0dHA6Ly9zc28uY2FudmFzLmRldi9TQU1M
38
+ MoIJAIz/He5UafnhMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAQ6
39
+ iucYVoOHBXHGybLUj8i3yZEI8C0mZQ/NBsihMGBP58vNSSKUJr4JPvYUIudwkLVH
40
+ T1FWdfMVVVUxqJvCFWfWcpCyKTe4FQ0WyTasq1F9LtCaeMczlkpK+E2XBlNyPGoo
41
+ 1fCDO6pXD7EIOprIFl3blspb5ROF8lCESjFKmyxVGHEOMs2GA0cX3xvW+AvCbYUC
42
+ Cg8Yo62X9vWW6PaKXHs3N+g1Ig16NwjdVIYvcxLc2KY0vrqu/R5c8RbmCxMZyss9
43
+ 5y5OLZblsTw3CPgxgMcCiBSYXnO0VTpT9ANW/SpeSE8XnfumxUjsUtxO4qN4O2es
44
+ ZtltN+yN40INHGRWnHc=</X509Certificate>
45
+ </X509Data>
46
+ </KeyInfo>
47
+ </Signature></saml:Assertion></samlp:Response>
@@ -0,0 +1,47 @@
1
+ <?xml version="1.0"?>
2
+ <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="_9a15e699-2d04-4ba7-a521-cfa4dcd21f44" Version="2.0" IssueInstant="2015-02-12T22:51:29Z" Destination="https://siteadmin.test.instructure.com/saml_consume" InResponseTo="_bec424fa5103428909a30ff1e31168327f79474984"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">issuer</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status><saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_cdfc3faf-90ad-462f-880d-677483210684" Version="2.0" IssueInstant="2015-02-12T22:51:29Z"><saml:Issuer>issuer</saml:Issuer><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">jacob</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotBefore="2015-02-12T22:51:29Z" NotOnOrAfter="2015-02-12T22:54:29Z" Recipient="https://siteadmin.test.instructure.com/saml_consume" InResponseTo="_bec424fa5103428909a30ff1e31168327f79474984"/></saml:SubjectConfirmation></saml:Subject><saml:AuthnStatement AuthnInstant="2015-02-12T22:51:29Z"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><saml:Attribute xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500" Name="urn:oid:2.5.4.42" FriendlyName="givenName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" x500:Encoding="LDAP"><saml:AttributeValue xsi:type="xsd:string">cody</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
3
+ <SignedInfo>
4
+ <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
5
+ <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
6
+ <Reference URI="#_cdfc3faf-90ad-462f-880d-677483210684">
7
+ <Transforms>
8
+ <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
9
+ <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
10
+ </Transforms>
11
+ <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
12
+ <DigestValue>G6PWZur5jAV2j+08LFvnZbQhU61B3QYOaKlncC0A2iE=</DigestValue>
13
+ </Reference>
14
+ </SignedInfo>
15
+ <SignatureValue>ld3IvZ/JzGFKrxV/YVxtNJD3j7mISXYjtum6OjLuzpXUBmqdzIlQ87YQkLmm4h0M
16
+ Nay3eEQGHho66x/ZoZljQpj1hUgj0od4v4pYubj9JFubr8WnQfIX67w3B/CZPlBO
17
+ 5Giai7KdvQtZzVzkQpWxzUOsHUJPWjt3b2S82bdwlRDn4VfCtFzhkd8+R3Mh0Rwm
18
+ Yd7GUqKdxWvvvG0r5l1zblxnwzE4f9j6Q2Z01gdabYYiGBjLOKTkEJU6CnxM0O9E
19
+ uOk0LvUnDhxMywIxwZYupOWNM2xUth0B9sBYUw8zala9II7vVoPNFb/Gk7+H18YB
20
+ 7TNJu+kWiAqpPKbJQY4KjQ==</SignatureValue>
21
+ <KeyInfo>
22
+ <X509Data>
23
+ <X509Certificate>MIID+jCCAuKgAwIBAgIJAIz/He5UafnhMA0GCSqGSIb3DQEBBQUAMFsxCzAJBgNV
24
+ BAYTAlVTMQ0wCwYDVQQIEwRVdGFoMRcwFQYDVQQKEw5Db2R5IFNBTUwgVGVzdDEk
25
+ MCIGA1UEAxMbaHR0cDovL3Nzby5jYW52YXMuZGV2L1NBTUwyMB4XDTE1MDIwOTIy
26
+ MTkxOVoXDTE1MDMxMTIyMTkxOVowWzELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0
27
+ YWgxFzAVBgNVBAoTDkNvZHkgU0FNTCBUZXN0MSQwIgYDVQQDExtodHRwOi8vc3Nv
28
+ LmNhbnZhcy5kZXYvU0FNTDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
29
+ AQCwlbZhVkDi6YAHFpkxSoInc9Jrmezv0XKi8YzrDzO9Y7zHJlYygUQmgvD4I5fQ
30
+ tVW8sbp7gS5cCBmjbGJRXx3996qLq12//WLYMDkHktrbU1zZ6vsJ8ajHyQv4OFvq
31
+ qBnForSkuJbNi/QVTKiwbbBOZ75CbNBs1InoMN5MY2S5NhG9JhLjpktxNKfXFEi5
32
+ Wr0rc2T0lSbTHv7L6DUFKeKX7uK9bNREozZDwkyYUHZl1Vez88WiJw7CmzNEnm5v
33
+ 13M6e60788M7E5FZBkTDkFK5+RV10ycYYNN7E8l0HzWSaB4+aJhKsK/Q7Yv+MG/j
34
+ Oj8KMeZvEJfhxx1Dz8idgy8RAgMBAAGjgcAwgb0wHQYDVR0OBBYEFOvkP77RRJET
35
+ X//KwdohNVfZBSvbMIGNBgNVHSMEgYUwgYKAFOvkP77RRJETX//KwdohNVfZBSvb
36
+ oV+kXTBbMQswCQYDVQQGEwJVUzENMAsGA1UECBMEVXRhaDEXMBUGA1UEChMOQ29k
37
+ eSBTQU1MIFRlc3QxJDAiBgNVBAMTG2h0dHA6Ly9zc28uY2FudmFzLmRldi9TQU1M
38
+ MoIJAIz/He5UafnhMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAQ6
39
+ iucYVoOHBXHGybLUj8i3yZEI8C0mZQ/NBsihMGBP58vNSSKUJr4JPvYUIudwkLVH
40
+ T1FWdfMVVVUxqJvCFWfWcpCyKTe4FQ0WyTasq1F9LtCaeMczlkpK+E2XBlNyPGoo
41
+ 1fCDO6pXD7EIOprIFl3blspb5ROF8lCESjFKmyxVGHEOMs2GA0cX3xvW+AvCbYUC
42
+ Cg8Yo62X9vWW6PaKXHs3N+g1Ig16NwjdVIYvcxLc2KY0vrqu/R5c8RbmCxMZyss9
43
+ 5y5OLZblsTw3CPgxgMcCiBSYXnO0VTpT9ANW/SpeSE8XnfumxUjsUtxO4qN4O2es
44
+ ZtltN+yN40INHGRWnHc=</X509Certificate>
45
+ </X509Data>
46
+ </KeyInfo>
47
+ </Signature></saml:Assertion></samlp:Response>
@@ -0,0 +1,79 @@
1
+ <?xml version="1.0"?>
2
+ <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://siteadmin.instructure.com/saml2">
3
+ <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
4
+
5
+ <KeyDescriptor use="encryption">
6
+ <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
7
+ <X509Data>
8
+ <X509Certificate>
9
+ SSB1c2UgdGhpcyBmb3IgZW5jcnlwdGlvbg==
10
+ </X509Certificate>
11
+ </X509Data>
12
+ </KeyInfo>
13
+ <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc">
14
+ <KeySize xmlns="http://www.w3.org/2001/04/xmlenc#">128</KeySize>
15
+ </EncryptionMethod>
16
+ </KeyDescriptor>
17
+
18
+ <KeyDescriptor use="signing">
19
+ <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
20
+ <X509Data>
21
+ <X509Certificate>
22
+ MIIE8TCCA9mgAwIBAgIJAITusxON60cKMA0GCSqGSIb3DQEBBQUAMIGrMQswCQYD
23
+ VQQGEwJVUzENMAsGA1UECBMEVXRhaDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkx
24
+ GTAXBgNVBAoTEEluc3RydWN0dXJlLCBJbmMxEzARBgNVBAsTCk9wZXJhdGlvbnMx
25
+ IDAeBgNVBAMTF0NhbnZhcyBTQU1MIENlcnRpZmljYXRlMSIwIAYJKoZIhvcNAQkB
26
+ FhNvcHNAaW5zdHJ1Y3R1cmUuY29tMB4XDTEzMDQyMjE3NDQ0M1oXDTE1MDQyMjE3
27
+ NDQ0M1owgasxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRVdGFoMRcwFQYDVQQHEw5T
28
+ YWx0IExha2UgQ2l0eTEZMBcGA1UEChMQSW5zdHJ1Y3R1cmUsIEluYzETMBEGA1UE
29
+ CxMKT3BlcmF0aW9uczEgMB4GA1UEAxMXQ2FudmFzIFNBTUwgQ2VydGlmaWNhdGUx
30
+ IjAgBgkqhkiG9w0BCQEWE29wc0BpbnN0cnVjdHVyZS5jb20wggEiMA0GCSqGSIb3
31
+ DQEBAQUAA4IBDwAwggEKAoIBAQDHRYRp/slsoqD7iPFo+8UFjqd+LgSQ062x09CG
32
+ m5uW9smY/x2ig8hxfd05Dtk42wrA9frRh6QiEhtoy8qL/4g/LOmYq5USDdzLXsPF
33
+ /nqTVPkTOhGcuSpfJbxucRsMfGL6IvrGqLNxpyfroyV1dv9/fim+d6bs7js5k1i5
34
+ EkKksgVlnnpUpOx5pswWVcZICeIJwTMe1C0KHcpUMycZxMHueJ+Y7tWHtWW+R75T
35
+ QWdWjL+TevEL57B3cW19+9Sud2Y63DcwP6V0aDrwArxQwmp73uUb5ol6gSSvD+Ol
36
+ CIsf6S/5gqMdgqxJJsWqzBOTeDsVr8m2Dx3VX7Plho7pk06FAgMBAAGjggEUMIIB
37
+ EDAdBgNVHQ4EFgQUQy1zIfZP/NZKPYLGugNSjjBnTYgwgeAGA1UdIwSB2DCB1YAU
38
+ Qy1zIfZP/NZKPYLGugNSjjBnTYihgbGkga4wgasxCzAJBgNVBAYTAlVTMQ0wCwYD
39
+ VQQIEwRVdGFoMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEZMBcGA1UEChMQSW5z
40
+ dHJ1Y3R1cmUsIEluYzETMBEGA1UECxMKT3BlcmF0aW9uczEgMB4GA1UEAxMXQ2Fu
41
+ dmFzIFNBTUwgQ2VydGlmaWNhdGUxIjAgBgkqhkiG9w0BCQEWE29wc0BpbnN0cnVj
42
+ dHVyZS5jb22CCQCE7rMTjetHCjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA
43
+ A4IBAQC1dgkv3cT4KRMR42mIKgJRp4Jf7swUrtoAFOdOr1R6fjI/9bFNSVNgauiQ
44
+ flN6q8QA5B2sbDihiSqAylm9F34hpI3C3PvzSWzuIk+Z2FPHcA05CZtwrUWj1M0c
45
+ eBXxXragtR7ZYtIbEb0srzBfwoFYvWnLU7tM8t6wM6+1rxvOuQFVCCSXyptsGoBl
46
+ D9qyzAbyYDgJZYpbTjaA9bqhpkn/9CLN3JhNHLyBVr03fp3hQqNwZ2do9bFZBnW0
47
+ c5Dx9pbKTvC3TAUb2cwUD69yTYS1oq7//yIC2ha2ouzkV/VpB1fcF5YEj2pc6uaj
48
+ lOTDX4Eg7OBEkTzU8cX04b15bJfE
49
+ </X509Certificate>
50
+ </X509Data>
51
+ </KeyInfo>
52
+ </KeyDescriptor>
53
+
54
+ <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
55
+ Location="https://siteadmin.instructure.com/saml_logout"/>
56
+ <AssertionConsumerService index="0" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
57
+ Location="https://siteadmin.instructure.com/saml_consume"/>
58
+ <AssertionConsumerService index="1" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
59
+ Location="https://siteadmin.staging.instructure.com/saml_consume"/>
60
+ <AssertionConsumerService index="2" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
61
+ Location="https://siteadmin.beta.instructure.com/saml_consume"/>
62
+ <AssertionConsumerService index="3" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
63
+ Location="https://siteadmin.test.instructure.com/saml_consume"/>
64
+
65
+ <AttributeConsumingService index="0">
66
+ <ServiceName xml:lang="en">service</ServiceName>
67
+ <RequestedAttribute Name="urn:oid:2.5.4.42" FriendlyName="givenName" />
68
+ </AttributeConsumingService>
69
+ </SPSSODescriptor>
70
+ <Organization>
71
+ <OrganizationName xml:lang="en">Canvas</OrganizationDisplayName>
72
+ <OrganizationDisplayName xml:lang="en">Canvas</OrganizationDisplayName>
73
+ <OrganizationURL xml:lang="en">https://www.canvaslms.com/</OrganizationDisplayName>
74
+ </Organization>
75
+ <ContactPerson contactType="technical">
76
+ <SurName>Administrator</SurName>
77
+ <EmailAddress>mailto:info@instructure.com</EmailAddress>
78
+ </ContactPerson>
79
+ </EntityDescriptor>
@@ -0,0 +1 @@
1
+ <saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_cdfc3faf-90ad-462f-880d-677483210684" IssueInstant="2015-02-12T22:51:29Z" Version="2.0"><saml:Issuer>issuer</saml:Issuer><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">jacob</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData InResponseTo="_bec424fa5103428909a30ff1e31168327f79474984" NotBefore="2015-02-12T22:51:29Z" NotOnOrAfter="2015-02-12T22:54:29Z" Recipient="https://siteadmin.test.instructure.com/saml_consume"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:AuthnStatement AuthnInstant="2015-02-12T22:51:29Z"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500" FriendlyName="givenName" Name="urn:oid:2.5.4.42" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" x500:Encoding="LDAP"><saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xsd:string">cody</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion>
@@ -0,0 +1,74 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module SAML2
4
+ describe AttributeConsumingService do
5
+ describe "#create_statement" do
6
+ let(:acs) do
7
+ requested_attributes = [
8
+ RequestedAttribute.new('name', true),
9
+ RequestedAttribute.new('age')
10
+ ]
11
+ AttributeConsumingService.new('my service', requested_attributes)
12
+ end
13
+
14
+ it "should require name attribute" do
15
+ -> { acs.create_statement({}) }.must_raise RequiredAttributeMissing
16
+ end
17
+
18
+ it "should create a statement" do
19
+ stmt = acs.create_statement('name' => 'cody')
20
+ stmt.attributes.length.must_equal 1
21
+ stmt.attributes.first.name.must_equal 'name'
22
+ stmt.attributes.first.value.must_equal 'cody'
23
+ end
24
+
25
+ it "should include optional attributes" do
26
+ stmt = acs.create_statement('name' => 'cody', 'age' => 29)
27
+ stmt.attributes.length.must_equal 2
28
+ stmt.attributes.first.name.must_equal 'name'
29
+ stmt.attributes.first.value.must_equal 'cody'
30
+ stmt.attributes.last.name.must_equal 'age'
31
+ stmt.attributes.last.value.must_equal 29
32
+ end
33
+
34
+ it "should ignore extra attributes" do
35
+ stmt = acs.create_statement('name' => 'cody', 'height' => 73)
36
+ stmt.attributes.length.must_equal 1
37
+ stmt.attributes.first.name.must_equal 'name'
38
+ stmt.attributes.first.value.must_equal 'cody'
39
+ end
40
+
41
+ it "should materialize deferred attributes" do
42
+ stmt = acs.create_statement('name' => -> { 'cody' })
43
+ stmt.attributes.length.must_equal 1
44
+ stmt.attributes.first.name.must_equal 'name'
45
+ stmt.attributes.first.value.must_equal 'cody'
46
+ end
47
+
48
+ it "should match explicit name formats" do
49
+ acs.requested_attributes.first.name_format = 'format'
50
+ stmt = acs.create_statement([Attribute.new('name', 'cody', nil, 'format'),
51
+ Attribute.new('name', 'unspecified'),
52
+ Attribute.new('name', 'other', nil, 'otherformat')])
53
+ stmt.attributes.length.must_equal 1
54
+ stmt.attributes.first.name.must_equal 'name'
55
+ stmt.attributes.first.value.must_equal 'cody'
56
+ end
57
+
58
+ it "should match explicitly requested name formats" do
59
+ acs.requested_attributes.first.name_format = 'format'
60
+ stmt = acs.create_statement('name' => 'cody')
61
+ stmt.attributes.length.must_equal 1
62
+ stmt.attributes.first.name.must_equal 'name'
63
+ stmt.attributes.first.value.must_equal 'cody'
64
+ end
65
+
66
+ it "should match explicitly provided name formats" do
67
+ stmt = acs.create_statement([Attribute.new('name', 'cody', 'format')])
68
+ stmt.attributes.length.must_equal 1
69
+ stmt.attributes.first.name.must_equal 'name'
70
+ stmt.attributes.first.value.must_equal 'cody'
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,39 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module SAML2
4
+ describe Attribute do
5
+ let(:eduPersonPrincipalNameXML) { <<XML.strip
6
+ <saml:Attribute xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" FriendlyName="eduPersonPrincipalName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" x500:Encoding="LDAP" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
7
+ <saml:AttributeValue xsi:type="xsd:string">user@domain</saml:AttributeValue>
8
+ </saml:Attribute>
9
+ XML
10
+ }
11
+
12
+ it "should auto-parse X500 attributes" do
13
+ attr = Attribute.from_xml(Nokogiri::XML(eduPersonPrincipalNameXML).root)
14
+ attr.must_be_instance_of Attribute::X500
15
+ attr.value.must_equal "user@domain"
16
+ attr.name.must_equal Attribute::X500::EduPerson::PRINCIPAL_NAME
17
+ attr.friendly_name.must_equal 'eduPersonPrincipalName'
18
+ attr.name_format.must_equal Attribute::NameFormats::URI
19
+ end
20
+
21
+ it "should serialize an X500 attribute correctly" do
22
+ attr = Attribute.create('eduPersonPrincipalName', 'user@domain')
23
+ attr.must_be_instance_of Attribute::X500
24
+ attr.value.must_equal "user@domain"
25
+ attr.name.must_equal Attribute::X500::EduPerson::PRINCIPAL_NAME
26
+ attr.friendly_name.must_equal 'eduPersonPrincipalName'
27
+ attr.name_format.must_equal Attribute::NameFormats::URI
28
+
29
+ doc = Nokogiri::XML::Builder.new do |builder|
30
+ builder['saml'].Root('xmlns:saml' => Namespaces::SAML) do |builder|
31
+ attr.build(builder)
32
+ builder.parent.child['xmlns:saml'] = Namespaces::SAML
33
+ end
34
+ end.doc
35
+ xml = doc.root.child.to_s
36
+ xml.must_equal eduPersonPrincipalNameXML
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,52 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module SAML2
4
+ describe AuthnRequest do
5
+ let(:sp) { Entity.parse(fixture('service_provider.xml')).roles.first }
6
+ let(:request) { AuthnRequest.parse(fixture('authnrequest.xml')) }
7
+
8
+ describe '.decode' do
9
+ it "should not choke on empty string" do
10
+ authnrequest = AuthnRequest.decode('')
11
+ authnrequest.valid_schema?.must_equal false
12
+ end
13
+
14
+ it "should not choke on garbage" do
15
+ authnrequest = AuthnRequest.decode('abc')
16
+ authnrequest.valid_schema?.must_equal false
17
+ end
18
+ end
19
+
20
+ it "should be valid" do
21
+ request.valid_schema?.must_equal true
22
+ request.resolve(sp).must_equal true
23
+ request.assertion_consumer_service.location.must_equal "https://siteadmin.test.instructure.com/saml_consume"
24
+ end
25
+
26
+ it "should not be valid if the ACS url is not in the SP" do
27
+ request.stub(:assertion_consumer_service_url, "garbage") do
28
+ request.resolve(sp).must_equal false
29
+ end
30
+ end
31
+
32
+ it "should use the default ACS if not specified" do
33
+ request.stub(:assertion_consumer_service_url, nil) do
34
+ request.resolve(sp).must_equal true
35
+ request.assertion_consumer_service.location.must_equal "https://siteadmin.instructure.com/saml_consume"
36
+ end
37
+ end
38
+
39
+ it "should find the ACS by index" do
40
+ request.stub(:assertion_consumer_service_url, nil) do
41
+ request.stub(:assertion_consumer_service_index, 2) do
42
+ request.resolve(sp).must_equal true
43
+ request.assertion_consumer_service.location.must_equal "https://siteadmin.beta.instructure.com/saml_consume"
44
+ end
45
+ end
46
+ end
47
+
48
+ it "should find the NameID policy" do
49
+ request.name_id_policy.must_equal NameID::Policy.new(true, NameID::Format::PERSISTENT)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,45 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module SAML2
4
+ describe Entity do
5
+ it "should parse and validate" do
6
+ entity = Entity.parse(fixture('service_provider.xml'))
7
+ entity.valid_schema?.must_equal true
8
+ end
9
+
10
+ it "should return nil when not valid schema" do
11
+ entity = Entity.parse("<xml></xml>")
12
+ entity.must_equal nil
13
+ end
14
+
15
+ it "should return nil on non-XML" do
16
+ entity = Entity.parse("garbage")
17
+ entity.must_equal nil
18
+ end
19
+
20
+ describe "valid schema" do
21
+ let(:entity) { Entity.parse(fixture('service_provider.xml')) }
22
+
23
+ it "should find the id" do
24
+ entity.entity_id.must_equal "http://siteadmin.instructure.com/saml2"
25
+ end
26
+
27
+ it "should parse the organization" do
28
+ entity.organization.display_name.must_equal 'Canvas'
29
+ entity.organization.display_name('en').must_equal 'Canvas'
30
+ entity.organization.display_name('es').must_equal nil
31
+ entity.organization.display_name(:all).must_equal en: 'Canvas'
32
+ end
33
+ end
34
+
35
+ describe Entity::Group do
36
+ it "should parse and validate" do
37
+ group = Entity.parse(fixture('entities.xml'))
38
+ group.must_be_instance_of Entity::Group
39
+ group.valid_schema?.must_equal true
40
+
41
+ group.map(&:entity_id).must_equal ['urn:entity1', 'urn:entity2']
42
+ end
43
+ end
44
+ end
45
+ end