openid_test.module

Dummy OpenID Provider used with SimpleTest.

The provider simply responds positively to all authentication requests. In addition to a Provider Endpoint (a URL used for Drupal to communicate with the provider using the OpenID Authentication protocol) the module provides URLs used by the various discovery mechanisms.

When a user enters an OpenID identity, the Relying Party (in the testing scenario, this is the OpenID module) looks up the URL of the Provider Endpoint using one of several discovery mechanisms. The Relying Party then redirects the user to Provider Endpoint. The provider verifies the user's identity and redirects the user back to the Relying Party accompanied by a signed message confirming the identity. Before redirecting to a provider for the first time, the Relying Party fetches a secret MAC key from the provider by doing a direct "associate" HTTP request to the Provider Endpoint. This key is used for verifying the signed messages from the provider.

File

drupal-7.x/modules/openid/tests/openid_test.module
View source
  1. <?php
  2. /**
  3. * @file
  4. * Dummy OpenID Provider used with SimpleTest.
  5. *
  6. * The provider simply responds positively to all authentication requests. In
  7. * addition to a Provider Endpoint (a URL used for Drupal to communicate with
  8. * the provider using the OpenID Authentication protocol) the module provides
  9. * URLs used by the various discovery mechanisms.
  10. *
  11. * When a user enters an OpenID identity, the Relying Party (in the testing
  12. * scenario, this is the OpenID module) looks up the URL of the Provider
  13. * Endpoint using one of several discovery mechanisms. The Relying Party then
  14. * redirects the user to Provider Endpoint. The provider verifies the user's
  15. * identity and redirects the user back to the Relying Party accompanied by a
  16. * signed message confirming the identity. Before redirecting to a provider for
  17. * the first time, the Relying Party fetches a secret MAC key from the provider
  18. * by doing a direct "associate" HTTP request to the Provider Endpoint. This
  19. * key is used for verifying the signed messages from the provider.
  20. */
  21. /**
  22. * Implements hook_menu().
  23. */
  24. function openid_test_menu() {
  25. $items['openid-test/yadis/xrds'] = array(
  26. 'title' => 'XRDS service document',
  27. 'page callback' => 'openid_test_yadis_xrds',
  28. 'access callback' => TRUE,
  29. 'type' => MENU_CALLBACK,
  30. );
  31. $items['openid-test/yadis/x-xrds-location'] = array(
  32. 'title' => 'Yadis discovery using X-XRDS-Location header',
  33. 'page callback' => 'openid_test_yadis_x_xrds_location',
  34. 'access callback' => TRUE,
  35. 'type' => MENU_CALLBACK,
  36. );
  37. $items['openid-test/yadis/http-equiv'] = array(
  38. 'title' => 'Yadis discovery using <meta http-equiv="X-XRDS-Location" ...>',
  39. 'page callback' => 'openid_test_yadis_http_equiv',
  40. 'access callback' => TRUE,
  41. 'type' => MENU_CALLBACK,
  42. );
  43. $items['openid-test/html/openid1'] = array(
  44. 'title' => 'HTML-based discovery using <link rel="openid.server" ...>',
  45. 'page callback' => 'openid_test_html_openid1',
  46. 'access callback' => TRUE,
  47. 'type' => MENU_CALLBACK,
  48. );
  49. $items['openid-test/html/openid2'] = array(
  50. 'title' => 'HTML-based discovery using <link rel="openid2.provider" ...>',
  51. 'page callback' => 'openid_test_html_openid2',
  52. 'access callback' => TRUE,
  53. 'type' => MENU_CALLBACK,
  54. );
  55. $items['openid-test/endpoint'] = array(
  56. 'title' => 'OpenID Provider Endpoint',
  57. 'page callback' => 'openid_test_endpoint',
  58. 'access callback' => TRUE,
  59. 'type' => MENU_CALLBACK,
  60. );
  61. $items['openid-test/redirect'] = array(
  62. 'title' => 'OpenID Provider Redirection Point',
  63. 'page callback' => 'openid_test_redirect',
  64. 'access callback' => TRUE,
  65. 'type' => MENU_CALLBACK,
  66. );
  67. $items['openid-test/redirected/%/%'] = array(
  68. 'title' => 'OpenID Provider Final URL',
  69. 'page callback' => 'openid_test_redirected_method',
  70. 'page arguments' => array(2, 3),
  71. 'access callback' => TRUE,
  72. 'type' => MENU_CALLBACK,
  73. );
  74. return $items;
  75. }
  76. /**
  77. * Implements hook_menu_site_status_alter().
  78. */
  79. function openid_test_menu_site_status_alter(&$menu_site_status, $path) {
  80. // Allow access to openid endpoint and identity even in offline mode.
  81. if ($menu_site_status == MENU_SITE_OFFLINE && user_is_anonymous() && in_array($path, array('openid-test/yadis/xrds', 'openid-test/endpoint'))) {
  82. $menu_site_status = MENU_SITE_ONLINE;
  83. }
  84. }
  85. /**
  86. * Menu callback; XRDS document that references the OP Endpoint URL.
  87. */
  88. function openid_test_yadis_xrds() {
  89. if ($_SERVER['HTTP_ACCEPT'] == 'application/xrds+xml') {
  90. // Only respond to XRI requests for one specific XRI. The is used to verify
  91. // that the XRI has been properly encoded. The "+" sign in the _xrd_r query
  92. // parameter is decoded to a space by PHP.
  93. if (arg(3) == 'xri') {
  94. if (variable_get('clean_url', 0)) {
  95. if (arg(4) != '@example*résumé;%25' || $_GET['_xrd_r'] != 'application/xrds xml') {
  96. drupal_not_found();
  97. }
  98. }
  99. else {
  100. // Drupal cannot properly emulate an XRI proxy resolver using unclean
  101. // URLs, so the arguments gets messed up.
  102. if (arg(4) . '/' . arg(5) != '@example*résumé;%25?_xrd_r=application/xrds xml') {
  103. drupal_not_found();
  104. }
  105. }
  106. }
  107. drupal_add_http_header('Content-Type', 'application/xrds+xml');
  108. print '<?xml version="1.0" encoding="UTF-8"?>';
  109. if (!empty($_GET['doctype'])) {
  110. print "\n<!DOCTYPE dct [ <!ELEMENT blue (#PCDATA)> ]>\n";
  111. }
  112. print '
  113. <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)" xmlns:openid="http://openid.net/xmlns/1.0">
  114. <XRD>
  115. <Status cid="' . check_plain(variable_get('openid_test_canonical_id_status', 'verified')) . '"/>
  116. <ProviderID>xri://@</ProviderID>
  117. <CanonicalID>http://example.com/user</CanonicalID>
  118. <Service>
  119. <Type>http://example.com/this-is-ignored</Type>
  120. </Service>
  121. <Service priority="5">
  122. <Type>http://openid.net/signon/1.0</Type>
  123. <URI>http://example.com/this-is-only-openid-1.0</URI>
  124. </Service>
  125. <Service priority="10">
  126. <Type>http://specs.openid.net/auth/2.0/signon</Type>
  127. <Type>http://openid.net/srv/ax/1.0</Type>
  128. <URI>' . url('openid-test/endpoint', array('absolute' => TRUE)) . '</URI>
  129. <LocalID>http://example.com/xrds</LocalID>
  130. </Service>
  131. <Service priority="15">
  132. <Type>http://specs.openid.net/auth/2.0/signon</Type>
  133. <URI>http://example.com/this-has-too-low-priority</URI>
  134. </Service>
  135. <Service>
  136. <Type>http://specs.openid.net/auth/2.0/signon</Type>
  137. <URI>http://example.com/this-has-too-low-priority</URI>
  138. </Service>
  139. ';
  140. if (arg(3) == 'server') {
  141. print '
  142. <Service>
  143. <Type>http://specs.openid.net/auth/2.0/server</Type>
  144. <URI>http://example.com/this-has-too-low-priority</URI>
  145. </Service>
  146. <Service priority="20">
  147. <Type>http://specs.openid.net/auth/2.0/server</Type>
  148. <URI>' . url('openid-test/endpoint', array('absolute' => TRUE)) . '</URI>
  149. </Service>';
  150. }
  151. elseif (arg(3) == 'delegate') {
  152. print '
  153. <Service priority="0">
  154. <Type>http://specs.openid.net/auth/2.0/signon</Type>
  155. <Type>http://openid.net/srv/ax/1.0</Type>
  156. <URI>' . url('openid-test/endpoint', array('absolute' => TRUE)) . '</URI>
  157. <openid:Delegate>http://example.com/xrds-delegate</openid:Delegate>
  158. </Service>';
  159. }
  160. print '
  161. </XRD>
  162. </xrds:XRDS>';
  163. }
  164. else {
  165. return t('This is a regular HTML page. If the client sends an Accept: application/xrds+xml header when requesting this URL, an XRDS document is returned.');
  166. }
  167. }
  168. /**
  169. * Menu callback; regular HTML page with an X-XRDS-Location HTTP header.
  170. */
  171. function openid_test_yadis_x_xrds_location() {
  172. drupal_add_http_header('X-XRDS-Location', url('openid-test/yadis/xrds', array('absolute' => TRUE)));
  173. return t('This page includes an X-RDS-Location HTTP header containing the URL of an XRDS document.');
  174. }
  175. /**
  176. * Menu callback; regular HTML page with <meta> element.
  177. */
  178. function openid_test_yadis_http_equiv() {
  179. $element = array(
  180. '#tag' => 'meta',
  181. '#attributes' => array(
  182. 'http-equiv' => 'X-XRDS-Location',
  183. 'content' => url('openid-test/yadis/xrds', array('absolute' => TRUE)),
  184. ),
  185. );
  186. drupal_add_html_head($element, 'openid_test_yadis_http_equiv');
  187. return t('This page includes a &lt;meta equiv=...&gt; element containing the URL of an XRDS document.');
  188. }
  189. /**
  190. * Menu callback; regular HTML page with OpenID 1.0 <link> element.
  191. */
  192. function openid_test_html_openid1() {
  193. drupal_add_html_head_link(array('rel' => 'openid.server', 'href' => url('openid-test/endpoint', array('absolute' => TRUE))));
  194. drupal_add_html_head_link(array('rel' => 'openid.delegate', 'href' => 'http://example.com/html-openid1'));
  195. return t('This page includes a &lt;link rel=...&gt; element containing the URL of an OpenID Provider Endpoint.');
  196. }
  197. /**
  198. * Menu callback; regular HTML page with OpenID 2.0 <link> element.
  199. */
  200. function openid_test_html_openid2() {
  201. drupal_add_html_head_link(array('rel' => 'openid2.provider', 'href' => url('openid-test/endpoint', array('absolute' => TRUE))));
  202. drupal_add_html_head_link(array('rel' => 'openid2.local_id', 'href' => 'http://example.com/html-openid2'));
  203. return t('This page includes a &lt;link rel=...&gt; element containing the URL of an OpenID Provider Endpoint.');
  204. }
  205. /**
  206. * Menu callback; OpenID Provider Endpoint.
  207. *
  208. * It accepts "associate" requests directly from the Relying Party, and
  209. * "checkid_setup" requests made by the user's browser based on HTTP redirects
  210. * (in OpenID 1) or HTML forms (in OpenID 2) generated by the Relying Party.
  211. */
  212. function openid_test_endpoint() {
  213. switch ($_REQUEST['openid_mode']) {
  214. case 'associate':
  215. _openid_test_endpoint_associate();
  216. break;
  217. case 'checkid_setup':
  218. _openid_test_endpoint_authenticate();
  219. break;
  220. }
  221. }
  222. /**
  223. * Menu callback; redirect during Normalization/Discovery.
  224. */
  225. function openid_test_redirect($count = 0) {
  226. if ($count == 0) {
  227. $url = variable_get('openid_test_redirect_url', '');
  228. }
  229. else {
  230. $url = url('openid-test/redirect/' . --$count, array('absolute' => TRUE));
  231. }
  232. $http_response_code = variable_get('openid_test_redirect_http_reponse_code', 301);
  233. header('Location: ' . $url, TRUE, $http_response_code);
  234. exit();
  235. }
  236. /**
  237. * Menu callback; respond with appropriate callback.
  238. */
  239. function openid_test_redirected_method($method1, $method2) {
  240. return call_user_func('openid_test_' . $method1 . '_' . $method2);
  241. }
  242. /**
  243. * OpenID endpoint; handle "associate" requests (see OpenID Authentication 2.0,
  244. * section 8).
  245. *
  246. * The purpose of association is to send the secret MAC key to the Relying Party
  247. * using Diffie-Hellman key exchange. The MAC key is used in subsequent
  248. * "authenticate" requests. The "associate" request is made by the Relying Party
  249. * (in the testing scenario, this is the OpenID module that communicates with
  250. * the endpoint using drupal_http_request()).
  251. */
  252. function _openid_test_endpoint_associate() {
  253. module_load_include('inc', 'openid');
  254. // Use default parameters for Diffie-Helmann key exchange.
  255. $mod = OPENID_DH_DEFAULT_MOD;
  256. $gen = OPENID_DH_DEFAULT_GEN;
  257. // Generate private Diffie-Helmann key.
  258. $r = _openid_dh_rand($mod);
  259. $private = _openid_math_add($r, 1);
  260. // Calculate public Diffie-Helmann key.
  261. $public = _openid_math_powmod($gen, $private, $mod);
  262. // Calculate shared secret based on Relying Party's public key.
  263. $cpub = _openid_dh_base64_to_long($_REQUEST['openid_dh_consumer_public']);
  264. $shared = _openid_math_powmod($cpub, $private, $mod);
  265. // Encrypt the MAC key using the shared secret.
  266. $enc_mac_key = base64_encode(_openid_dh_xorsecret($shared, base64_decode(variable_get('mac_key'))));
  267. // Generate response including our public key and the MAC key. Using our
  268. // public key and its own private key, the Relying Party can calculate the
  269. // shared secret, and with this it can decrypt the encrypted MAC key.
  270. $response = array(
  271. 'ns' => 'http://specs.openid.net/auth/2.0',
  272. 'assoc_handle' => 'openid-test',
  273. 'session_type' => $_REQUEST['openid_session_type'],
  274. 'assoc_type' => $_REQUEST['openid_assoc_type'],
  275. 'expires_in' => '3600',
  276. 'dh_server_public' => _openid_dh_long_to_base64($public),
  277. 'enc_mac_key' => $enc_mac_key,
  278. );
  279. // Respond to Relying Party in the special Key-Value Form Encoding (see OpenID
  280. // Authentication 1.0, section 4.1.1).
  281. drupal_add_http_header('Content-Type', 'text/plain');
  282. print _openid_create_message($response);
  283. }
  284. /**
  285. * OpenID endpoint; handle "authenticate" requests.
  286. *
  287. * All requests result in a successful response. The request is a GET or POST
  288. * made by the user's browser based on an HTML form or HTTP redirect generated
  289. * by the Relying Party. The user is redirected back to the Relying Party using
  290. * a URL containing a signed message in the query string confirming the user's
  291. * identity.
  292. */
  293. function _openid_test_endpoint_authenticate() {
  294. module_load_include('inc', 'openid');
  295. $expected_identity = variable_get('openid_test_identity');
  296. if ($expected_identity && $_REQUEST['openid_identity'] != $expected_identity) {
  297. $response = variable_get('openid_test_response', array()) + array(
  298. 'openid.ns' => OPENID_NS_2_0,
  299. 'openid.mode' => 'error',
  300. 'openid.error' => 'Unexpted identity',
  301. );
  302. drupal_add_http_header('Content-Type', 'text/plain');
  303. header('Location: ' . url($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE)));
  304. return;
  305. }
  306. // Generate unique identifier for this authentication.
  307. $nonce = _openid_nonce();
  308. // Generate response containing the user's identity.
  309. $response = variable_get('openid_test_response', array()) + array(
  310. 'openid.ns' => OPENID_NS_2_0,
  311. 'openid.mode' => 'id_res',
  312. 'openid.op_endpoint' => url('openid-test/endpoint', array('absolute' => TRUE)),
  313. 'openid.claimed_id' => !empty($_REQUEST['openid_claimed_id']) ? $_REQUEST['openid_claimed_id'] : '',
  314. 'openid.identity' => $_REQUEST['openid_identity'],
  315. 'openid.return_to' => $_REQUEST['openid_return_to'],
  316. 'openid.response_nonce' => $nonce,
  317. 'openid.assoc_handle' => 'openid-test',
  318. );
  319. if (isset($response['openid.signed'])) {
  320. $keys_to_sign = explode(',', $response['openid.signed']);
  321. }
  322. else {
  323. // Unless openid.signed is explicitly defined, all keys are signed.
  324. $keys_to_sign = array();
  325. foreach ($response as $key => $value) {
  326. // Strip off the "openid." prefix.
  327. $keys_to_sign[] = substr($key, 7);
  328. }
  329. $response['openid.signed'] = implode(',', $keys_to_sign);
  330. }
  331. // Sign the message using the MAC key that was exchanged during association.
  332. $association = new stdClass();
  333. $association->mac_key = variable_get('mac_key');
  334. if (!isset($response['openid.sig'])) {
  335. $response['openid.sig'] = _openid_signature($association, $response, $keys_to_sign);
  336. }
  337. // Put the signed message into the query string of a URL supplied by the
  338. // Relying Party, and redirect the user.
  339. drupal_add_http_header('Content-Type', 'text/plain');
  340. header('Location: ' . url($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE)));
  341. }