PageRenderTime 51ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/dist/identity-provider/tests/Functional/SAML/SAMLFlowTest.php

https://bitbucket.org/hatim_alam/sugar-8
PHP | 335 lines | 175 code | 47 blank | 113 comment | 7 complexity | b8571e7768b9b29fb54114c2b9df932a MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause, Apache-2.0, MIT, BSD-2-Clause
  1. <?php
  2. /*
  3. * Your installation or use of this SugarCRM file is subject to the applicable
  4. * terms available at
  5. * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
  6. * If you do not agree to all of the applicable terms or do not have the
  7. * authority to bind the entity as an authorized representative, then do not
  8. * install or use this SugarCRM file.
  9. *
  10. * Copyright (C) SugarCRM Inc. All rights reserved.
  11. */
  12. namespace Sugarcrm\IdentityProvider\Tests\Functional\SAML;
  13. use DOMDocument;
  14. use Sugarcrm\IdentityProvider\Tests\Functional\Config;
  15. /**
  16. * Class SAMLFlowTest
  17. *
  18. * Test case to test basic SAML flow (no assertion encryption).
  19. *
  20. * @package Sugarcrm\IdentityProvider\Tests\Functional\SAML
  21. */
  22. abstract class SAMLFlowTest extends AppFlowTest
  23. {
  24. /**
  25. * Service provider URL which SAML authentication starts from.
  26. *
  27. * @var string
  28. */
  29. protected $samlLoginEndpoint;
  30. /**
  31. * Assertion consumer service URL from our SP.
  32. *
  33. * @var string
  34. */
  35. protected $samlAcsEndpoint;
  36. /**
  37. * Service provider URL which SAML logout starts from.
  38. *
  39. * @var string
  40. */
  41. protected $samlLogoutEndpoint;
  42. /**
  43. * Service provider URL where LogoutResponse sent to.
  44. *
  45. * @var string
  46. */
  47. protected $samlLogoutHandlerEndpoint;
  48. /**
  49. * Path to SAML fixtures.
  50. *
  51. * @var string
  52. */
  53. protected $fixturesPath = __DIR__ . '/fixtures';
  54. /**
  55. * Name of provider to test.
  56. *
  57. * @var string
  58. */
  59. private $providerKey;
  60. protected function setUp()
  61. {
  62. parent::setUp();
  63. $this->samlLoginEndpoint = Config::get('SAML_LOGIN_ENDPOINT');
  64. $this->samlAcsEndpoint = Config::get('SAML_ACS_ENDPOINT');
  65. $this->samlLogoutEndpoint = Config::get('SAML_LOGOUT_ENDPOINT');
  66. $this->samlLogoutHandlerEndpoint = Config::get('SAML_LOGOUT_HANDLER_ENDPOINT');
  67. }
  68. /**
  69. * Test to check that SAML login endpoint is redirecting to IdP with proper SAMLRequest.
  70. */
  71. public function testAuthnRequestFromServiceProvider()
  72. {
  73. if ($this->samlLoginEndpoint === false) {
  74. $this->markTestSkipped('You need to configure Login url to execute AuthnRequest tests');
  75. }
  76. $this->webClient->request('GET', $this->samlLoginEndpoint);
  77. $response = $this->webClient->getResponse();
  78. $this->assertEquals(302, $response->getStatusCode());
  79. $location = $response->getTargetUrl();
  80. preg_match_all('/(.*)\?/', $location, $matches);
  81. $redirectUrl = $matches[1][0];
  82. parse_str(parse_url($location, PHP_URL_QUERY), $parameters);
  83. $xml = $this->decodeSAMLAssertion($parameters['SAMLRequest']);
  84. $authnRequestNode = $xml->getElementsByTagName('AuthnRequest')->item(0);
  85. $this->assertEquals(
  86. $redirectUrl,
  87. $authnRequestNode->getAttribute('Destination')
  88. );
  89. $this->assertEquals(
  90. 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
  91. $authnRequestNode->getAttribute('ProtocolBinding')
  92. );
  93. $this->assertEquals(
  94. $this->samlAcsEndpoint,
  95. $authnRequestNode->getAttribute('AssertionConsumerServiceURL')
  96. );
  97. $issuerNode = $xml->getElementsByTagName('Issuer')->item(0);
  98. $this->assertNotNull($issuerNode);
  99. $authnContextClassRefNode = $xml->getElementsByTagName('AuthnContextClassRef')->item(0);
  100. $this->assertNotNull($authnContextClassRefNode);
  101. $this->assertEquals(
  102. 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport',
  103. $authnContextClassRefNode->nodeValue
  104. );
  105. }
  106. /**
  107. * Provides url parameters info for testRelayStateFromServiceProvider
  108. * @return array
  109. **/
  110. public function relayStateFromServiceProviderDataProvider()
  111. {
  112. return [
  113. 'relayStateIsEmpty' => [
  114. 'requestParameters' => [],
  115. 'expectedRelayState' => 'http://localhost:8000/saml/login-end-point',
  116. ],
  117. 'relayStateNotEmpty' => [
  118. 'requestParameters' => ['RelayState' => 'http://test.com/notEmptyRelay'],
  119. 'expectedRelayState' => 'http://test.com/notEmptyRelay',
  120. ],
  121. ];
  122. }
  123. /**
  124. * Test to check that SAML login endpoint is redirecting to IdP with proper relayState parameter.
  125. *
  126. * @param array $requestParameters
  127. * @param string $expectedRelayState
  128. *
  129. * @dataProvider relayStateFromServiceProviderDataProvider
  130. */
  131. public function testRelayStateFromServiceProvider($requestParameters, $expectedRelayState)
  132. {
  133. if ($this->samlLoginEndpoint === false) {
  134. $this->markTestSkipped('You need to configure Login url to execute AuthnRequest tests');
  135. }
  136. $this->webClient->request('GET', $this->samlLoginEndpoint, $requestParameters);
  137. $response = $this->webClient->getResponse();
  138. parse_str(parse_url($response->getTargetUrl(), PHP_URL_QUERY), $parameters);
  139. $this->assertEquals(302, $response->getStatusCode());
  140. $this->assertEquals($expectedRelayState, $parameters['RelayState']);
  141. }
  142. /**
  143. * Test to check that provider responses are processed successfully.
  144. *
  145. * @dataProvider responseProvider
  146. * @param $responseFile
  147. */
  148. public function testResponseFromIdPProcessedSuccessfully($responseFile)
  149. {
  150. if ($this->samlAcsEndpoint === false) {
  151. $this->markTestSkipped('You need to configure ACS url to execute ACS tests');
  152. }
  153. $this->webClient->request(
  154. 'POST',
  155. $this->samlAcsEndpoint,
  156. ['SAMLResponse' => $this->encodeSAMLAssertion(file_get_contents($responseFile))]
  157. );
  158. $this->webClient->followRedirect();
  159. $this->assertContains(
  160. 'User is authenticated successfully',
  161. $this->webClient->getResponse()->getContent()
  162. );
  163. }
  164. /**
  165. * Test to check that service provider really use RelayState and redirects to it.
  166. *
  167. * @dataProvider responseProvider
  168. * @param $responseFile
  169. */
  170. public function testRelayStateIsProcessed($responseFile)
  171. {
  172. if ($this->samlAcsEndpoint === false) {
  173. $this->markTestSkipped('You need to configure ACS url to execute ACS tests');
  174. }
  175. $urls = ['http://test.com', 'http://test.com/', 'http://test.com/foo/bar'];
  176. foreach ($urls as $url) {
  177. $this->webClient->request(
  178. 'POST',
  179. $this->samlAcsEndpoint,
  180. [
  181. 'SAMLResponse' => $this->encodeSAMLAssertion(file_get_contents($responseFile)),
  182. 'RelayState' => $url,
  183. ]
  184. );
  185. $response = $this->webClient->getResponse();
  186. $this->assertEquals(302, $response->getStatusCode());
  187. $this->assertContains($url, $response->getTargetUrl());
  188. }
  189. }
  190. /**
  191. * Test to check that proper logout request is sent.
  192. */
  193. public function testLogoutRequest()
  194. {
  195. if ($this->samlLogoutEndpoint === false) {
  196. $this->markTestSkipped('You need to configure Logout url to execute LogoutRequest tests');
  197. }
  198. $this->webClient->request('GET', $this->samlLogoutEndpoint);
  199. $response = $this->webClient->getResponse();
  200. $this->assertEquals(302, $response->getStatusCode());
  201. $location = $response->getTargetUrl();
  202. preg_match_all('/(.*)\?/', $location, $matches);
  203. $redirectUrl = $matches[1][0];
  204. parse_str(parse_url($location, PHP_URL_QUERY), $parameters);
  205. $xml = $this->decodeSAMLAssertion($parameters['SAMLRequest']);
  206. $logoutRequestNode = $xml->getElementsByTagName('LogoutRequest')->item(0);
  207. $this->assertEquals(
  208. $redirectUrl,
  209. $logoutRequestNode->getAttribute('Destination')
  210. );
  211. $issuerNode = $xml->getElementsByTagName('Issuer')->item(0);
  212. $this->assertNotNull($issuerNode);
  213. }
  214. /**
  215. * Checking behaviour when SAMLRequest or SAMLResponse is not passed.
  216. */
  217. public function testIdpLogoutRequestWithEmptyData()
  218. {
  219. if ($this->samlLogoutHandlerEndpoint === false) {
  220. $this->markTestSkipped('You need to configure Logout Handler url to execute LogoutResponse tests');
  221. }
  222. $this->webClient->request('GET', $this->samlLogoutHandlerEndpoint);
  223. $this->assertContains('Invalid SAML logout data', $this->webClient->getResponse()->getContent());
  224. }
  225. /**
  226. * Test to check that LogoutResponse is handled properly.
  227. *
  228. * @dataProvider logoutResponseProvider
  229. * @param $responseFile string
  230. */
  231. public function testLogoutResponseHandledSuccessfully($responseFile)
  232. {
  233. if ($this->samlLogoutHandlerEndpoint === false) {
  234. $this->markTestSkipped('You need to configure Logout Handler url to execute LogoutResponse tests');
  235. }
  236. $this->webClient->request(
  237. 'GET',
  238. $this->samlLogoutHandlerEndpoint,
  239. ['SAMLResponse' => $this->encodeSAMLAssertion(file_get_contents($responseFile))]
  240. );
  241. $this->webClient->followRedirect();
  242. $this->assertContains('User is logged out', $this->webClient->getResponse()->getContent());
  243. }
  244. /**
  245. * Returns path to files with responses from providers
  246. *
  247. * @return array
  248. */
  249. abstract public function responseProvider();
  250. /**
  251. * Returns path to files with logout responses from providers
  252. *
  253. * @return array
  254. */
  255. abstract public function logoutResponseProvider();
  256. /**
  257. * Method to decode SAML assertion into DOMDocument
  258. *
  259. * @param string $assertion
  260. * @return DOMDocument
  261. */
  262. protected function decodeSAMLAssertion($assertion)
  263. {
  264. $samlAssertion = gzinflate(base64_decode($assertion));
  265. $xml = new DOMDocument();
  266. $xml->loadXML($samlAssertion);
  267. return $xml;
  268. }
  269. /**
  270. * Method to encode SAML assertion to be passed to IdP or SP.
  271. *
  272. * @param string $assertion
  273. * @return string
  274. */
  275. protected function encodeSAMLAssertion($assertion)
  276. {
  277. return base64_encode($assertion);
  278. }
  279. /**
  280. * Sets provider key.
  281. *
  282. * @param string $providerKey
  283. */
  284. protected function setProviderKey($providerKey)
  285. {
  286. $this->providerKey = $providerKey;
  287. }
  288. }