kl-ruby-saml 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.gitignore +14 -0
  4. data/.travis.yml +17 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE +19 -0
  7. data/README.md +575 -0
  8. data/Rakefile +41 -0
  9. data/changelog.md +75 -0
  10. data/gemfiles/nokogiri-1.5.gemfile +5 -0
  11. data/lib/onelogin/ruby-saml.rb +17 -0
  12. data/lib/onelogin/ruby-saml/attribute_service.rb +57 -0
  13. data/lib/onelogin/ruby-saml/attributes.rb +128 -0
  14. data/lib/onelogin/ruby-saml/authrequest.rb +156 -0
  15. data/lib/onelogin/ruby-saml/http_error.rb +7 -0
  16. data/lib/onelogin/ruby-saml/idp_metadata_parser.rb +161 -0
  17. data/lib/onelogin/ruby-saml/logging.rb +30 -0
  18. data/lib/onelogin/ruby-saml/logoutrequest.rb +131 -0
  19. data/lib/onelogin/ruby-saml/logoutresponse.rb +241 -0
  20. data/lib/onelogin/ruby-saml/metadata.rb +123 -0
  21. data/lib/onelogin/ruby-saml/response.rb +722 -0
  22. data/lib/onelogin/ruby-saml/saml_message.rb +158 -0
  23. data/lib/onelogin/ruby-saml/settings.rb +165 -0
  24. data/lib/onelogin/ruby-saml/slo_logoutrequest.rb +258 -0
  25. data/lib/onelogin/ruby-saml/slo_logoutresponse.rb +136 -0
  26. data/lib/onelogin/ruby-saml/utils.rb +172 -0
  27. data/lib/onelogin/ruby-saml/validation_error.rb +7 -0
  28. data/lib/onelogin/ruby-saml/version.rb +5 -0
  29. data/lib/ruby-saml.rb +1 -0
  30. data/lib/schemas/saml-schema-assertion-2.0.xsd +283 -0
  31. data/lib/schemas/saml-schema-authn-context-2.0.xsd +23 -0
  32. data/lib/schemas/saml-schema-authn-context-types-2.0.xsd +821 -0
  33. data/lib/schemas/saml-schema-metadata-2.0.xsd +337 -0
  34. data/lib/schemas/saml-schema-protocol-2.0.xsd +302 -0
  35. data/lib/schemas/sstc-metadata-attr.xsd +35 -0
  36. data/lib/schemas/sstc-saml-attribute-ext.xsd +25 -0
  37. data/lib/schemas/sstc-saml-metadata-algsupport-v1.0.xsd +41 -0
  38. data/lib/schemas/sstc-saml-metadata-ui-v1.0.xsd +89 -0
  39. data/lib/schemas/xenc-schema.xsd +136 -0
  40. data/lib/schemas/xml.xsd +287 -0
  41. data/lib/schemas/xmldsig-core-schema.xsd +309 -0
  42. data/lib/xml_security.rb +358 -0
  43. data/ruby-saml.gemspec +57 -0
  44. data/test/certificates/certificate1 +12 -0
  45. data/test/certificates/certificate_without_head_foot +1 -0
  46. data/test/certificates/formatted_certificate +14 -0
  47. data/test/certificates/formatted_private_key +12 -0
  48. data/test/certificates/formatted_rsa_private_key +12 -0
  49. data/test/certificates/invalid_certificate1 +1 -0
  50. data/test/certificates/invalid_certificate2 +1 -0
  51. data/test/certificates/invalid_certificate3 +12 -0
  52. data/test/certificates/invalid_private_key1 +1 -0
  53. data/test/certificates/invalid_private_key2 +1 -0
  54. data/test/certificates/invalid_private_key3 +10 -0
  55. data/test/certificates/invalid_rsa_private_key1 +1 -0
  56. data/test/certificates/invalid_rsa_private_key2 +1 -0
  57. data/test/certificates/invalid_rsa_private_key3 +10 -0
  58. data/test/certificates/ruby-saml.crt +14 -0
  59. data/test/certificates/ruby-saml.key +15 -0
  60. data/test/idp_metadata_parser_test.rb +95 -0
  61. data/test/logging_test.rb +62 -0
  62. data/test/logout_requests/invalid_slo_request.xml +6 -0
  63. data/test/logout_requests/slo_request.xml +4 -0
  64. data/test/logout_requests/slo_request.xml.base64 +1 -0
  65. data/test/logout_requests/slo_request_deflated.xml.base64 +1 -0
  66. data/test/logout_requests/slo_request_with_session_index.xml +5 -0
  67. data/test/logout_responses/logoutresponse_fixtures.rb +67 -0
  68. data/test/logoutrequest_test.rb +211 -0
  69. data/test/logoutresponse_test.rb +258 -0
  70. data/test/metadata_test.rb +203 -0
  71. data/test/request_test.rb +282 -0
  72. data/test/response_test.rb +1094 -0
  73. data/test/responses/adfs_response_sha1.xml +46 -0
  74. data/test/responses/adfs_response_sha256.xml +46 -0
  75. data/test/responses/adfs_response_sha384.xml +46 -0
  76. data/test/responses/adfs_response_sha512.xml +46 -0
  77. data/test/responses/adfs_response_xmlns.xml +45 -0
  78. data/test/responses/attackxee.xml +13 -0
  79. data/test/responses/idp_descriptor.xml +3 -0
  80. data/test/responses/invalids/invalid_audience.xml.base64 +1 -0
  81. data/test/responses/invalids/invalid_issuer_assertion.xml.base64 +1 -0
  82. data/test/responses/invalids/invalid_issuer_message.xml.base64 +1 -0
  83. data/test/responses/invalids/invalid_signature_position.xml.base64 +1 -0
  84. data/test/responses/invalids/invalid_subjectconfirmation_inresponse.xml.base64 +1 -0
  85. data/test/responses/invalids/invalid_subjectconfirmation_nb.xml.base64 +1 -0
  86. data/test/responses/invalids/invalid_subjectconfirmation_noa.xml.base64 +1 -0
  87. data/test/responses/invalids/invalid_subjectconfirmation_recipient.xml.base64 +1 -0
  88. data/test/responses/invalids/multiple_assertions.xml.base64 +2 -0
  89. data/test/responses/invalids/multiple_signed.xml.base64 +1 -0
  90. data/test/responses/invalids/no_id.xml.base64 +1 -0
  91. data/test/responses/invalids/no_saml2.xml.base64 +1 -0
  92. data/test/responses/invalids/no_signature.xml.base64 +1 -0
  93. data/test/responses/invalids/no_status.xml.base64 +1 -0
  94. data/test/responses/invalids/no_status_code.xml.base64 +1 -0
  95. data/test/responses/invalids/no_subjectconfirmation_data.xml.base64 +1 -0
  96. data/test/responses/invalids/no_subjectconfirmation_method.xml.base64 +1 -0
  97. data/test/responses/invalids/response_encrypted_attrs.xml.base64 +1 -0
  98. data/test/responses/invalids/response_invalid_signed_element.xml.base64 +1 -0
  99. data/test/responses/invalids/status_code_responder.xml.base64 +1 -0
  100. data/test/responses/invalids/status_code_responer_and_msg.xml.base64 +1 -0
  101. data/test/responses/no_signature_ns.xml +48 -0
  102. data/test/responses/open_saml_response.xml +56 -0
  103. data/test/responses/response_assertion_wrapped.xml.base64 +93 -0
  104. data/test/responses/response_encrypted_nameid.xml.base64 +1 -0
  105. data/test/responses/response_eval.xml +7 -0
  106. data/test/responses/response_no_cert_and_encrypted_attrs.xml +29 -0
  107. data/test/responses/response_unsigned_xml_base64 +1 -0
  108. data/test/responses/response_with_ampersands.xml +139 -0
  109. data/test/responses/response_with_ampersands.xml.base64 +93 -0
  110. data/test/responses/response_with_multiple_attribute_values.xml +67 -0
  111. data/test/responses/response_with_saml2_namespace.xml.base64 +102 -0
  112. data/test/responses/response_with_signed_assertion.xml.base64 +66 -0
  113. data/test/responses/response_with_signed_assertion_2.xml.base64 +1 -0
  114. data/test/responses/response_with_undefined_recipient.xml.base64 +1 -0
  115. data/test/responses/response_without_attributes.xml.base64 +79 -0
  116. data/test/responses/response_wrapped.xml.base64 +150 -0
  117. data/test/responses/signed_message_encrypted_signed_assertion.xml.base64 +1 -0
  118. data/test/responses/signed_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  119. data/test/responses/simple_saml_php.xml +71 -0
  120. data/test/responses/starfield_response.xml.base64 +1 -0
  121. data/test/responses/test_sign.xml +43 -0
  122. data/test/responses/unsigned_message_aes128_encrypted_signed_assertion.xml.base64 +1 -0
  123. data/test/responses/unsigned_message_aes192_encrypted_signed_assertion.xml.base64 +1 -0
  124. data/test/responses/unsigned_message_aes256_encrypted_signed_assertion.xml.base64 +1 -0
  125. data/test/responses/unsigned_message_des192_encrypted_signed_assertion.xml.base64 +1 -0
  126. data/test/responses/unsigned_message_encrypted_assertion_without_saml_namespace.xml.base64 +1 -0
  127. data/test/responses/unsigned_message_encrypted_signed_assertion.xml.base64 +1 -0
  128. data/test/responses/unsigned_message_encrypted_unsigned_assertion.xml.base64 +1 -0
  129. data/test/responses/valid_response.xml.base64 +1 -0
  130. data/test/saml_message_test.rb +56 -0
  131. data/test/settings_test.rb +218 -0
  132. data/test/slo_logoutrequest_test.rb +275 -0
  133. data/test/slo_logoutresponse_test.rb +185 -0
  134. data/test/test_helper.rb +252 -0
  135. data/test/utils_test.rb +145 -0
  136. data/test/xml_security_test.rb +329 -0
  137. metadata +415 -0
@@ -0,0 +1 @@
1
+ PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfZTU3YTRiZmMxMmMxMjZlNWQxZTE3MjY5MGJiMzNjYTVjZjRlNTQ1YTE3IiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNS0wMy0xOVQxNDowMTowOFoiIERlc3RpbmF0aW9uPSJodHRwOi8vcnVieXNhbWwuY29tOjMwMDAvc2FtbC9hY3MiIEluUmVzcG9uc2VUbz0iXzUwZjEzZGUwLWIwNmUtMDEzMi01YzNjLTAwOTBmNWRlZGQ3NyI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vaWRwLmV4YW1wbGUuY29tL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPjx4ZW5jOkVuY3J5cHRlZERhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIiB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTI4LWNiYyIvPjxkc2lnOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PHhlbmM6RW5jcnlwdGVkS2V5Pjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNyc2Etb2FlcC1tZ2YxcCIvPjx4ZW5jOkNpcGhlckRhdGE+PHhlbmM6Q2lwaGVyVmFsdWU+V0J6Nkt3bEtZRzBKcXM4cGwvK1pqeVRCT1lGaFlsRjdDdldqYXdITERqMUU0V0JLVnc1QmM1cVR3MVAvZnlSSU1DQXNpblA5TCtsZTRlR3ZRTEFxbTJXejlBUHZZaFJ5MlY2bFJLU04zY21VUm9QRGtOSFp5b0lYWmQ4TlZ0ei82SEk5akZvaWFYRmRENDhiUTVhNkFZV2lLaUdOZEdpT055WG9tc0dPUzNzPTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHNpZzpLZXlJbmZvPgogICA8eGVuYzpDaXBoZXJEYXRhPgogICAgICA8eGVuYzpDaXBoZXJWYWx1ZT4rb2R4dGhoWFM1YzA3RHRIeUFTeEtGUEhmc3M4TE1mWW5vckp1eVdLWHRuLzBKR2prT1dvQzFSK0VQR0o5ZEpZR2IrekZmSzd1akF0Si9BMkwyM3NEMGV3TVh5RHVkQ28zTkplRm5LYUNJaTBVbmdTTDZCMzQ5cUtXVDlHZ0I3b2MzOHFSYzhtSHVsaHpYdnQ0YmFUVjZmZXE4cnlIb0tiVXkxMEVsRXZXZ2t1RjBweWw1R1dLempJSnZzakZZOFJZNHhRRUNmRFkxcGRYQ2ZxR2RhQzd3dGFOdE1DbWFpdzVKM21FZnBXSnpNMlREY3dUUUJDYnJLRDJVNktEZzZNYkdjUzJWeGtoMEJkVWc5eWtaT2NocXVWYmJJd0w1dFdjR2dpWS9zbnBuU2dOWEx0ZjBSSURvdExlendtOG9kL2hmaGwrQVA3eklkVVVVWHNDYXBWUlV6Uk01Y1ZHN0NOaHBlQ25XcTMxem1hckxRblBtbWdqdTI3QlE3aTN2aWRud25hV2s2U2lmL29JUVFPMExWTXBBUXcyUWNpUU9SRk1lWml0TnUvTTljNkFwaXlvalV6a1Q4UjJlSitlcTcydmpQRHpybDJrYWdMVU1zMXJ0dFNmVVFLM0Zac0Z3bVYyKzJIM0FqaWF4bmFMYzVLUE9qMEJGakNyRzNSQy9TTmpEazZpU202L0h5aUh4Qmd2VHJyNEE1Y2tid0ZudEgxb1lKYlltY1JiQUI0cUVqd0IrK0FFZjlPVWVCVmNDVUM0cDluZEdMQzZNTkcrMzIzM1N0Mkc5ZEQ1dk54SUk2WU9MUGhwYlQ0Y1ZpYkZmRVRTdlRDYVNLVDN5THNOTEVlbUZxWUhoR0dNSFFjSFYvRFkvNmxhbFJSRkZVQkVZNWJnNWp5MzF3VGI4bTJMQncwQTN5OCswWHlaUFhCNEdZMlFIVWU5UkdUYVZHVkFoZXlFaTZ4RTI2bks1NU5ocnRnbDl4b0l2aFpMVmEvdjNCQ2ZFbnBZTCtYMVhQdzZwQnoxbEFZWjErN05abGtqcGw0L0FUL3AyMVNkZVVpaDB3OGZsMzNTVEpjWDYxdWNtcW9TS3FOL2tWUllZejY2SHFjTlpNb2dVYmx3bVNnQ3hGYU51THBydy84WlZjSlk4cHkrWkhSZ0JFNFJUclpoNld0TkwyaEg2bzdpUy9XSEgzYlRiYkEweXowMi9idGFZd1JZaGpLOUNTaDRPYjZ2MkZuSU1rMzkvSzV5VmJtZHExMFVFclo4SFllTGl0N2IvT3pmUVJ2Wnp5TnZHaWhoc3FsYlhPNUZSL3ROOG8xZFVuaVkxeURlcHZzS29LNmFUN3FGYjZYazd2RHQzNlR4MkY4NDFobHM3dVh5OVdWWUhKN2lxUnBkRU1taHBOOEFBWG5nZ3RVS1V4QTU2MTkxOGcyNjVtTXZERGgyckVUK2xBVjVIdk1WUXVYR2Zxd29ST0REaU1WRjJTZW83eGd2VWM3bHgzZCtkVU1SNFFKeHROd3hMVTJTWW1xL0thbnlXWTRYVGxZNmJzOE1pRExTdFlhYjFGN3hQK1J5S0JuSTBxdHc3UUJ3YytBNTZOUkxjOVNkaE1vYlRkYnlwMVpRdEpJL2F6dzBsaFB3Q09mZHhFekl3bHJtUVJZRld2dXZqVXpNcE51dkxPTlhvejdkbW9hQUpsZDYyZm0rZHcrUW1lYVRtZjg3K1luMHU1U0FBdW5keGZsOUNwemJLRU9JTFU5NWNRN3BKYkNTcGlkZWRoYm9YWithU1VKdUpub0ZPWHppcStpc3F3Z0M3UkR5bm1pQ0oyalppTDBxVzNJa0Y2Y1lKcHloOWMwMEthSGJTd3NkUTRrMm9Nbk91VHVvbVFHY28zR0NpSzJHMjUvRC8rU0FFTzJIODQ1UncyQ3d4RzdQRDFwRVU1Ylp4Y0VuLzlaVkR3Q1hxaEE5UW15UzZQaGZHWTRvUTYvSEJyVEZsSnN3OWRJbkdFMzhSZlZ2SkVWU0hGcklmVlZ4TVRzbUR5V0hFb0ROeTR0MU1QYkl5VGRuSldqWk1LN0ZZL0k4cndoQ2FHLzFNWG9wWEUza0Fmd1h5bkI0ZEtBUnA3T1lwcFBnS2F0NVV5Y0Via1pBWlpIMmhmMVIveFRBeExidzdKaGRaQytQU2ExWUVNUjYxczB1ZGZsN1BOVHdhUDF2Z0VSY3R3NmdDaTMxM0kxNDkzeUExS1FwMTNaekFQRUk0RkZnTVcvUmdSVEppWVhrZTkzaVNmeUxTTk9GRGNPaGxMaXpSWldqWWoxRm9RbE9vN2IxbGZsZ1pUT05mQzJzbWFtSDFZNGVUUlVaVjdxQXgzSWkxTHh3azFSL1phNm9VNHA0OGdHVUowTENCVE9WOU82Z0JSUzYxdFdSZld1aGhrUWFiTVphS3NxdEx2aW1PSW5NcFl6WGhsSEptN0Z1c0s4ajBsUGpqdjBuZUtDY2t5L0c2Zm5LTERaQzgyVER2UE5vQmVLZkRJWmNvUm9vRDhyejU4bDd3TWFkSzBUZzFqdnZKYmZLU0xrcmpidUpHeGp2L2hmTzd5OWFYNW43U1NhZ2orYkV3OVNIdlpSaWx1Qmtjb0Y1bVFOTGhIcjRtempnNlRRT0VkMmYybXhJNjJkL09mVE94Y2VFQU1Ua1VWWUpQRVUyQ01KWlJka2h5TXFiVGFJZGJVSkRzRmY1SlNBaTNuMVlWbGtWWDEwWkwvVE1TUTBUVjVWdnozOXprS29IcFBQY2ZHY1Y0Q0o1Ym1pYkh2RVdrTnN0ZXovV09QQ2RMLzFLNnhUeFB2NkNWWHNBMHJqQ2RJc0tGd0FsMGFTMmtLQkVKUmdhcXVLL2t5Vk5oR0ZWaHEwUG12RUFZZWwvbklpKzRiejZRQjlyNVI5Z3JNaUQ5dUdGMG5HbHlWRHV5ZHRLSlZYS3VEajVwVHpvSU4vM3FGQWFvY3lkWmJSMHN1RUN4Sloxc3pIUVJVTWNMcFYvdVFmVDRsMkpIajdIeVlqQUc2ZnBFbzdQKy9XelNCSjd1dkdxQXJOeGJHbzljV3JTQ1RpaWw5ampYd3N6eG8zQzlGc1RmUFU4a2FnSHJsalBhWEduMS92SFdYVTlTTVkwVjl4T1p6bUdXUlI2QmNKSW5qNXJaOFBmaHNGVVY3YmY3emFpcDdxZkJJWWRVMlRvekQ2OElnTHQxM1ZFM0ZDT1lHR21mM0hUVDMrSDhCSTBjbGtJMkt0TzZVdWQ2Q2ZVcjVmR0lGWlhLQysyalNyNlBpWG9kSkRBMU92WHM4VlZpOFltSm5QMTMxeHBMU2g5QWRVUGxPR2gxODJ5WENPNlN5Y0hYc1ZuN0IrOVUxU3Y0NXZlSWxhSWFSRVMwU0ZENVpxU215WkgxMWNHSEVwTUtZZWZwMnU1TFFxVVVsTEtReDNsN09WNTlYWG5yUmFnS0tRdGFrT29JSVdwcGFCeUJIdENCWmxGdk5MNTV0NlMvcEdwSUMzMzA1QWdubTRhdlJzMjZ1UlV2ZnhralNRZ3ZSeGltM2pkQTBzbUFVV2FEWk9KekE4STFjaG9CZnBQazF2WHBQWEhQc2hRcGgwdEVXeHpaS3dnTjZBQklZSGM3SXcvUXJMMUFtQ1c3VmpVZXpnQU1hSDNRaGNndE1QdExVbmZCRG1DZWF0ZEdUYjBBMm9iNlZFV3JyY0VnR2FOTlh5M3JrNXFGWXdaTjZieEwxVHVnTDdnRjh1NERLN2U3TTdtUzltU0pFbEI2S2NmZ2lhank4TWFlaVlRSFBWeGI1ZkZhNjhPcU96S0J2MkVKTTNmKy9WQ2xibHE4Q1Y1NUIrckFuQlVYM01Ray9aZEprU2tBWHpHL2hWQXpzb0s4RnNSQmJubWEyaFJYV0lIY0M0eUk2Z0xKV1ZYQzFxZnNXSkZzMmQvSGN0ZEVXQVNkS0d0NnFranVpMitHZFFLbnpjRUpBSkptUXdGVklObUNUc1JJTWxkajNMLy81RkpBL05CN0VpREFZeUJMcmhiblAwUkNLblBJWXVyMEpLR3pzSUwvSFBqRkNtRXA0L0w3eEE0ZFQ1bXRBbUVKeURoUzN5d0hoZ3QzQlV2bmJDa0lxdmhEZ2R2Uk9LZ2Z5RlJkNTlpTFlZQUx2TTlPUnpySzM1Zm9VeEpxMmhwSUlqLzRabyt2blhjaTQxVlZsdUpiM2dqZ1VuMFVSbDZhN1ZBQ2RkQ2UxR1A1ZmI0NE91YlJBbzdHYVpySmFkWnpWN3RQVlhnZ0xFVmZiaVovWGZVMjR6YzR0aFIrV3NtMEJTSmlYOUxFQ1doM1UzWjlidHZQQmx6b1hvSVhxZmhFQ1V6eGIxRUtOQ1dUUjJBNDVyNWJwRzJoVysvTmVNK2dqNUN0bGhubGQ5QXFHZGlXMmQwLzljMFdxNjJSM3RJVklScTZ1WFk0cVBjcUlWcFNaeWtPcHVnM3dkeUJBZFYyUXp0MUVTTVFaenVXWEJTSnBMM0NIQ2YvemNDRnZwWEo3RFM2QzNLeStteEU0L1ZJUlNmYWJYWXFJcWpMQXg2eUNLREdLa1N2c0k2QWFxTTRNN1hVU2tHV2JiVTN1SmlDRm1CMDRKS1U0OGc4VG4yMlh4ZXNNMWpobnY1N2I2Wnl1VVVneDBJTWJacW1iNUtBN3BWcitCdTFhaUR3L2VLU3k4TGplbjRBd0xGS0xiZGRTd2luSFNzWGpIYm03NDV1VEVZSm5NV1NYalRhNG5PMjhmRGZLeVcyM1BwWVNuZmREMjlEN3NMdkt2VFpEVWowdjZmWVhFZTNQeGk0czRQVVJNYVVPSU1jTFNmYkN3ZStHUm5FWXhRRUUrQkF3K25PcHVrNnVjMWUycHZHMWJGRHJ6UWJUQThtTDFmOVVacER1MlpiUDZUREFZdTBWNVVGVFpUWWs4cWNDZFBxcy9PdlZwMy9wTWJ4eWNlQ21UNTdEeWpLNHhnNEdFOERWSk5GRHlxeEw1S0dUeENMeUVpdGtDa3JubVhGdTNKV1NZdVNOa2RhbUxFUitIMVExamQxNE94SWdoYXdKRksvMTJYZElKSjNzUUlxNHdWV1NSV1VEV1h2eHhyRlBOSktuNUdRT0NJUEVRRjYzL0NNaDdlZGU5WnhTTGVQZW5qb0N5VjZIbFFQSUpwOHZXTFFnYjR6dC9EdXNReHh0VmJpR1FNVTdVUVZPTEJFeGRGZUFteDdiTTNuditla280SVVKYWRnYmVFa2txOXhPZW10dEh1RnhmUWFiNWU4WFl4ZG52bDM3NnR6bXB5Tm5jenB0T3ZEdHkxWnJBampubWhlTkYxcHU0d3ZDZElWS1Y1YmZxb1BaWTJVbnhURHlZemR0NmRKNnJEOG1POGNwdGlmRUp2cTRGQXdNWU1kTlVveVNtdGZTWmQ0MFdXNzlBa2dPU1NwMXIyZ2NPN2h2TjF1YTRBU2trVjRPeEltYlRhSllVWTFnZVFScTJTTVp1UlZhU08wNEMrRmg0N01ZblhncGh2bU80RkxYenY1bWVlQUNRWko3bHZldjFIOHZSd2JjTGZxejNtRFl2ZWFoajVlMjkrSGtEVEFDYXI2d2UvaWxrZGttdXhnWmpoQXY0UUlSQ1ROejE5WERzcGdiQ0JHZGZWT2k5ck03aTk4dVpVY1RzcDlPK0doTit5MG5Ca0R6STlxdnRSK1JFSHZVYnhsUEhzNTZyYzNXMFZ0bFNRYkF0SVA3M2t0bkV1azIvVTFNUElTcm1YbWs0WXp5RHNIRTFNUHJaVFZHTzJDQ21BS2NCQm84V21TZm1jd3RHNGVmYzI2Mis3UE41SUtZMmp6eWRNNHRDVk9ObHlFaEJPTUVJNHdNUjFlZGtLZ2tBR25sbHRtMndoUTd3L1lpUkJFNURXN29BOGsra2ZFaTdGbXQzR2Y4Ly9qa3hOUmQ1YzRlZ1JrVWJaVFZDRXRyWDJUQTVzV3FNTXBNSWxha2hYZFdoZ3p0UEtZQ1kybWFrYmwreFkwVkMxSEhpSEdMSFVjeEd3WnJPblRicVJSUkEwYXNjYWkzcDh6VVc1TW1aWGJOaXJUY0tGY2JpcW9KZHc0T05PVGhFV2JCeDc0TVgvQXozZnlpRG5talpSVlZ0RjlWQ09GU0JpK2lWaW5lU0JGMm1HL3d2YzdDdnRITEcydC9BMUNnL2RieklvWnJZVElreWg2LzNBc0RDMS83T3RjZEwxNitNSXM1RXBDbSt3aU9pOEppRlRvejRVc1FRREhNVm9iMndlWnFpR29ITHJLVngyM05zZWd3ZW5hZjZ2cVhESXExa2QwUXNnbTl0YXpNSTNyTVplU1FxU1BIWG5DV1Y0RlRmdDNQNTd4OGNId2ZtUmUwU1RNRUE1QjlFMFZqVDBWVWZtUDloOXBKaDNKb0RaY3gyTmEyTytqRDVUTDF6SnRJcy83TlB3dkl4MDVlWVByelliNWQvOGtGemF4V1ExRzUrb0VLNm1KckdaYkxXZzJaZ0dmNGFXLzRGaXVySTdxNFZzMDc2UDYvTGlsKzlxRFdIWFRJWGZIOExITkRhWTh4eFpoazFhT0hZMzdRQjNQd3dqMTljYkJWMHhRcHIwQzdlb3J1NnZnWmpEZHN4cTRralZQdmFUWWhjM1lPQk9qczhRbC9xaTVxOGNKTlZGSXUvZk4yU0kxb2hWSWU0QXdYT3FmV0kzOHdpY2l3ZVpmaG9EWSsvb0xrM1ZKMGl3aEdnTW43dTQ4VWxhajZwN24rL1BlUEMrcWNhNVR5WUtNTkNxUHUrSXQ1TWxIK1M1S2N1eXU3WHFWeURRdmJPTGZhdE5JWFZ4RVZjQWcvQXpjQnpmUVVVMnU4VElUWUl4TVIyRHBrY1JidG1FMER3enRIRlRPWCt0NGQwem1neFlqR3EvcmYvenZYUnRyM3BsT05ON2RyM1RaTWpTWHB5WUljSHRBSGZsR1FBUlpRdVZDamR5QUc1VmszZ1dkZE9qYXM5Z1V1djRrUnptYjJQNk9sZTJlKy9WRjA0a0tWd0dXNGhtK0s2dlhDV0k1bnArRDNKaDQyUERtL2VjcnowWC92NXgweUFieFQxbWJBMnBoeG1uVUxPS0NuUkhzNHBoMk1iVUhCdTBXNlpqUi9EdVpwYnF0SDBzR1NBRHJDSWVzcDZuQ3VjelpmNE1ISVhCbUQ5azNYdDczSE4rbllCVDBlVG82L2cyZDl3TVNwVTVXU1p2cXVkcVBuM2t5eDZPRURueE1ad0FjTk1YMG95ZEJYY1ZodE9VM2ZYUmp3YlpsUjF3eXFBa0tETk8vRzFONHNQZEJBa3FhR1RHVGR3c3JzNXhqcnB6bWEzd0RycHY3Wm9CMWlkUjNtSGRKdnNtdnY0blZmU2lYaG5wN3ZoakI5L3B5MWdJZ01ScTVIalpmaTl4Yk1XUjJYdllhV2ZlRTJTbkQxRFhoR1pZekh3djREU2w4NU5CRUw5bFlSaTdpbEYwMnpRU2NDcnplRkR0Z3ZtQlVSNzQ5UHA4U0M2am4xVkswSWVLbUpmaHpqWDFNbURzV2duTVJUYVRTbVRzekVqU2RacVFWZVhXTzVjR3RRY3NYV2tZSHNEd09rUEE0U3NrUXAxbmVkam9BUWRhQ2xQcTRIN2Q5LzFEM2JueHEvdm5xb1ExNDBlVTIvV1RLWUplRVo1dlhNREMxUFVyRkdKbVUxeUJCR0VmMnp4ME42NG85YkxCczJORk9rNTQ3aU5CalhDOW1WcnpNaUkwWDZ5WWttczk4b3Z2M2Y5SDlnUGdtbGxEaFJkSzdadUgvWGsvMnVRZXkvclRwYkdKd1lOckpoMXNOb3cwL1dEYU4vNldMV1NvU0F6eEZwZXhKcjV6NEFNS0JBSWFrUHFFdjBTZ1RzOFFvZGtjR2ljSGFNdFg4NWVpUkdLZHlsN0pLV0krYjU5MWZMZlJJMVVsOGxjeVkwRXh6MkxhZTk3VVVwT1FuSHBpSys5dnNqU2prbEFDcndaMHZVRGFoNllHL0Q4OGMwbnZDU3NoKzNNYUJsc25Pc2VyT01aQWF0OTk2emRKRkpYRkhFOWt6dytrekw4N0VJV294Nk1ObXdKWlZ3dUxHRkgzZkY1bU0xRk8rdFdCdDZ3UFh2b0R5dGVnY1BGOCtpZ1pJYzN3VkpDZm1iTWlnaDZRY0E5d0YvU2p3SWYyZ3VpdVNORDUwOUFVN0U1aTZYK0FCSGs0OUdSSEdlQmZLbkMrQzc3T1pVT3JpeFErUk1aekhVdUZ0SmZKWWJJd2VxYkpZdHhoVWREVDNGK2pYV3krcWcwOW9ONldPNmRFYk55MHN0Y3BtSUFHLzFxSmwvd3o3b3R1aDY2bnV0QW9TaHJ3eElyWjVEd0ZOUGdsUnF3bzFTbENMV1EyNGc1QzAzS21Fb29NSHdFTURtYStac1E2aVJlbllFVkVLR09ET1lqbnZyK2psZktLQm1tWVFDSTA5WjNEMzZWWEdkM0JNV1NmZXVlMElLcUtsb2w3bS9YM29wdGlpY2R5Y3hQbmVUZlNxMVdpVzE5NWVscTlPMysxQTFabEZhVmlpdmpjcHovZzQ0ZTJBM0tJKzE0bWMxYzF2ZFM3cDUvUjJ3aEUydVcvSFN2enREdlZqQi9PUUVOdTBia01SZVVwTlNYeE8xNjVyTkViU2p3SjAxQld3QkxXSmZsTGp0ay9UdUVyb3N1cXVScGFHZGI0ZEhrTHZWWDM3Zjk4ZGpqeFdoU0hrb0NlakVaWGtVSmc5a1l5cE1hMnhVd214ZFRGNGMyaVZsREhkNllVcjRGbGRzNHNsTFdOVTNHRWs2RFprSytaaFk3emRCRVloMUFCMThmdVhacU9qWGMrczZrZk9GV1ZzbXQ2aCszNGd0anl4YXB6STJBMUR2TDlnTFV6ekRLR1ZyZ0VJNlA5QkJPVnhLWXdFakNZY0NZT1JJYUdxVTdUZ2Z6bzNtRE4vQTU3MllXSnNxc3VoRDVJRHZiblhVU3RCdHd5TithYjF5dzZjRlBEMzkyZUlFY2t1RklhWnlrQ3NZMlNvbERqWU1HMjRiRFo4bzBOL1Y0N05xancydlNjQlFkQkQzczRCVVp2SGhoa1NNYU1YRjhUY2I1NWMrSGhva05IVUNSOEVDT0lKSUZWSGdoTzVaNzliNDlOQkhrNHRVdXFHMUxBVFJnOVlia0c5dCtnTlUraG9hQm1QRVpPQWtQVkljaFN5RTg2YkZlbkxyb0VSeTM4R3NIM1Z4Y2hOK2F6OEtNMzg0SGFIUHdhQ0NOUDdLZjh6cjBZWkJuWDJtWDlVWkZHSHpYYlBtSUhBVFV1Nmtac0NwY3QyVElOaHdlNDJGWXpsS29LV2RMakFiRlN0K2dsb0JGbS9Dc2RiYm1zNjI5dDE1YTJzbEFnblMySXB0Z0oxQ0lONlo1cDFwcEw1TUl2TUhIbGRLUk4wODMwWlIzMnlZMHEvcWhPdnFzTHhpdTVxczdPUzV2emhVQlVNcURmc0ZWd3grSm5FS1RBOCswV3YvbWZSYnp5UE5EcTBGZW1RUWkrN0lBMFIzNkR5OVlubnVpd2RpM3BvU3VXcW5WajBCMG0zazVHMXRYTDliVUdQMnRjMTlWcSsrYWNyMDFSbytmWkRnSGhOVU9oTi9ZU2F4cWJDVHlGV2ZGcFBHSUEyTk84OXJOYk5adzBxbFB0OS9PR201c3ZOdmJ1V0pTb3ozZzhMVzJxZENYV1FVZDB2M0NjTGhnUmZiWjlPT21BUkJzUDlYejRLZzBUYXFld2tYd3JGQVFLTk5teHYzMnM0eWRyLy82QXRqalBWTzRhV2FEVmVSVzFFSFlsQXVqSkw4WWRBSU8zYTBzcTdGQW93UTFkbkpCK1YvVFl2VmRMRkVpRlJEUVJmcmlHSTZsVUluUHZMUXVLaXk3WUlrOExlUDU2L1VEQ0srRWlJcE5CNnpId3p3bU0vTU53dEtLendrTXorQmRZUFVMbVZ3R0ZoSW94MGJuY0ZXNzFJczhzaWwvYTFiZnEwVkk5TnMwSHpEVVFYamFTaVJxY0hXQlgzQkpDbFU5bXl1Z1dTb2VZeFdTd3ROVTJISzJ6b1JzQUVyMjZDNVVDazBJaXRzT1gwN0doaU5zOU1ZejFBc0ZWUVRLSjBtWDRFR2I3eURXU2czZC9UM3EzK3p1RjNqSVkzNHBqbjl1eWFBSkV6UnRnWmVCOXNZOS9qd2o5ZHhwVDBlb1pqazFFSHBhdkw3cVJkOUx2Y0U2ZllMTysxRWdsZkxFT3lzRm5ONkZjMHQ3UGkxVXdHU01NbXhuTEl2V2pBV0VLcFd1RGUrRUJZWW1CUjJhclhRZ2ZPRU9xbDNEeTR3N2NXbjA1Zz09PC94ZW5jOkNpcGhlclZhbHVlPgogICA8L3hlbmM6Q2lwaGVyRGF0YT4KPC94ZW5jOkVuY3J5cHRlZERhdGE+PC9zYW1sOkVuY3J5cHRlZEFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==
@@ -0,0 +1 @@
1
+ PHNhbWxwOlJlc3BvbnNlIHhtbG5zOnNhbWxwPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6cHJvdG9jb2wiIHhtbG5zOnNhbWw9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24iIElEPSJfOTM2NWZhYTNhNmY2YTIzYjVjZDc1YTc0Y2IwNzg2ZGMxOWU4NDUyMDBlIiBWZXJzaW9uPSIyLjAiIElzc3VlSW5zdGFudD0iMjAxNS0wMy0xOVQxNDowMDozMVoiIERlc3RpbmF0aW9uPSJodHRwOi8vcnVieXNhbWwuY29tOjMwMDAvc2FtbC9hY3MiIEluUmVzcG9uc2VUbz0iXzNhYjdhZGIwLWIwNmUtMDEzMi01YzNiLTAwOTBmNWRlZGQ3NyI+PHNhbWw6SXNzdWVyPmh0dHBzOi8vaWRwLmV4YW1wbGUuY29tL3NpbXBsZXNhbWwvc2FtbDIvaWRwL21ldGFkYXRhLnBocDwvc2FtbDpJc3N1ZXI+PHNhbWxwOlN0YXR1cz48c2FtbHA6U3RhdHVzQ29kZSBWYWx1ZT0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnN0YXR1czpTdWNjZXNzIi8+PC9zYW1scDpTdGF0dXM+PHNhbWw6RW5jcnlwdGVkQXNzZXJ0aW9uPjx4ZW5jOkVuY3J5cHRlZERhdGEgeG1sbnM6eGVuYz0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjIiB4bWxuczpkc2lnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIiBUeXBlPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNFbGVtZW50Ij48eGVuYzpFbmNyeXB0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8wNC94bWxlbmMjYWVzMTI4LWNiYyIvPjxkc2lnOktleUluZm8geG1sbnM6ZHNpZz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI+PHhlbmM6RW5jcnlwdGVkS2V5Pjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNyc2Etb2FlcC1tZ2YxcCIvPjx4ZW5jOkNpcGhlckRhdGE+PHhlbmM6Q2lwaGVyVmFsdWU+eGtKbjdMUUhyVFp6TGRyTmVjMzNoTVNDNWRjOEIwdU00Znc4SEtod3BrV0lOd0M5ZGVhajBRVno0Zlo4Mlp2MlFkUDN2NnIwd0ovOFZyVUlGOHB1SlRIU1UwdTJFVVUrWHFCSnh2aXZHcDJVaFVQQ3lXbWlyU2k2ZWZCbldxTUtoT3o1WUpvYUxFdTE3eXcra09uYXZMWlB2MmJCOGI1Ym8ybVpQaWFQRzZzPTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHNpZzpLZXlJbmZvPgogICA8eGVuYzpDaXBoZXJEYXRhPgogICAgICA8eGVuYzpDaXBoZXJWYWx1ZT5FUmlUa2RKekpOODQxcUlSd1Q3OStsYVRRWld6ODNvc29Lb2hSQklhQkhOTGFzR3VCQXBSNmlKcXNTclFiMi82d1dkZERhTk9xYm5CRVkrNlplMW1TekRqWW9tcld0VkREMnBzcnprMVQyOUc1Nkx1VFFpT3RUdE9VbE9QT1ZVa1M5VHAxUmdDWG1pN05vNDlsSkpIeE04YjUzWTZoTmpKUWlTd3JFZHhocnY4M01TdXFZYWh1VDVuMnlCOWpHZy9abkY2SVAyWVhMVDdjZDRrWVJqLzBnMG1FQkdBSUhWM1pHUkxWU0tnMENNVlBuUVVDUExYcVZDS29SYkYybnQ1UjhRNmdHbUttLzJ6c3lFZkR3L3ZXMDc2ZlZnNkVvSytYVlovTS9nSEtjY0EraXllSEtNTjJrL3lXZUhuN0ZCMzJqWTlvUjhDdDZtVzlKbGdCYmxpTTFTQ0RNbnNodUpYbEtueVh1dk9mSlFkMTJOVXJ0OWxXQUN3bktRelNaM3kvYjVVdGhhbEFZVGdIUFZ0TDg3MWhYbkM5TVRlVGZBaytIcVY3bmZkUEpUay9qVkRJaTBXc2JxbGYrcHBDMW52UmFiOG92V21mcFVLRlloSGhpUTZLNlhRR283eFQvZEtxVjcrMnd2aEh0dllQdDhpVzc2UmtXdEhWRWpaYUhqZ0NGL1hHcE1yajVDdVg4S01RM2I2N0NJeGIyOFY1TXhuVVhySlYxcTVUbkVGNTZidmxHNE5OcVFUWVdNY3UrYmhjQTV3MUUvckpaeFNLZnBnN1ZkVDRSTUEzczBaQ1hzOURSN1JualFQOXN6cnl0YXJNUndGK0FJMDdBeHQyL3o3MXQ1NTFyTnpDZWROTGRRRm5GYkZoOUlnRFNtWWluN3kxbXhzYTdJaUxVWmNKd3RKVGgzM3RLZjBPYndNZmo2OHZXZ0VKMEFxOFpqbmhWQjdDN3NSSzR2aDdOT2dsZFMvQ1haMkw1RTVrWWgyaFZ6cWgwRUdKcmtPczNSYjMvbmJaQ3dPNHpkQVl2djc0cXJQVWtFVXhHMy9wOXdqY2crYlNGdWxjSkNwazdpMCtObnNvY1EzT3BEUUdVRmh0djZROTI1aDJMREFZUVNrUWdJdlBucWJ0K0E2VlJDbmc2ZyttTXdiYndQNm8vYWdZMkN5ZHVFRDBRSU5Bdlp6ODVDcHRzM3VmNHV4SkVuYXp0REdUMVJCUEZ3aE11SlZ0N1FlNG5xdXJSZnRDaCtUNktpVnZ3RTZDcWZ1UkZRbStTbFdGeVlxNXU3R2RSTkZzdVQzWUtqaTNtRitVZHFnTkphKytSNGJwT3RabTFRUXp2MXFTUzZVQ3RySkhiakJYaFlEaWczZEV6MWVya01XQ3d3amNpY3BlU3JqQkZjMGxjMVZ6WWZtMW1PWVdCSFRFMlJmRG5FVXp0T3pCVW1pQlZEM0dYbVM0eHJVRXJJcXozUkhlMWVyRzhId1NaYmZzWGZPcGZSNUs4UnNxTDVBOGwrdmpTTjZ3ZlFHQjF1SWYwN09QcGx6TEwvVWxaV2NQUXErajBHR1FBZC80S2JsNWdpV1NTYnd1ZWFXOTUvZFVKUUdjT3c3Z0l3OUxJUmFzQVdiNkpRMC9UUmd3VSthN0hvaitjRkFkV3hrZVY2M1Y5ZzRCdndPVzNacjAwcFNvMkc5ZytLVXNBdUZReGs5aDljREVxZEVNMnZQdGZlU2tqcFhPWm5HT3JSTlZVRzIzbGdKSEVRc1ROYitWUWI0Uk9GSkJWampkcEU3akhNVTlZYUs4RWZRb2tVQmZyKzhPdElkcDkrTEFVbmZaM0pLR01LT1JPdVEycnRqYjNOQ1o4c3k1dTZ1c29tMG1iY3hIaVZxQStYYkZZb05mZXI0SnlxSSt2YW5TQytkWG5VRUhuV3hGOXM2T3FtU0xVTkNRMFRsS2hYQS82U0VCVEM1TGIrTnVzdDgwdFlIU2dCbTJTNDlhRkQ1c1Iyc0krOFNDZDNmZnRjeVpFU1pDSDd6SC90MGM2Z3I3QUVJUEhhcm8wam1KUFgvMlN4Ym9mMFREbWQxVkNQMXZFUUlLby9qWGdpUHBsSVBNemsrWlRLTENuNVRHamxCWFFNTGtFZ1hRdUZjeGY2a3duV2d2Vis0d2ZrVjBkU3dlTUY0UklxN29VQk11UmNLTFp6c0kxeEsxK09nMW9yb3Mvb01nbWtmQXZSK0xIbi9xMnZsNzNIZ1RNS3l0aTV0QndXSkJER1ExdUtQNFVEemxPM0wzU1U3MnhUMlNlQWthdlU2V0owZno2WFhSVklZbmZrZnpKNm5VcFhZeEVBN0crOVgyeklCZTZ4OW95RzFyT0I4dE5HM0l5SEpYZlZ1ano1VGFNVnljUDVoRnFCeStFTjJXRjFWdTBxTVVnaTdaWTl1MnhEbnJJOVhrWUQrZDBoWGhDNk93L0V6TWkrVEhWNncyTXNPaHk1RTBGTVFQYmlpcFJTaXJ6eHMwYlFrS0xtZ3hreGF1bGFXUHVDQVRjMmxOWk9iMVhwM3kyNzdUWDVJa0pRZGJvMWg3NzJjVVRmL3RmVzZsYi9teTM0VmJGRUpFdXNNWFNQeHdYYTJMa05vcUR2SUJQanNNWW5vZ2FncmlWV2QrV0RJak0vU3FoK1JoUm8wT3dwL1Y0dzhLaHJDVTB0RkkvZzFwbjU5aTRtcmhuTVV5ZVpkWVNQazBURmROQk1la01CNUtxM0NjOWtIU3BlNFRDQWtXa2tuWjlxS0Ywc0g3a1FuRHpmdldzckxESGR2OWlqWC9LeitKNHd3b3BlU0tjTGJqT09ScG5OZDBzQXVGNEZod3VPaDl0OWJIZ3hJVk5waXBjZmlNVUt6eEkxSWNMUWpGQzdhTnVjL0srU2RXNG13MUw5ckk4SmloTkgyMXZpZDRoOUJaVWFZMThmZWRXdmR0SCswVzJUUHNIZ01LTTVLYkFEUzZpeGN4YUs5cUJBRUxEYVJySXdZMjA5cERnRElqNlRVL0lyaEI1U0tHc3JFb0VudnZ4RFhJZHM3dlpQZmJuNlE1S3BoeGNVNjZ6TGhJMUF0R21Za08xdUlSUnJqWDNBMXpjaGhIcEUyT21URmhDRExZTUl1R1BoWTU1STcxTm56bjZ5c0YySEhueFJCV0VJRVY4YUVGRkE4N1V6cWhkek5XSTNzTWN5N3lWc3lMeFdwZFNCK0dwc0NNOXZVMStCUlpoMTBLZU5ST1NRcnlZM2xVcC91WXBXQ3N1VFBNWHVZdnFTNlJTZWJGZ1Z1MzhiZ0dmYUJ2TElMZmIvWWtjSFdHUC81WW8vbmRFZ3NUSTJwRFRxZHBEQXJOYXhvSEpKK3VDMURoUEtzUWdjMU9MdGRjSVI4MWVIbjlKSGRVS0JSV2NIVSs4R0JFY01tRmpIcmJyN1Y4ZVZQT0V1VG9kTzFnSWpRdVVDME9qUkQ1TzdnYWRQTndNRzVKVjNRODc4VjhzdGtyUUZQeUZkZ3NDU2pyUFBnZVhpZm1zWkRlVnpWdytiam9BQ3FiVEhjWUdZT0VZVWtTZlllNEc0M1lQL3dlMHdyNGVoSlVvOW5qNHRhUDhJTjhubm44WlR2dWFpOExIVmdSWit1WFpJN3FKdk9Ub2oxczhxQmw3S1daVEhSdVZDdkFDdlpwN1Y2bXp6QnhLZFF3QjhBNS9tbnAzam5WaGpGa0RUemN6QkRhUHF5aHVyTjNwWVZkR2hWNjJUVHJFQ2YwNHUwdXZKWS84NHY1NnhIc25ET3cwUHRLWjNLYlJkYVFUNHJJM1lmUzdGbEk0amsvdUljRzVPaWpLeHplSzh2OVVDRG43dEFvREFUa1NRdmk5VG45Y0pmVWRMTVlxS1hOKzNDTEtPc0pIOTc0Yis3OFpoRnBsRDZ2Kys2UEh6WmFlZWxQa1AvOWE5dllVYkRQWDNSeDNieFZmY1ZnTzNNdm0vMW1CL0Q3MXEzM3pFVXJlUzBxQlF6THFXTFNPbUFjYTl1WEFzVE1ydlVCamwySXI1TlhjTHZwbTVrVU11M0IzU2F6SmlEVDIxMmZQdTAzeFQzUElZbEdNWHljMTBLbUZCeVhldktBY1JUc1M4b3Y3SUhuY3R4bzZBczIvVXNpUVUwUzJTYTJmeDB3N1Q5K3lMT1BrVVB0cGxDTEhqcVRxM1pEc21wUnBEVjNYWUZNVmdCTE02THFLakRlcDN1SkIzUTFQWDFwZXdIM0hrbFd5VjdXeUlXc29ENmhGQUQzdFJpbnJJT1oyR1l2VStGZlRmWnVKMkJsNlVHcEpWM3ZZYTRNblZ0UWxweU5Lc09vQ2Z4QU80eGkwY1dvdGxtSnB5T3o3UGxFK21La1lDS0RsN2kzZzFuYnRrMnZDNmwxWjdySmFQa1lMQnNqVzRXdWlmT3M5aC9EeXJtYWVTazJCR2NUMXc4dmdpRzl6c0hzUkVwY083SUhGbVB0VmlOZHd6UlNkKzB1ZnVicDFnNDJ2Ny8vV2dOVS9zd1QreGVVazVFdy9kbm5uVmpXRjdpaEtHK0xqQTZaRzVXdkNZcWV5VDFLZmZDSkMyYXdTTDhtZ0hUcGFCSGZPNkRiQStabmgrNW1OaUhOZEQ0S2RDcVVOdWFDVzNPTDNmdGovZmd5WExtNEpRbDg4czNpeFBrSFdETFFra3ZOSEpmNFpNRDBaZ3p3MEdoWjlkRUVsY3JHclRTcTJLY1gwd21BQzJiTzQxRkVmdklpNFlHVE1pNDRxMVVIN3NLaU9hbTRiRHJuV2Y5citlY1VodXRDaXZxQVk1Zys5T1B0M1ZMZ2RFSHY5SHlVazJQallCQVFKQStuT1NIbkJHeExKUVhEMHVYWW9YM1JyOGM0d0hDeXlDNXBuUnRXWDZmTGlhWHJydG02bWk0Qnh6VUcwRkdCV05qdmFDdmJnT3hGTjdpOHhzb2RlOEZFbERVRllYcDhNRzN5Rmx5RHA3RkhFWnNXd1VYNThFNlpqK0hqQlVFVGNZUVhValgzZ0JDMlJ4alpjejgwWnNTYUZFSWhzcGhJQXpSRXIvSmNmUmdwSkg3YjBGdmJHL0VJUUJHTXdTWjFMSzdaTTBvWEpZK1c2QkdTaEt0cDhlT3I2RWo4ZnhjeFFvRHhJRDFIL2pnS3d6bHRLcTZ2MWZwWVNiQ2pkK2xFdXozT0JSTGErYlZyOHJ6U1JlekoxWGJRQ3ZDRGsvS2VRUzJhdTNGT29HSWhhWVR6RGVjOEd2RXgveGUxZ1FqdXVtQ0tzZkRPQWRUazJSTkN3N3p2c1BHaytJTXRlTVVOSnlhK2JFSzhqMXprYi9FbHhjNnNtd0FTY290WjJtZUtlL2xoaDBUd1g4blZCSGpUUC81Z1BGR2w4ZmwzNmFKMURxUmlqOU82aXYyaDlkY2tUMXhiQ1dZL3BXOGdGNy9UeU9Rb0RqZS9KTFpnOTd4aFowUHhvUXlXSG9wWGxOYUtpdmZEWEY1c1Y3MmxjT3phelNQMzJtU2VsSnVoNGtFUzMzMmRwalFMc2lUMlRHSzd0c04zcjc3Uk0yaUtwQTNIRS9YeWhqeUZxa3dJNGM2RTd0Q1FtWTlDUFhlTldaeUxXWGRMLzRGZ3ZzN2VsbVFqaFZ2aHgrOUs1Q0x2NU8vRFlUZUIzMUw5b0YwekxWNk4vSyt2ekNPRmFGVDZ0STZCK2ljWFV0Y2N1S0djZ2dJREY0Ti96K0FLQ05LMW0yVFgwUDBZN1pQRVNkVnp2U29ZUXFZYkNSeCt5R1RBQ0VIZDZjNzJ5M1VsZ2VjaWZzY2RKOTgzRitRREZqZVpCUmY4QzROaFd4QUx5cndxQWcxdEtKUFg4UlhFcFFmSkRIb0dOZElGRnZlWmhrdS80aFlaRjZCMkdKOGwvZmdJdFFEY3dTbnFjdExOQmx3VkowUDVjZ2JVM1NDQVlRRi8rbGRCME5KaUo4Z3pZT1FDcDJOOGtKRUVsNGNjNWdxMjh2NUJ0bVVHaEE3R2lsUzRaU0hzNnRKSXVxZWdvOFpXS2lyNEtJS1pnNVFpaVRmSy9zTUU0dS9aYWRLSm5KNW1oQ1RFK003SEcrVHlybW42cmtnc0hIbjZzVGM0cHgyM3h4SUgzUElNNVlDUXdLMER4b3BhZmxYRFhVQndZWVVyN21POHp2K05Mekkzby9ua1F6d1gyK1dwM2NiOXFFUy84TjdxNmgzNjUzeThqZ0tZaSsxVHYrT0RMVEZheDlRa0FHQUtOc2JlVzlkT1dDR292RFNsY0Eraml6Qm1PK2FnMkR5MUFvWkVJL2lveTlyZm1OV2U3ck5DdlIvdWxWTnN5ZUM3Vk8wWTNvNmc3SkN0dGpjNStCZWg0L0hDVGcvQ242bDEvL0xodTFqVmVjU0NaUU9UdjV3TFFLcUlURjQvTnRuZUd5dFdrVXNyb0p3TXUxUGpDRkJDcDBJOTM4dEJrOG1DcHpmNTdpM0M1MEkrdklFRk5ETnEzem1vU2tjTWFlOFU2S3ZwMS9lN3A0d3R3andIQXZ5N1B3ZXd0WXpuZGhUNUN3cUVxMXVOL1Nya0RsWGFDQU1aR3Q5akhUek9OdmlYam94N2wwZnIvbDhISjd5L0dMbm83d2RFMDBhMEx4emx4TkdOZGNLdXFyRGpuZEUzUW1MVVhyelRJQ1NINysyUDVicWhvZVNqajQ0SEF1L1FrREhwQnFRbDBrOTJEaUt5NFVTYlVMbnJBbFJuOGl4MHU5dUFaTCthTXdyRVJ5RnRJb2V0TkpNcldwa0VIRUxnMlY3NDRxUjNRWThRbmp2TDlqbUc1SjFyQVhOQ2sycWJGSTRTTFlPK2JMSUFaYXhJY1hyUDVHTWYvb2pZVVpFbkJpbnkyRStKV1I5VEZSamgrOHl4WGs5L1B3SldJM1JESkZkVjZyWThyc0ttMU0yKzAxcU53akFZS2tXVGR5QmVnM0psdmI4NEJvcU93UFBvSkhYS0IwZkpzWTgyUi9Gb1ZRSzI2YUw2YkE1eGNBdmZWc3p3U1lzKzR5VEpBMnJvQT09PC94ZW5jOkNpcGhlclZhbHVlPgogICA8L3hlbmM6Q2lwaGVyRGF0YT4KPC94ZW5jOkVuY3J5cHRlZERhdGE+PC9zYW1sOkVuY3J5cHRlZEFzc2VydGlvbj48L3NhbWxwOlJlc3BvbnNlPg==
@@ -0,0 +1 @@
1
+ pVdbk6LIEn4/EfsfDOfRsCkughrdvacEtVFRadBWX05AUQjKTQoF/fVb4mW6e3pmZ/c8aWZlfvllZl2Sxz+LMKgccEr8OHqqsg+g+ufzH/95JFYYJO1XTJI4IrhCjSLSPiufqvs0ascW8Uk7skJM2hlqG1AbtbkH0LYIwWlGoarvXJJf+yRpnMUoDqoVVXmqJm5hoyYnWq5Txy0X1x3etetOUxLqSJAAQDx2AWpWK/MbZ4pBXQnZYzUimRVlVAVYoQ7EOhBMwLU5rg24VbWiYJL5kZWVXl6WJW2GsZLkIdw71kMUM4TEDKLp7kNMAaNb8mb8VP2fiwSLF2xQlzClA1gO15Fl4brU5Gxks7xt883qc1m2dsklfT5HINcQcYSDeO1HDygOmbMR98i8t310SNvw15TcPr1V2yF3lnmeP+T8Q5yuGQ4AwIAWQ20c4q+/VWm3KpWbP3bUyI1LONmK4shHVuCfypQ1nHmxU4HBOk79zAt/As4yLDiD13GB6ogVom9VpgzxPUhJ8jfhPnBNiVUnnsVeEc94r9jFKY4Qrsxe1afqt99tf5mimVoRceM0JB/Ff8YKRwfanAQ7dXJLjhL8Z4BfVu2R+ZGj4q/pLvw3xbsW7jvI3Ar2+Fk9CSs9IDwcrBSbJ80Dmi0Y/hXVevpTSeC9cam4l/wifto29wZfPHRvOAjj6Eim7C6MLMmo4S47Qlw6hJuNzE36qT5ZG5tpYsZoH+qit3zZnkxjlUoZozJDc2IcXmR9p7CpJwQG2ChTprt/GUM527aWUhBws+N6L3Gjmt3LdiuupUStdLQ1nH7q11TpxStGvCg5LjQMT6yRo/SGD11nq7k5VnQOLughEjud/vokmd2nezrv+NOrjCqH+HhPcNEALcXKrLsgn28sl56UDD9rqir3T7IMl7os6/J4jPRFHPLcXIHjznq787Z+v5WDDtRnPah0DE0nuawvlbmu97v5YD47dUca3PYhO+vKHU02ZkLRM6HZWY/nHYhMpRvsV/3As8NebnNFgk7dVw02L/aepppcIzD7rch6EwpFgcOLHzEhmO9XJ4h7OSjGCgTaRismil5oSs8qdeZHndZTC/kEBxf/pQmDufmOq0q5DsxNd651tDJ2p9A0462xtd56++Wic7D7wUl77eZKXtoPu7k3tcO5ZnNOYm+6mgbjC2ca7fWtEWlq39Ug6MvGrm+oNq/o3XONIBT6Y6jIHV+nqehKLIY1fXVY6kwxAt1gtN4nQ1ZX0HIkuI0p2uZkvDZayewwl9yTuZNftk3TGxXmVuA0ndPQgEzwbDCfSlxz6B3DTU/eFeupMN/nr9tWmsAA1ELvKGpT57g5QrHPCptUeTPExpGMkDcUMuaQJ13lBAw96MZ9dqudgsAIpdPK4BcpXkNJ2Yyt2VLf5aoCddj5nFPnklMHjjTu0Jd9nTmENUs8CKA2X3AnZ/cCOZ1hDz3Wt/XToCFoA3kyfzuQ2kFfuKue54RgMtU4NVVmUrw7DKe7qVhMIB4MxRdwnEqasBzxrpUZB3UZhu5xIbe2GW8cTsz4mL6cpt4sGoAjZsjMWBTRSS9yFDKtaQ53qW5BXk/07UtDOto9ZhKnxymucV55LD5v9LvychSY94fkwzF6vo4CRkZF8lGSYwdXynP26xeelNZtY48QJqS8GX8EbcPb7HB9/4qfvX8ss9BGBvJwaFXvtv7fG9f9cj5A+Ifh4TJ3tBqsaAOXrwsNXqyzwBXrSHStestxGhyPWaHVRL81afwfc0ApGHt7g1F2lca0nqpS6dE3xMp+Xmj2gS01vlN3S9M2TdkPoOOk56I/Z/QV+O/7+NfIF/iPkeU4cv0zxrkbl8fq1x1GYdvGVorT6s+BzjutMo6zSTRJoZvh9Fw9HryrnnSZ014x8hMfn8ub3v7eN81XyJ+WrhSoheOfl8k5agfTquCyYez3kOw15G/Quu3SveOfH086HWapjy7hP6zcG07iENOCP+DCCpMAlz23rkZXxnefT/IHdOZTOvd4mRedTxAOaYEqpfg3E7BBdwJF+Em2jS9M1cjBBZ1/WdFtSMC1EeDZBgCSBfhGw3GxJdo8EkRUfU+KUs1wkX2hkgP6hUAHkOdffkSgNjrbUfWU/uRx6kzpNwLtLHbKcSqJ0+xesC/Av1j7oLsX7a69XT33m+k2/D//BQ==
@@ -0,0 +1,56 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+
3
+ class RubySamlTest < Minitest::Test
4
+
5
+ describe "SamlMessage" do
6
+
7
+ let(:settings) { OneLogin::RubySaml::Settings.new }
8
+ let(:saml_message) { OneLogin::RubySaml::SamlMessage.new }
9
+ let(:response_document) { read_response("response_unsigned_xml_base64") }
10
+ let(:response_document_xml) { read_response("adfs_response_xmlns.xml") }
11
+
12
+ it "return decoded raw saml" do
13
+ decoded_raw = saml_message.send(:decode_raw_saml, logout_request_deflated_base64)
14
+ assert logout_request_document, decoded_raw
15
+ end
16
+
17
+ it "return encoded raw saml" do
18
+ settings.compress_request = true
19
+ encoded_raw = saml_message.send(:encode_raw_saml, logout_request_document, settings)
20
+ assert logout_request_deflated_base64, encoded_raw
21
+
22
+ settings.compress_request = false
23
+ deflated = saml_message.send(:deflate, logout_request_deflated_base64)
24
+ encoded_raw = saml_message.send(:encode_raw_saml, deflated, settings)
25
+ assert logout_request_deflated_base64, encoded_raw
26
+ end
27
+
28
+ it "return decoded string" do
29
+ decoded = saml_message.send(:decode, response_document)
30
+ assert response_document_xml, decoded
31
+
32
+ decoded = saml_message.send(:decode, logout_request_base64)
33
+ assert logout_request_document, decoded
34
+ end
35
+
36
+ it "return encoded string" do
37
+ encoded = saml_message.send(:encode, response_document_xml)
38
+ assert response_document, encoded
39
+
40
+ encoded = saml_message.send(:encode, logout_request_document)
41
+ assert logout_request_base64, encoded
42
+ end
43
+
44
+ it "return deflated string" do
45
+ deflated = saml_message.send(:deflate, logout_request_document)
46
+ encoded_deflated = saml_message.send(:encode, deflated)
47
+ assert logout_request_deflated_base64, encoded_deflated
48
+ end
49
+
50
+ it "return inflated string" do
51
+ decoded = saml_message.send(:decode, logout_request_deflated_base64)
52
+ decoded_inflated = saml_message.send(:inflate, decoded)
53
+ assert response_document_xml, decoded_inflated
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,218 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+
3
+ require 'onelogin/ruby-saml/settings'
4
+
5
+ class SettingsTest < Minitest::Test
6
+
7
+ describe "Settings" do
8
+ before do
9
+ @settings = OneLogin::RubySaml::Settings.new
10
+ end
11
+
12
+ it "should provide getters and settings" do
13
+ accessors = [
14
+ :idp_entity_id, :idp_sso_target_url, :idp_slo_target_url, :idp_cert, :idp_cert_fingerprint,
15
+ :issuer, :assertion_consumer_service_url, :assertion_consumer_service_binding,
16
+ :single_logout_service_url, :single_logout_service_binding,
17
+ :sp_name_qualifier, :name_identifier_format, :name_identifier_value,
18
+ :sessionindex, :attributes_index, :passive, :force_authn,
19
+ :compress_request, :double_quote_xml_attribute_values, :protocol_binding,
20
+ :security, :certificate, :private_key,
21
+ :authn_context, :authn_context_comparison, :authn_context_decl_ref,
22
+ :assertion_consumer_logout_service_url,
23
+ :assertion_consumer_logout_service_binding
24
+ ]
25
+
26
+ accessors.each do |accessor|
27
+ value = Kernel.rand
28
+ @settings.send("#{accessor}=".to_sym, value)
29
+ assert_equal value, @settings.send(accessor)
30
+ end
31
+
32
+ end
33
+
34
+ it "create settings from hash" do
35
+ config = {
36
+ :assertion_consumer_service_url => "http://app.muda.no/sso",
37
+ :issuer => "http://muda.no",
38
+ :sp_name_qualifier => "http://sso.muda.no",
39
+ :idp_sso_target_url => "http://sso.muda.no/sso",
40
+ :idp_slo_target_url => "http://sso.muda.no/slo",
41
+ :idp_cert_fingerprint => "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
42
+ :name_identifier_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
43
+ :attributes_index => 30,
44
+ :passive => true,
45
+ :protocol_binding => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
46
+ }
47
+ @settings = OneLogin::RubySaml::Settings.new(config)
48
+
49
+ config.each do |k,v|
50
+ assert_equal v, @settings.send(k)
51
+ end
52
+ end
53
+
54
+ it "configure attribute service attributes correctly" do
55
+ @settings = OneLogin::RubySaml::Settings.new
56
+ @settings.attribute_consuming_service.configure do
57
+ service_name "Test Service"
58
+ add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
59
+ end
60
+
61
+ assert_equal @settings.attribute_consuming_service.configured?, true
62
+ assert_equal @settings.attribute_consuming_service.name, "Test Service"
63
+ assert_equal @settings.attribute_consuming_service.attributes, [{:name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name" }]
64
+ end
65
+
66
+ it "does not modify default security settings" do
67
+ settings = OneLogin::RubySaml::Settings.new
68
+ settings.security[:authn_requests_signed] = true
69
+ settings.security[:embed_sign] = true
70
+ settings.security[:digest_method] = XMLSecurity::Document::SHA256
71
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
72
+
73
+ new_settings = OneLogin::RubySaml::Settings.new
74
+ assert_equal new_settings.security[:authn_requests_signed], false
75
+ assert_equal new_settings.security[:embed_sign], false
76
+ assert_equal new_settings.security[:digest_method], XMLSecurity::Document::SHA1
77
+ assert_equal new_settings.security[:signature_method], XMLSecurity::Document::RSA_SHA1
78
+ end
79
+
80
+ describe "#single_logout_service_url" do
81
+ it "when single_logout_service_url is nil but assertion_consumer_logout_service_url returns its value" do
82
+ settings.single_logout_service_url = nil
83
+ settings.assertion_consumer_logout_service_url = "http://app.muda.no/sls"
84
+
85
+ assert_equal "http://app.muda.no/sls", settings.single_logout_service_url
86
+ end
87
+ end
88
+
89
+ describe "#single_logout_service_binding" do
90
+ it "when single_logout_service_binding is nil but assertion_consumer_logout_service_binding returns its value" do
91
+ settings.single_logout_service_binding = nil
92
+ settings.assertion_consumer_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
93
+
94
+ assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.single_logout_service_binding
95
+ end
96
+ end
97
+
98
+ describe "#get_idp_cert" do
99
+ it "returns nil when the cert is an empty string" do
100
+ @settings = OneLogin::RubySaml::Settings.new
101
+ @settings.idp_cert = ""
102
+ assert_equal nil, @settings.get_idp_cert
103
+ end
104
+
105
+ it "returns nil when the cert is nil" do
106
+ @settings = OneLogin::RubySaml::Settings.new
107
+ @settings.idp_cert = nil
108
+ assert_equal nil, @settings.get_idp_cert
109
+ end
110
+
111
+ it "returns the certificate when it is valid" do
112
+ @settings = OneLogin::RubySaml::Settings.new
113
+ @settings.idp_cert = ruby_saml_cert_text
114
+ assert @settings.get_idp_cert.kind_of? OpenSSL::X509::Certificate
115
+ end
116
+
117
+ it "raises when the certificate is not valid" do
118
+ # formatted but invalid cert
119
+ @settings.idp_cert = read_certificate("formatted_certificate")
120
+ assert_raises(OpenSSL::X509::CertificateError) {
121
+ @settings.get_idp_cert
122
+ }
123
+ end
124
+ end
125
+
126
+ describe "#get_sp_cert" do
127
+ it "returns nil when the cert is an empty string" do
128
+ @settings = OneLogin::RubySaml::Settings.new
129
+ @settings.certificate = ""
130
+ assert_equal nil, @settings.get_sp_cert
131
+ end
132
+
133
+ it "returns nil when the cert is nil" do
134
+ @settings = OneLogin::RubySaml::Settings.new
135
+ @settings.certificate = nil
136
+ assert_equal nil, @settings.get_sp_cert
137
+ end
138
+
139
+ it "returns the certificate when it is valid" do
140
+ @settings = OneLogin::RubySaml::Settings.new
141
+ @settings.certificate = ruby_saml_cert_text
142
+ assert @settings.get_sp_cert.kind_of? OpenSSL::X509::Certificate
143
+ end
144
+
145
+ it "raises when the certificate is not valid" do
146
+ # formatted but invalid cert
147
+ @settings.certificate = read_certificate("formatted_certificate")
148
+ assert_raises(OpenSSL::X509::CertificateError) {
149
+ @settings.get_sp_cert
150
+ }
151
+ end
152
+
153
+ end
154
+
155
+ describe "#get_sp_key" do
156
+ it "returns nil when the private key is an empty string" do
157
+ @settings = OneLogin::RubySaml::Settings.new
158
+ @settings.private_key = ""
159
+ assert_equal nil, @settings.get_sp_key
160
+ end
161
+
162
+ it "returns nil when the private key is nil" do
163
+ @settings = OneLogin::RubySaml::Settings.new
164
+ @settings.private_key = nil
165
+ assert_equal nil, @settings.get_sp_key
166
+ end
167
+
168
+ it "returns the private key when it is valid" do
169
+ @settings = OneLogin::RubySaml::Settings.new
170
+ @settings.private_key = ruby_saml_key_text
171
+ assert @settings.get_sp_key.kind_of? OpenSSL::PKey::RSA
172
+ end
173
+
174
+ it "raises when the private key is not valid" do
175
+ # formatted but invalid rsa private key
176
+ @settings.private_key = read_certificate("formatted_rsa_private_key")
177
+ assert_raises(OpenSSL::PKey::RSAError) {
178
+ @settings.get_sp_key
179
+ }
180
+ end
181
+
182
+ end
183
+
184
+ describe "#get_fingerprint" do
185
+ it "get the fingerprint value when cert and fingerprint in settings are nil" do
186
+ @settings = OneLogin::RubySaml::Settings.new
187
+ @settings.idp_cert_fingerprint = nil
188
+ @settings.idp_cert = nil
189
+ fingerprint = @settings.get_fingerprint
190
+ assert_nil fingerprint
191
+ end
192
+
193
+ it "get the fingerprint value when there is a cert at the settings" do
194
+ @settings = OneLogin::RubySaml::Settings.new
195
+ @settings.idp_cert_fingerprint = nil
196
+ @settings.idp_cert = ruby_saml_cert_text
197
+ fingerprint = @settings.get_fingerprint
198
+ assert fingerprint.downcase == ruby_saml_cert_fingerprint.downcase
199
+ end
200
+
201
+ it "get the fingerprint value when there is a fingerprint at the settings" do
202
+ @settings = OneLogin::RubySaml::Settings.new
203
+ @settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint
204
+ @settings.idp_cert = nil
205
+ fingerprint = @settings.get_fingerprint
206
+ assert fingerprint.downcase == ruby_saml_cert_fingerprint.downcase
207
+ end
208
+
209
+ it "get the fingerprint value when there are cert and fingerprint at the settings" do
210
+ @settings = OneLogin::RubySaml::Settings.new
211
+ @settings.idp_cert_fingerprint = ruby_saml_cert_fingerprint
212
+ @settings.idp_cert = ruby_saml_cert_text
213
+ fingerprint = @settings.get_fingerprint
214
+ assert fingerprint.downcase == ruby_saml_cert_fingerprint.downcase
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,275 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
2
+ require 'logout_responses/logoutresponse_fixtures'
3
+
4
+ require 'onelogin/ruby-saml/slo_logoutrequest'
5
+ require 'timecop'
6
+
7
+ class RubySamlTest < Minitest::Test
8
+
9
+ describe "SloLogoutrequest" do
10
+
11
+ let(:settings) { OneLogin::RubySaml::Settings.new }
12
+ let(:logout_request) { OneLogin::RubySaml::SloLogoutrequest.new(logout_request_document) }
13
+ let(:invalid_logout_request) { OneLogin::RubySaml::SloLogoutrequest.new(invalid_logout_request_document) }
14
+
15
+ before do
16
+ settings.idp_entity_id = 'https://app.onelogin.com/saml/metadata/SOMEACCOUNT'
17
+ settings.soft = true
18
+ logout_request.settings = settings
19
+ invalid_logout_request.settings = settings
20
+ end
21
+
22
+ describe "initiator" do
23
+ it "raise an exception when logout request is initialized with nil" do
24
+ assert_raises(ArgumentError) { OneLogin::RubySaml::SloLogoutrequest.new(nil) }
25
+ end
26
+ end
27
+
28
+ describe "#is_valid?" do
29
+ it "return false when logout request is initialized with blank data" do
30
+ logout_request_blank = OneLogin::RubySaml::SloLogoutrequest.new('')
31
+ assert !logout_request_blank.is_valid?
32
+ assert_includes logout_request_blank.errors, 'Blank logout request'
33
+ end
34
+
35
+ it "return true when the logout request is initialized with valid data" do
36
+ assert logout_request.is_valid?
37
+ assert_empty logout_request.errors
38
+ assert_equal 'someone@example.org', logout_request.nameid
39
+ end
40
+
41
+ it "should be idempotent when the logout request is initialized with invalid data" do
42
+ assert !invalid_logout_request.is_valid?
43
+ assert_equal ['Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd'], invalid_logout_request.errors
44
+ assert !invalid_logout_request.is_valid?
45
+ assert_equal ['Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd'], invalid_logout_request.errors
46
+ end
47
+
48
+ it "should be idempotent when the logout request is initialized with valid data" do
49
+ assert logout_request.is_valid?
50
+ assert_empty logout_request.errors
51
+ assert logout_request.is_valid?
52
+ assert_empty logout_request.errors
53
+ end
54
+
55
+ it "raise error for invalid xml" do
56
+ invalid_logout_request.soft = false
57
+ assert_raises(OneLogin::RubySaml::ValidationError) { invalid_logout_request.is_valid? }
58
+ end
59
+ end
60
+
61
+ describe "#nameid" do
62
+ it "extract the value of the name id element" do
63
+ assert_equal "someone@example.org", logout_request.nameid
64
+ end
65
+ end
66
+
67
+ describe "#issuer" do
68
+ it "return the issuer inside the logout request" do
69
+ assert_equal "https://app.onelogin.com/saml/metadata/SOMEACCOUNT", logout_request.issuer
70
+ end
71
+ end
72
+
73
+ describe "#id" do
74
+ it "extract the value of the ID attribute" do
75
+ assert_equal "_c0348950-935b-0131-1060-782bcb56fcaa", logout_request.id
76
+ end
77
+ end
78
+
79
+ describe "#not_on_or_after" do
80
+ it "extract the value of the NotOnOrAfter attribute" do
81
+ time_value = '2014-07-17T01:01:48Z'
82
+ assert_equal nil, logout_request.not_on_or_after
83
+ logout_request.document.root.attributes['NotOnOrAfter'] = time_value
84
+ assert_equal Time.parse(time_value), logout_request.not_on_or_after
85
+ end
86
+ end
87
+
88
+ describe '#session_indexes' do
89
+ it "return empty array when no SessionIndex" do
90
+ assert_equal [], logout_request.session_indexes
91
+ end
92
+
93
+ it "return an Array with one SessionIndex" do
94
+ logout_request_with_session_index = OneLogin::RubySaml::SloLogoutrequest.new(logout_request_xml_with_session_index)
95
+ assert_equal ['_ea853497-c58a-408a-bc23-c849752d9741'], logout_request_with_session_index.session_indexes
96
+ end
97
+ end
98
+
99
+ describe "#validate_id" do
100
+ it "return true when there is a valid ID in the logout request" do
101
+ assert logout_request.send(:validate_id)
102
+ assert_empty logout_request.errors
103
+ end
104
+
105
+ it "return false when there is an invalid ID in the logout request" do
106
+ logout_request_blank = OneLogin::RubySaml::SloLogoutrequest.new('')
107
+ assert !logout_request_blank.send(:validate_id)
108
+ assert_includes logout_request_blank.errors, "Missing ID attribute on Logout Request"
109
+ end
110
+ end
111
+
112
+ describe "#validate_version" do
113
+ it "return true when the logout request is SAML 2.0 Version" do
114
+ assert logout_request.send(:validate_version)
115
+ end
116
+
117
+ it "return false when the logout request is not SAML 2.0 Version" do
118
+ logout_request_blank = OneLogin::RubySaml::SloLogoutrequest.new('')
119
+ assert !logout_request_blank.send(:validate_version)
120
+ assert_includes logout_request_blank.errors, "Unsupported SAML version"
121
+ end
122
+ end
123
+
124
+ describe "#validate_not_on_or_after" do
125
+ it "return true when the logout request has a valid NotOnOrAfter or does not contain any" do
126
+ assert logout_request.send(:validate_not_on_or_after)
127
+ assert_empty logout_request.errors
128
+ Timecop.freeze Time.parse('2011-06-14T18:25:01.516Z') do
129
+ time_value = '2014-07-17T01:01:48Z'
130
+ logout_request.document.root.attributes['NotOnOrAfter'] = time_value
131
+ assert logout_request.send(:validate_not_on_or_after)
132
+ assert_empty logout_request.errors
133
+ end
134
+ end
135
+
136
+ it "return false when the logout request has an invalid NotOnOrAfter" do
137
+ logout_request.document.root.attributes['NotOnOrAfter'] = '2014-07-17T01:01:48Z'
138
+ assert !logout_request.send(:validate_not_on_or_after)
139
+ assert /Current time is on or after NotOnOrAfter/.match(logout_request.errors[0])
140
+ end
141
+
142
+ it "raise when the logout request has an invalid NotOnOrAfter" do
143
+ logout_request.document.root.attributes['NotOnOrAfter'] = '2014-07-17T01:01:48Z'
144
+ logout_request.soft = false
145
+ assert_raises(OneLogin::RubySaml::ValidationError, "Current time is on or after NotOnOrAfter") do
146
+ logout_request.send(:validate_not_on_or_after)
147
+ end
148
+ end
149
+ end
150
+
151
+ describe "#validate_request_state" do
152
+ it "return true when valid logout request xml" do
153
+ assert logout_request.send(:validate_request_state)
154
+ assert_empty logout_request.errors
155
+ assert logout_request.send(:validate_request_state)
156
+ assert_empty logout_request.errors
157
+ end
158
+
159
+ it "return false when invalid logout request xml" do
160
+ logout_request_blank = OneLogin::RubySaml::SloLogoutrequest.new('')
161
+ logout_request_blank.soft = true
162
+ assert !logout_request_blank.send(:validate_request_state)
163
+ assert_includes logout_request_blank.errors, "Blank logout request"
164
+ end
165
+
166
+ it "raise error for invalid xml" do
167
+ logout_request_blank = OneLogin::RubySaml::SloLogoutrequest.new('')
168
+ logout_request_blank.soft = false
169
+ assert_raises(OneLogin::RubySaml::ValidationError, "Blank logout request") do
170
+ logout_request_blank.send(:validate_request_state)
171
+ end
172
+ end
173
+ end
174
+
175
+ describe "#validate_structure" do
176
+ it "return true when encountering a valid Logout Request xml" do
177
+ assert logout_request.send(:validate_structure)
178
+ assert_empty logout_request.errors
179
+ end
180
+
181
+ it "return false when encountering a Logout Request bad formatted" do
182
+ assert !invalid_logout_request.send(:validate_structure)
183
+ assert_includes invalid_logout_request.errors, "Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd"
184
+ end
185
+
186
+ it "raise when encountering a Logout Request bad formatted" do
187
+ invalid_logout_request.soft = false
188
+ assert_raises(OneLogin::RubySaml::ValidationError, "Element '{urn:oasis:names:tc:SAML:2.0:assertion}Issuer': This element is not expected") do
189
+ invalid_logout_request.send(:validate_structure)
190
+ end
191
+ end
192
+ end
193
+
194
+ describe "#validate_issuer" do
195
+ it "return true when the issuer of the Logout Request matchs the IdP entityId" do
196
+ logout_request.settings.idp_entity_id = 'https://app.onelogin.com/saml/metadata/SOMEACCOUNT'
197
+ assert logout_request.send(:validate_issuer)
198
+ end
199
+ it "return false when the issuer of the Logout Request does not match the IdP entityId" do
200
+ logout_request.settings.idp_entity_id = 'http://idp.example.com/invalid'
201
+ assert !logout_request.send(:validate_issuer)
202
+ assert_includes logout_request.errors, "Doesn't match the issuer, expected: <#{logout_request.settings.idp_entity_id}>, but was: <https://app.onelogin.com/saml/metadata/SOMEACCOUNT>"
203
+ end
204
+ it "raise when the issuer of the Logout Request does not match the IdP entityId" do
205
+ logout_request.settings.idp_entity_id = 'http://idp.example.com/invalid'
206
+ logout_request.soft = false
207
+ assert_raises(OneLogin::RubySaml::ValidationError, "Doesn't match the issuer, expected: <#{logout_request.settings.idp_entity_id}>, but was: <https://app.onelogin.com/saml/metadata/SOMEACCOUNT>") do
208
+ logout_request.send(:validate_issuer)
209
+ end
210
+ end
211
+ end
212
+
213
+ describe "#validate_signature" do
214
+ before do
215
+ settings.idp_slo_target_url = "http://example.com?field=value"
216
+ settings.security[:logout_requests_signed] = true
217
+ settings.security[:embed_sign] = false
218
+ settings.certificate = ruby_saml_cert_text
219
+ settings.private_key = ruby_saml_key_text
220
+ settings.idp_cert = ruby_saml_cert_text
221
+ end
222
+
223
+ it "return true when valid RSA_SHA1 Signature" do
224
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
225
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
226
+ params['RelayState'] = params[:RelayState]
227
+ options = {}
228
+ options[:get_params] = params
229
+ logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options)
230
+ logout_request_sign_test.settings = settings
231
+ assert logout_request_sign_test.send(:validate_signature)
232
+ end
233
+
234
+ it "return true when valid RSA_SHA256 Signature" do
235
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
236
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
237
+ options = {}
238
+ options[:get_params] = params
239
+ logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options)
240
+ params['RelayState'] = params[:RelayState]
241
+ logout_request_sign_test.settings = settings
242
+ assert logout_request_sign_test.send(:validate_signature)
243
+ end
244
+
245
+ it "return false when invalid RSA_SHA1 Signature" do
246
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
247
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
248
+ params['RelayState'] = 'http://invalid.exampcle.com'
249
+ params[:RelayState] = params['RelayState']
250
+ options = {}
251
+ options[:get_params] = params
252
+
253
+ logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options)
254
+ logout_request_sign_test.settings = settings
255
+ assert !logout_request_sign_test.send(:validate_signature)
256
+ end
257
+
258
+ it "raise when invalid RSA_SHA1 Signature" do
259
+ settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
260
+ settings.soft = false
261
+ params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
262
+ params['RelayState'] = 'http://invalid.exampcle.com'
263
+ params[:RelayState] = params['RelayState']
264
+ options = {}
265
+ options[:get_params] = params
266
+ options[:settings] = settings
267
+
268
+ logout_request_sign_test = OneLogin::RubySaml::SloLogoutrequest.new(params['SAMLRequest'], options)
269
+ assert_raises(OneLogin::RubySaml::ValidationError, "Invalid Signature on Logout Request") do
270
+ logout_request_sign_test.send(:validate_signature)
271
+ end
272
+ end
273
+ end
274
+ end
275
+ end