bakery.module

  1. cis7 sites/all/modules/ulmus/bakery/bakery.module
  2. cle7 sites/all/modules/ulmus/bakery/bakery.module
  3. ecd7 sites/all/modules/ulmus/bakery/bakery.module
  4. elmsmedia7 sites/all/modules/ulmus/bakery/bakery.module
  5. harmony7 sites/all/modules/ulmus/bakery/bakery.module
  6. icor7 sites/all/modules/ulmus/bakery/bakery.module
  7. meedjum_blog7 sites/all/modules/ulmus/bakery/bakery.module
  8. mooc7 sites/all/modules/ulmus/bakery/bakery.module

Functions

Namesort descending Description
bakery_bake_data Encrypt and sign data for Bakery transfer.
bakery_bake_oatmeal_cookie Create a cookie for passing information between sites for registration and login.
bakery_boot Implements hook_boot().
bakery_decrypt Decryption handler.
bakery_eat_gingerbread_cookie Respond with account information.
bakery_eat_stroopwafel_cookie Menu callback, invoked on the slave
bakery_eat_thinmint_cookie Update the user's login time to reflect them validating their email address.
bakery_encrypt Encryption handler.
bakery_form_alter Implements hook_form_alter().
bakery_login Special Bakery login callback authenticates the user and returns to slave.
bakery_login_return Custom return for errors during slave login process.
bakery_menu Implements hook_menu().
bakery_permission Implements hook_permission().
bakery_pull_form Form for admins to pull accounts.
bakery_pull_form_submit If the request succeeds, go to the user page. Otherwise, show an error.
bakery_pull_form_validate Make sure we are not trying to request an existing user.
bakery_register Special Bakery register callback registers the user and returns to slave.
bakery_register_return Custom return for slave registration process.
bakery_request_account Request account information from master to create account locally.
bakery_settings Admin settings, see INSTALL.txt
bakery_settings_submit
bakery_taste_gingerbread_cookie Validate the account information request.
bakery_taste_oatmeal_cookie
bakery_taste_stroopwafel_cookie Validate update request.
bakery_taste_thinmint_cookie Verify the validation request.
bakery_translated_menu_link_alter Implements hook_translated_menu_link_alter().
bakery_uncrumble Form to let users repair minor problems themselves.
bakery_uncrumble_access Only let people with actual problems mess with uncrumble.
bakery_uncrumble_submit
bakery_uncrumble_validate Validation for bakery_uncrumble form.
bakery_user_authenticate_finalize Finalize the login process. Must be called when logging in a user.
bakery_user_external_login Perform standard Drupal login operations for a user object.
bakery_user_login Implements hook_user_login().
bakery_user_logout Implements hook_user_logout().
bakery_user_page Access callback for path /user.
bakery_user_presave Implements hook_user_presave().
bakery_user_update Implements hook_user_update().
bakery_user_view Implements hook_user_view().
bakery_validate_data Validate signature and decrypt data.
_bakery_bake_chocolatechip_cookie Create a new cookie for identification
_bakery_cookie_name Name for cookie including session.cookie_secure and variable extension.
_bakery_eat_cookie Destroy unwanted cookies
_bakery_init_field Build internal init url (without scheme).
_bakery_init_field_url Build full init url to master.
_bakery_login_submit Handle login by redirecting to master.
_bakery_pass_validate Validate handler for the password reset login.
_bakery_register_submit Handle registration by redirecting to master.
_bakery_reset_submit Submit handler for the password reset form.
_bakery_save_destination_param Check if a form destination is set and save it in $data array.
_bakery_save_slave_uid Save UID provided by a slave site. Should only be used on the master site.
_bakery_taste_chocolatechip_cookie Test identification cookie
_bakery_user_logout Custom logout function modified from user_logout.
_bakery_validate_cookie Function to validate cookies

File

sites/all/modules/ulmus/bakery/bakery.module
View source
  1. <?php
  2. /**
  3. * Implements hook_menu().
  4. */
  5. function bakery_menu() {
  6. $items = array();
  7. $items['admin/config/system/bakery'] = array(
  8. 'title' => 'Bakery',
  9. 'access arguments' => array('administer bakery'),
  10. 'page callback' => 'drupal_get_form',
  11. 'page arguments' => array('bakery_settings'),
  12. 'description' => 'Infrastructure-wide single-sign-on system options.',
  13. );
  14. if (variable_get('bakery_is_master', 0)) {
  15. $items['bakery'] = array(
  16. 'title' => 'Register',
  17. 'access callback' => 'user_is_anonymous',
  18. 'page callback' => 'bakery_register',
  19. 'type' => MENU_CALLBACK,
  20. );
  21. $items['bakery/login'] = array(
  22. 'title' => 'Login',
  23. 'access callback' => 'user_is_anonymous',
  24. 'page callback' => 'bakery_login',
  25. 'type' => MENU_CALLBACK,
  26. );
  27. $items['bakery/validate'] = array(
  28. 'title' => 'Validate',
  29. 'access callback' => 'bakery_taste_thinmint_cookie',
  30. 'page callback' => 'bakery_eat_thinmint_cookie',
  31. 'type' => MENU_CALLBACK,
  32. );
  33. $items['bakery/create'] = array(
  34. 'title' => 'Bakery create',
  35. 'access callback' => 'bakery_taste_gingerbread_cookie',
  36. 'page callback' => 'bakery_eat_gingerbread_cookie',
  37. 'type' => MENU_CALLBACK,
  38. );
  39. }
  40. else {
  41. $items['bakery'] = array(
  42. 'title' => 'Register',
  43. 'access callback' => TRUE,
  44. 'page callback' => 'bakery_register_return',
  45. 'type' => MENU_CALLBACK,
  46. );
  47. $items['bakery/login'] = array(
  48. 'title' => 'Login',
  49. 'access callback' => TRUE,
  50. 'page callback' => 'bakery_login_return',
  51. 'type' => MENU_CALLBACK,
  52. );
  53. $items['bakery/update'] = array(
  54. 'title' => 'Update',
  55. 'access callback' => 'bakery_taste_stroopwafel_cookie',
  56. 'page callback' => 'bakery_eat_stroopwafel_cookie',
  57. 'type' => MENU_CALLBACK,
  58. );
  59. $items['bakery/repair'] = array(
  60. 'title' => 'Repair account',
  61. 'access callback' => 'bakery_uncrumble_access',
  62. 'page callback' => 'drupal_get_form',
  63. 'page arguments' => array('bakery_uncrumble'),
  64. 'type' => MENU_CALLBACK,
  65. );
  66. $items['admin/config/people/bakery'] = array(
  67. 'title' => 'Pull Bakery user',
  68. 'description' => 'Request an account from the master site',
  69. 'access arguments' => array('administer users'),
  70. 'page callback' => 'drupal_get_form',
  71. 'page arguments' => array('bakery_pull_form'),
  72. 'type' => MENU_NORMAL_ITEM,
  73. );
  74. }
  75. return $items;
  76. }
  77. /**
  78. * Implements hook_translated_menu_link_alter().
  79. */
  80. function bakery_translated_menu_link_alter(&$item, $map) {
  81. if ($item['href'] == 'bakery') {
  82. $destination = drupal_get_destination();
  83. $item['localized_options']['query'] = $destination;
  84. }
  85. }
  86. /**
  87. * Implements hook_permission().
  88. */
  89. function bakery_permission() {
  90. return array(
  91. 'administer bakery' => array(
  92. 'title' => t('Administer Bakery'),
  93. ),
  94. 'bypass bakery' => array(
  95. 'title' => t('Bypass Bakery'),
  96. 'description' => t('Bypass SSO enforcement policy and allow a user to log in without a valid SSO cookie'),
  97. 'restrict access' => TRUE,
  98. ),
  99. );
  100. }
  101. /**
  102. * Implements hook_user_login().
  103. */
  104. function bakery_user_login(&$edit, $account) {
  105. if (variable_get('bakery_is_master', 0) && isset($account->uid)) {
  106. $init = _bakery_init_field($account->uid);
  107. _bakery_bake_chocolatechip_cookie($account->name, $account->mail, $init);
  108. }
  109. }
  110. /**
  111. * Implements hook_user_logout().
  112. */
  113. function bakery_user_logout($account) {
  114. global $user;
  115. $cookie = _bakery_validate_cookie();
  116. // Only delete the SSO cookie if the name is the same in case there was an
  117. // existing session that's being logged out and SSO cookie is for new session.
  118. if ($user->uid && $cookie && $cookie['name'] === $user->name) {
  119. _bakery_eat_cookie();
  120. }
  121. // Destroy session cookie.
  122. _bakery_eat_cookie(session_name());
  123. }
  124. /**
  125. * Implements hook_user_presave().
  126. */
  127. function bakery_user_presave(&$edit, $account, $category) {
  128. if (variable_get('bakery_is_master', 0)) {
  129. // Invoke implementations of hook_bakery_transmit() for syncing arbitrary
  130. // data.
  131. $_SESSION['bakery']['data'] = module_invoke_all('bakery_transmit', $edit, $account, $category);
  132. // We store email/name if they changed. We want to wait with doing
  133. // anything else until the changes are saved locally.
  134. foreach (variable_get('bakery_supported_fields', array('mail' => 'mail', 'name' => 'name')) as $type => $enabled) {
  135. if ($enabled && isset($edit[$type]) && isset($account->$type) && $account->$type != $edit[$type]) {
  136. $_SESSION['bakery'][$type] = $edit[$type];
  137. }
  138. }
  139. }
  140. }
  141. /**
  142. * Implements hook_user_update().
  143. */
  144. function bakery_user_update(&$edit, $account, $category) {
  145. global $user;
  146. // We need to push changes.
  147. if (variable_get('bakery_is_master', 0) && isset($_SESSION['bakery'])) {
  148. $type = 'stroopwafel';
  149. $key = variable_get('bakery_key', '');
  150. $payload['data'] = serialize($_SESSION['bakery']);
  151. $payload['timestamp'] = $_SERVER['REQUEST_TIME'];
  152. $payload['uid'] = $account->uid;
  153. $payload['category'] = $category;
  154. $payload['type'] = $type;
  155. $data = bakery_bake_data($payload);
  156. // Respond with encrypted and signed account information.
  157. $payload = drupal_http_build_query(array($type => $data));
  158. unset($_SESSION['bakery']);
  159. // now update the slaves
  160. $slaves = variable_get('bakery_slaves', array());
  161. foreach ($slaves as $slave) {
  162. $options = array(
  163. 'headers' => array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'),
  164. 'method' => 'POST',
  165. 'data' => $payload,
  166. );
  167. $result = drupal_http_request($slave .'bakery/update', $options);
  168. if ($result->code != 200) {
  169. drupal_set_message(t('Error %error for site at %url', array('%error' => $result->code .' '. $result->error, '%url' => $slave)));
  170. }
  171. else {
  172. drupal_set_message($result->data);
  173. // TODO: Roll back the change.
  174. }
  175. }
  176. if ($user->uid === $account->uid) {
  177. // Rebake SSO cookie so user stays authenticated.
  178. $init = _bakery_init_field($account->uid);
  179. _bakery_bake_chocolatechip_cookie($account->name, $account->mail, $init);
  180. }
  181. }
  182. }
  183. /**
  184. * Implements hook_user_view().
  185. */
  186. function bakery_user_view($account, $view_mode, $langcode) {
  187. if (!variable_get('bakery_is_master', 0)) {
  188. $master = variable_get('bakery_master', 'http://drupal.org/');
  189. $init_url = _bakery_init_field_url($account->init);
  190. if (parse_url($master, PHP_URL_HOST) == parse_url($init_url, PHP_URL_HOST)) {
  191. $account->content['summary']['master_profile'] = array(
  192. '#type' => 'user_profile_item',
  193. '#title' => t('Primary profile'),
  194. '#markup' => l(t('Profile on primary site'), substr($init_url, 0, strlen($init_url) - 5)), // Take everything up to '/edit'.
  195. '#access' => user_access('access user profiles'),
  196. );
  197. }
  198. }
  199. }
  200. /**
  201. * Implements hook_boot().
  202. */
  203. function bakery_boot() {
  204. _bakery_taste_chocolatechip_cookie();
  205. }
  206. /**
  207. * Implements hook_form_alter().
  208. *
  209. */
  210. function bakery_form_alter(&$form, $form_state, $form_id) {
  211. switch ($form_id) {
  212. case 'user_profile_form':
  213. case 'user_edit_form':
  214. if (!variable_get('bakery_is_master', 0) && !user_access('administer users')) {
  215. $init_url = _bakery_init_field_url($form['#user']->init);
  216. $index = key($form);
  217. if (isset($form['account'])) {
  218. drupal_set_message(t('You can change the name, mail, and password <a href="!url">at the master site</a>.', array('!url' => check_url($init_url))), 'status', FALSE);
  219. $form['account']['#access'] = FALSE;
  220. $form['account']['name']['#access'] = FALSE;
  221. $form['account']['pass']['#access'] = FALSE;
  222. $form['account']['mail']['#access'] = FALSE;
  223. }
  224. foreach (variable_get('bakery_supported_fields', array('mail' => 'mail', 'name' => 'name')) as $type => $value) {
  225. if ($value) {
  226. switch($type) {
  227. case 'mail':
  228. case 'name':
  229. break;
  230. case 'picture':
  231. if (isset($form['picture'])) {
  232. $form['picture']['picture_delete']['#access'] = FALSE;
  233. $form['picture']['picture_upload']['#access'] = FALSE;
  234. $form['picture']['#description'] = t('You can change the image <a href="!url">at the master site</a>.', array('!url' => check_url($init_url)));
  235. }
  236. break;
  237. case 'language':
  238. if (isset($form['locale'][$type])) {
  239. $form['locale'][$type]['#disabled'] = TRUE;
  240. $form['locale'][$type]['#description'] .= ' '. t('You can change the language setting <a href="!url">at the master site</a>.', array('!url' => check_url($init_url)));
  241. }
  242. break;
  243. case 'signature':
  244. if (isset($form['signature_settings'][$type])) {
  245. $form['signature_settings'][$type]['#disabled'] = TRUE;
  246. $form['signature_settings'][$type]['#description'] .= ' '. t('You can change the signature <a href="!url">at the master site</a>.', array('!url' => check_url($init_url)));
  247. }
  248. break;
  249. default:
  250. if (isset($form[$type])) {
  251. $form[$type]['#disabled'] = TRUE;
  252. }
  253. if (isset($form[$type][$type])) {
  254. $form[$type][$type]['#disabled'] = TRUE;
  255. $form[$type][$type]['#description'] .= ' '. t('You can change this setting <a href="!url">at the master site</a>.', array('!url' => check_url($init_url)));
  256. }
  257. break;
  258. }
  259. }
  260. }
  261. }
  262. break;
  263. case 'user_register_form':
  264. // Provide register ability on the slave sites.
  265. if (!variable_get('bakery_is_master', FALSE)) {
  266. if (arg(0) == 'admin') {
  267. // Admin create user form. Add a note about account synchronization.
  268. $form['account']['bakery_help'] = array(
  269. '#value' => t('<strong>Note:</strong> Only use this form to create accounts for users who exist on <a href="!url">@master</a> and not on this site. Be sure to use the exact same username and e-mail for the account here that they have on @master.', array('!url' => variable_get('bakery_master', 'http://drupal.org'), '@master' => variable_get('bakery_master', 'http://drupal.org'))),
  270. '#weight' => -100,
  271. );
  272. }
  273. else {
  274. // Anonymous user registration form.
  275. // Populate fields if set from previous attempt.
  276. if (isset($_SESSION['bakery']['register'])) {
  277. $form['account']['name']['#default_value'] = $_SESSION['bakery']['register']['name'];
  278. $form['account']['mail']['#default_value'] = $_SESSION['bakery']['register']['mail'];
  279. unset($_SESSION['bakery']['register']);
  280. }
  281. // Replace the submit handler with our own.
  282. $form['#submit'] = array('_bakery_register_submit');
  283. }
  284. }
  285. break;
  286. case 'user_pass':
  287. // Slave sites need to make sure the local account exists, if the master
  288. // account exists.
  289. if (!variable_get('bakery_is_master', FALSE)) {
  290. array_unshift($form['#validate'], '_bakery_pass_validate');
  291. }
  292. break;
  293. case 'user_pass_reset':
  294. // As part of the slave site registration we need to handle email
  295. // validation and password reset.
  296. if (!variable_get('bakery_is_master', FALSE)) {
  297. // Set a submit handler for the psuedo-reset form.
  298. $form['#submit'] = array('_bakery_reset_submit');
  299. // Unset its custom action.
  300. unset($form['#action']);
  301. }
  302. break;
  303. case 'user_login_block':
  304. case 'user_login':
  305. // Provide login ability on the slave sites.
  306. if (!variable_get('bakery_is_master', FALSE)) {
  307. // Replace two validators from user module because they log the user in
  308. // and test if account exists. We want to check if the account exists on
  309. // the master instead.
  310. $form['#validate'] = array_diff($form['#validate'], array('user_login_authenticate_validate', 'user_login_final_validate'));
  311. // Also replace the submit handler with our own to set a redirect cookie.
  312. $form['#submit'] = array('_bakery_login_submit');
  313. }
  314. break;
  315. default:
  316. break;
  317. }
  318. }
  319. /**
  320. * Validate handler for the password reset login.
  321. */
  322. function _bakery_pass_validate($form, &$form_state) {
  323. // On a slave site it's possible that a user requests their password but
  324. // doesn't have an account on the slave site. So, we check if that's the case
  325. // and use our helpful functions to create their account on the slave site.
  326. $name = trim($form_state['values']['name']);
  327. $account = user_load_by_mail($name);
  328. if (!$account) {
  329. // No success, try to load by name.
  330. $account = user_load_by_name($name);
  331. }
  332. if (!$account) {
  333. // Attempt to copy account from master.
  334. bakery_request_account($name, TRUE);
  335. }
  336. }
  337. /**
  338. * Submit handler for the password reset form.
  339. */
  340. function _bakery_reset_submit($form, &$form_state) {
  341. global $base_url;
  342. // If we're here it means the user has validated their email correctly.
  343. $master = variable_get('bakery_master', 'http://drupal.org/');
  344. $key = variable_get('bakery_key', '');
  345. // It's safe to use arg(2) here to load the user and log in because the
  346. // callback has validated the request and Drupal's Form API protects us
  347. // against forgery.
  348. $account = user_load(arg(2));
  349. // If they have not logged in before we need to update the master site.
  350. if ($account->login == 0) {
  351. $type = 'thinmint';
  352. $payload = array();
  353. $payload['name'] = $account->name;
  354. $payload['slave'] = rtrim($base_url, '/') . '/'; // Match how slaves are set on the master.
  355. $payload['uid'] = $account->uid;
  356. $payload['timestamp'] = $_SERVER['REQUEST_TIME'];
  357. $payload['type'] = $type;
  358. $data = bakery_bake_data($payload);
  359. $payload = drupal_http_build_query(array($type => $data));
  360. // Push validation to master.
  361. $http_options = array('method' => 'POST', 'data' => $payload, 'headers' => array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'));
  362. $result = drupal_http_request($master . 'bakery/validate', $http_options);
  363. }
  364. // If they have logged in before or the master updated correctly, log them in.
  365. if ($account->login > 0 || $result->code == 200) {
  366. // Log the user in.
  367. $init = _bakery_init_field($account->uid);
  368. _bakery_bake_chocolatechip_cookie($account->name, $account->mail, $init);
  369. global $user;
  370. $user = $account;
  371. $edit = array('name' => $user->name);
  372. bakery_user_authenticate_finalize($edit);
  373. // Inform them that they need to reset their password.
  374. drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to login. Please change your password at <a href="!url">@master</a>.', array('!url' => check_url(_bakery_init_field_url($user->init)), '@master' => variable_get('bakery_master', ''))));
  375. drupal_goto('user/'. $user->uid);
  376. }
  377. else {
  378. drupal_goto('user/login');
  379. }
  380. }
  381. /**
  382. * Check if a form destination is set and save it in $data array.
  383. *
  384. * Used to preserve destination in Bakery redirection to master and slave
  385. * during login and registration.
  386. *
  387. * @see drupal_goto()
  388. *
  389. * @param $form
  390. * Form definition to check.
  391. * @param $data
  392. * Array to store the detected destination value, if any.
  393. */
  394. function _bakery_save_destination_param($form, &$data) {
  395. // Hold on to destination if set.
  396. if (strpos($form['#action'], 'destination=') !== FALSE) {
  397. // If an absolute URL is in destination parse_url() will issue a warning
  398. // and not populate $url_args so no further protection is needed.
  399. parse_str(parse_url($form['#action'], PHP_URL_QUERY), $url_args);
  400. if (!empty($url_args['destination'])) {
  401. $data['destination'] = $url_args['destination'];
  402. }
  403. }
  404. }
  405. /**
  406. * Handle registration by redirecting to master.
  407. */
  408. function _bakery_register_submit($form, &$form_state) {
  409. // Create an array of fields to send to the master. We need these four fields.
  410. $allowed = array('name', 'mail', 'pass', 'timezone');
  411. foreach ($form_state['values'] as $key => $value) {
  412. if (!in_array($key, $allowed)) {
  413. unset($form_state['values'][$key]);
  414. }
  415. }
  416. // Remove unneeded values.
  417. form_state_values_clean($form_state);
  418. // Save values to cookie.
  419. $data = $form_state['values'];
  420. _bakery_save_destination_param($form , $data);
  421. unset($_GET['destination']);
  422. // Store name and email in case of error and return from master.
  423. $_SESSION['bakery']['register'] = array(
  424. 'name' => $data['name'],
  425. 'mail' => $data['mail'],
  426. );
  427. // Create cookie and redirect to master.
  428. bakery_bake_oatmeal_cookie($data['name'], $data);
  429. drupal_goto(variable_get('bakery_master', 'http://drupal.org/') . 'bakery');
  430. }
  431. /**
  432. * Handle login by redirecting to master.
  433. */
  434. function _bakery_login_submit($form, &$form_state) {
  435. // Get rid of all the values we don't explicitly know we want. While this may
  436. // break some modules it ensures we don't send sensitive data between sites.
  437. $allowed = array('name', 'pass', 'op');
  438. foreach ($form_state['values'] as $key => $value) {
  439. if (!in_array($key, $allowed)) {
  440. unset($form_state['values'][$key]);
  441. }
  442. }
  443. $data = $form_state['values'];
  444. _bakery_save_destination_param($form , $data);
  445. unset($_GET['destination']);
  446. // Save query parameters to be available when user returns from master.
  447. $data['query'] = drupal_get_query_parameters();
  448. // Create cookie and redirect to master.
  449. bakery_bake_oatmeal_cookie($data['name'], $data);
  450. drupal_goto(variable_get('bakery_master', 'http://drupal.org/') . 'bakery/login');
  451. }
  452. /**
  453. * Admin settings, see INSTALL.txt
  454. */
  455. function bakery_settings($form, &$form_state) {
  456. $form = array(
  457. '#submit' => array('bakery_settings_submit'),
  458. );
  459. $form['bakery_is_master'] = array(
  460. '#type' => 'checkbox',
  461. '#title' => 'Is this the master site?',
  462. '#default_value' => variable_get('bakery_is_master', 0),
  463. '#description' => t('On the master site, accounts need to be created by traditional processes, i.e by a user registering or an admin creating them.'),
  464. );
  465. $form['bakery_master'] = array(
  466. '#type' => 'textfield',
  467. '#title' => 'Master site',
  468. '#default_value' => variable_get('bakery_master', 'http://drupal.org/'),
  469. '#description' => t('Specify the master site for your bakery network.'),
  470. );
  471. $form['bakery_slaves'] = array(
  472. '#type' => 'textarea',
  473. '#title' => 'Slave sites',
  474. '#default_value' => implode("\n", variable_get('bakery_slaves', array())),
  475. '#description' => t('Specify any slave sites in your bakery network that you want to update if a user changes email or username on the master. Enter one site per line, in the form "http://sub.example.com/".'),
  476. );
  477. $form['bakery_help_text'] = array(
  478. '#type' => 'textarea',
  479. '#title' => 'Help text for users with synch problems.',
  480. '#default_value' => variable_get('bakery_help_text', 'Otherwise you can contact the site administrators.'),
  481. '#description' => t('This message will be shown to users if/when they have problems synching their accounts. It is an alternative to the "self repair" option and can be blank.'),
  482. );
  483. $form['bakery_freshness'] = array(
  484. '#type' => 'textfield',
  485. '#title' => 'Seconds of age before a cookie is old',
  486. '#default_value' => variable_get('bakery_freshness', '3600'),
  487. );
  488. $form['bakery_key'] = array(
  489. '#type' => 'textfield',
  490. '#title' => 'Private key for cookie validation',
  491. '#default_value' => variable_get('bakery_key', ''),
  492. );
  493. $form['bakery_domain'] = array(
  494. '#type' => 'textfield',
  495. '#title' => 'Cookie domain',
  496. '#default_value' => variable_get('bakery_domain', ''),
  497. );
  498. $default = variable_get('bakery_supported_fields', array('mail' => 'mail', 'name' => 'name'));
  499. $default['mail'] = 'mail';
  500. $default['name'] = 'name';
  501. $options = array('name' => t('username'), 'mail' => t('e-mail'), 'status' => t('status'), 'picture' => t('user picture'), 'language' => t('language'), 'signature' => t('signature'),);
  502. if (module_exists('profile')) {
  503. $result = db_query('SELECT name, title FROM {profile_field} ORDER BY category, weight');
  504. foreach ($result as $field) {
  505. $options[$field->name] = check_plain($field->title);
  506. }
  507. }
  508. $form['bakery_supported_fields'] = array(
  509. '#type' => 'checkboxes',
  510. '#title' => 'Supported profile fields',
  511. '#default_value' => $default,
  512. '#options' => $options,
  513. '#description' => t('Choose the profile fields that should be exported by the master and imported on the slaves. Username and E-mail are always exported. The correct export of individual fields may depend on the appropriate settings for other modules on both master and slaves. You need to configure this setting on both the master and the slaves.'),
  514. );
  515. // Tell system_settings_form() to not set default_values since we have already done so.
  516. return system_settings_form($form, FALSE);
  517. }
  518. function bakery_settings_submit($form, &$form_state) {
  519. // Rebuild the menu because the router items are based on the selection of
  520. // the master site. (Rebuilding it immediately here would be too early,
  521. // because the 'bakery_is_master' variable doesn't get set until the next
  522. // submit handler runs. So we trigger a rebuild on the next page request
  523. // instead.)
  524. variable_set('menu_rebuild_needed', TRUE);
  525. // Updating of data on slave sites will not work unless the url of the master site has a trailing slash.
  526. // We now remove the trailing slash (if present) and concatenate with a new trailing slash.
  527. $form_state['values']['bakery_master'] = trim($form_state['values']['bakery_master'], '/') .'/';
  528. // The list of slave sites needs transforming from a text string into array for storage.
  529. // Also, redirection after login will only work if there is a trailing slash after each entry.
  530. if ($form_state['values']['bakery_slaves']) {
  531. // Transform the text string into an array.
  532. $form_state['values']['bakery_slaves'] = explode("\n", trim(str_replace("\r", '', $form_state['values']['bakery_slaves'])));
  533. // For each entry, remove the trailing slash (if present) and concatenate with a new trailing slash.
  534. foreach ($form_state['values']['bakery_slaves'] as &$slave) {
  535. $slave = trim($slave, '/') .'/';
  536. }
  537. }
  538. else {
  539. $form_state['values']['bakery_slaves'] = array();
  540. }
  541. }
  542. /**
  543. * Special Bakery register callback registers the user and returns to slave.
  544. */
  545. function bakery_register() {
  546. $cookie = bakery_taste_oatmeal_cookie();
  547. if ($cookie) {
  548. // Valid cookie.
  549. // Destroy the current oatmeal cookie, we'll set a new one when we return to the slave.
  550. _bakery_eat_cookie('OATMEAL');
  551. if (variable_get('user_register', 1)) {
  552. // Users are allowed to register.
  553. $data = array();
  554. // Save errors.
  555. $errors = array();
  556. $name = trim($cookie['data']['name']);
  557. $mail = trim($cookie['data']['mail']);
  558. // Check if user exists with same email.
  559. $account = user_load_by_mail($mail);
  560. if ($account) {
  561. $errors['mail'] = 1;
  562. }
  563. else {
  564. // Check username.
  565. $account = user_load_by_name($name);
  566. if ($account) {
  567. $errors['name'] = 1;
  568. }
  569. }
  570. }
  571. else {
  572. watchdog('bakery', 'Master Bakery site user registration is disabled but users are trying to register from a subsite.', array(), WATCHDOG_ERROR);
  573. $errors['register'] = 1;
  574. }
  575. if (empty($errors)) {
  576. // Create user.
  577. $userinfo = $cookie['data'];
  578. if (!$cookie['data']['pass']) {
  579. $pass = user_password();
  580. }
  581. else {
  582. $pass = $cookie['data']['pass'];
  583. }
  584. // Set additional properties.
  585. $userinfo['name'] = $name;
  586. $userinfo['mail'] = $mail;
  587. $userinfo['pass'] = $pass;
  588. $userinfo['init'] = $mail;
  589. $userinfo['status'] = 1;
  590. $userinfo['authname_bakery'] = $name;
  591. $account = user_save('', $userinfo);
  592. // Set some info to return to the slave.
  593. $data['uid'] = $account->uid;
  594. $data['mail'] = $mail;
  595. watchdog('user', 'New external user: %name using module bakery from slave !slave.', array('%name' => $account->name, '!slave' => $cookie['slave']), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $account->uid .'/edit'));
  596. // Redirect to slave.
  597. if (!variable_get('user_email_verification', TRUE)) {
  598. // Create identification cookie and log user in.
  599. $init = _bakery_init_field($account->uid);
  600. _bakery_bake_chocolatechip_cookie($account->name, $account->mail, $init);
  601. bakery_user_external_login($account);
  602. }
  603. else {
  604. // The user needs to validate their email, redirect back to slave to
  605. // inform them.
  606. $errors['validate'] = 1;
  607. }
  608. }
  609. else {
  610. // There were errors.
  611. session_destroy();
  612. }
  613. // Redirect back to custom Bakery callback on slave.
  614. $data['errors'] = $errors;
  615. $data['name'] = $name;
  616. // Carry destination through return.
  617. if (isset($cookie['data']['destination'])) {
  618. $data['destination'] = $cookie['data']['destination'];
  619. }
  620. // Bake a new cookie for validation on the slave.
  621. bakery_bake_oatmeal_cookie($name, $data);
  622. drupal_goto($cookie['slave'] . 'bakery');
  623. }
  624. // Invalid request.
  625. drupal_access_denied();
  626. }
  627. /**
  628. * Custom return for slave registration process.
  629. *
  630. * Redirects to the homepage on success or to the register page if there was a problem.
  631. */
  632. function bakery_register_return() {
  633. $cookie = bakery_taste_oatmeal_cookie();
  634. if ($cookie) {
  635. // Valid cookie, now destroy it.
  636. _bakery_eat_cookie('OATMEAL');
  637. // Destination in cookie was set before user left this site, extract it to
  638. // be sure destination workflow is followed.
  639. if (empty($cookie['data']['destination'])) {
  640. $destination = '<front>';
  641. }
  642. else {
  643. $destination = $cookie['data']['destination'];
  644. }
  645. $errors = $cookie['data']['errors'];
  646. if (empty($errors)) {
  647. drupal_set_message(t('Registration successful. You are now logged in.'));
  648. // Redirect to destination.
  649. drupal_goto($destination);
  650. }
  651. else {
  652. if (!empty($errors['register'])) {
  653. drupal_set_message(t('Registration is not enabled on @master. Please contact a site administrator.', array('@master' => variable_get('bakery_master', 'http://drupal.org/'))), 'error');
  654. watchdog('bakery', 'Master Bakery site user registration is disabled', array(), WATCHDOG_ERROR);
  655. }
  656. if (!empty($errors['validate'])) {
  657. // If the user must validate their email then we need to create an
  658. // account for them on the slave site.
  659. $new = array(
  660. 'name' => $cookie['name'],
  661. 'mail' => $cookie['data']['mail'],
  662. 'init' => _bakery_init_field($cookie['data']['uid']),
  663. 'status' => 1,
  664. 'pass' => user_password(),
  665. );
  666. $account = user_save(new stdClass(), $new);
  667. // Notify the user that they need to validate their email.
  668. _user_mail_notify('register_no_approval_required', $account);
  669. unset($_SESSION['bakery']['register']);
  670. drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.'));
  671. }
  672. if (!empty($errors['name'])) {
  673. drupal_set_message(t('Name is already taken.'), 'error');
  674. }
  675. if (!empty($errors['mail'])) {
  676. drupal_set_message(t('E-mail address is already registered.'), 'error');
  677. }
  678. if (!empty($errors['mail_denied'])) {
  679. drupal_set_message(t('The e-mail address has been denied access..'), 'error');
  680. }
  681. if (!empty($errors['name_denied'])) {
  682. drupal_set_message(t('The name has been denied access..'), 'error');
  683. }
  684. // There are errors so keep user on registration page.
  685. drupal_goto('user/register');
  686. }
  687. }
  688. drupal_access_denied();
  689. }
  690. /**
  691. * Special Bakery login callback authenticates the user and returns to slave.
  692. */
  693. function bakery_login() {
  694. $cookie = bakery_taste_oatmeal_cookie();
  695. if ($cookie) {
  696. // Make sure there are query defaults.
  697. $cookie['data'] += array('query' => array());
  698. $errors = array();
  699. // Remove the data pass cookie.
  700. _bakery_eat_cookie('OATMEAL');
  701. // First see if the user_login form validation has any errors for them.
  702. $name = trim($cookie['data']['name']);
  703. $pass = trim($cookie['data']['pass']);
  704. // Execute the login form which checks username, password, status and flood.
  705. $form_state = array();
  706. $form_state['values'] = $cookie['data'];
  707. drupal_form_submit('user_login', $form_state);
  708. $errors = form_get_errors();
  709. if (empty($errors)) {
  710. // Check if account credentials are correct.
  711. $account = user_load_by_name($name);
  712. if (isset($account->uid)) {
  713. // Check if the mail is denied.
  714. if (drupal_is_denied('user', $account->mail)) {
  715. $errors['name'] = t('The name %name is registered using a reserved e-mail address and therefore could not be logged in.', array('%name' => $name));
  716. }
  717. else {
  718. // Passed all checks, create identification cookie and log in.
  719. $init = _bakery_init_field($account->uid);
  720. _bakery_bake_chocolatechip_cookie($account->name, $account->mail, $init);
  721. global $user;
  722. $user = $account;
  723. $edit = array('name' => $user->name);
  724. bakery_user_authenticate_finalize($edit);
  725. }
  726. }
  727. else {
  728. $errors['incorrect-credentials'] = 1;
  729. }
  730. }
  731. if (!empty($errors)) {
  732. // Report failed login.
  733. watchdog('user', 'Login attempt failed for %user.', array('%user' => $name));
  734. // Clear the messages on the master's session, since they were set during
  735. // drupal_form_submit() and will be displayed out of context.
  736. drupal_get_messages();
  737. }
  738. // Bake a new cookie for validation on the slave.
  739. $data = array(
  740. 'errors' => $errors,
  741. 'name' => $name,
  742. );
  743. // Carry destination through login.
  744. if (isset($cookie['data']['destination'])) {
  745. $data['destination'] = $cookie['data']['destination'];
  746. }
  747. // Carry other query parameters through login.
  748. $data['query'] = $cookie['data']['query'];
  749. bakery_bake_oatmeal_cookie($name, $data);
  750. drupal_goto($cookie['slave'] . 'bakery/login');
  751. }
  752. drupal_access_denied();
  753. }
  754. /**
  755. * Custom return for errors during slave login process.
  756. */
  757. function bakery_login_return() {
  758. $cookie = bakery_taste_oatmeal_cookie();
  759. if ($cookie) {
  760. // Make sure we always have a default query key.
  761. $cookie['data'] += array('query' => array());
  762. // Valid cookie, now destroy it.
  763. _bakery_eat_cookie('OATMEAL');
  764. if (!empty($cookie['data']['errors'])) {
  765. $errors = $cookie['data']['errors'];
  766. if (!empty($errors['incorrect-credentials'])) {
  767. drupal_set_message(t('Sorry, unrecognized username or password.'), 'error');
  768. }
  769. elseif (!empty($errors['name'])) {
  770. // In case an attacker got the hash we filter the argument here to avoid
  771. // exposing a XSS vector.
  772. drupal_set_message(filter_xss($errors['name']), 'error');
  773. }
  774. }
  775. // Prepare the url options array to pass to drupal_goto().
  776. $options = array('query' => $cookie['data']['query']);
  777. if (empty($cookie['data']['destination'])) {
  778. drupal_goto('user', $options);
  779. }
  780. else {
  781. $destination = $cookie['data']['destination'];
  782. if (($pos = strpos($cookie['data']['destination'], '?')) !== FALSE) {
  783. // Destination contains query arguments that must be extracted.
  784. $destination = substr($cookie['data']['destination'], 0, $pos);
  785. $options['query'] += drupal_get_query_array(substr($cookie['data']['destination'], $pos + 1));
  786. }
  787. drupal_goto($destination, $options);
  788. }
  789. }
  790. // If user is logged in and visiting this page redirect to <front>.
  791. elseif (user_is_logged_in()) {
  792. drupal_goto();
  793. }
  794. drupal_access_denied();
  795. }
  796. /**
  797. * Access callback for path /user.
  798. *
  799. * Displays user profile if user is logged in, or login form for anonymous
  800. * users.
  801. */
  802. function bakery_user_page() {
  803. global $user;
  804. if ($user->uid) {
  805. menu_set_active_item('user/'. $user->uid);
  806. return menu_execute_active_handler();
  807. }
  808. }
  809. /**
  810. * Encrypt and sign data for Bakery transfer.
  811. *
  812. * @param Array of data to be transferred.
  813. *
  814. * @return String of signed and encrypted data, url safe.
  815. */
  816. function bakery_bake_data($data) {
  817. $key = variable_get('bakery_key', '');
  818. $data = bakery_encrypt(serialize($data));
  819. $signature = hash_hmac('sha256', $data, $key);
  820. return base64_encode($signature . $data);
  821. }
  822. /**
  823. * Validate signature and decrypt data.
  824. *
  825. * @param String of Bakery data, base64 encoded.
  826. * @param Optional string defining the type of data this is.
  827. *
  828. * @return Unserialized data or FALSE if invalid.
  829. */
  830. function bakery_validate_data($data, $type = NULL) {
  831. $key = variable_get('bakery_key', '');
  832. $data = base64_decode($data);
  833. $signature = substr($data, 0, 64);
  834. $encrypted_data = substr($data, 64);
  835. if ($signature !== hash_hmac('sha256', $encrypted_data, $key)) {
  836. return FALSE;
  837. }
  838. $decrypted_data = unserialize(bakery_decrypt($encrypted_data));
  839. // Prevent one cookie being used in place of another.
  840. if ($type !== NULL && $decrypted_data['type'] !== $type) {
  841. return FALSE;
  842. }
  843. // Allow cookies to expire when the browser closes.
  844. if (variable_get('bakery_freshness', '3600') == 0
  845. || $decrypted_data['timestamp'] + variable_get('bakery_freshness', '3600') >= $_SERVER['REQUEST_TIME']) {
  846. return $decrypted_data;
  847. }
  848. return FALSE;
  849. }
  850. /**
  851. * Name for cookie including session.cookie_secure and variable extension.
  852. *
  853. * @param string $type
  854. * CHOCOLATECHIP or OATMEAL, default CHOCOLATECHIP
  855. * @return string
  856. * The cookie name for this environment.
  857. */
  858. function _bakery_cookie_name($type = 'CHOCOLATECHIP') {
  859. // Use different names for HTTPS and HTTP to prevent a cookie collision.
  860. if (ini_get('session.cookie_secure')) {
  861. if (variable_get('bakery_loose_ssl', FALSE)) {
  862. // Prefer SSL cookie if loose.
  863. if (isset($_COOKIE[$type . 'SSL'])) {
  864. $type .= 'SSL';
  865. }
  866. }
  867. else {
  868. // Always use SSL cookie if strict.
  869. $type .= 'SSL';
  870. }
  871. }
  872. // Allow installation to modify the cookie name.
  873. $extension = variable_get('bakery_cookie_extension', '');
  874. $type .= $extension;
  875. return $type;
  876. }
  877. /**
  878. * Function to validate cookies
  879. *
  880. * @param $type (string) CHOCOLATECHIP or OATMEAL, default CHOCOLATECHIP
  881. *
  882. * @return validated and decrypted cookie in an array, FALSE if invalid, or NULL
  883. */
  884. function _bakery_validate_cookie($type = 'CHOCOLATECHIP') {
  885. $key = variable_get('bakery_key', '');
  886. $type = _bakery_cookie_name($type);
  887. if (!isset($_COOKIE[$type]) || !$key || !variable_get('bakery_domain', '')) {
  888. // No cookie is set or site is misconfigured. Return NULL so existing
  889. // cookie is not deleted by bakery_eat_cookie().
  890. return NULL;
  891. }
  892. if (($data = bakery_validate_data($_COOKIE[$type], $type)) !== FALSE) {
  893. return $data;
  894. }
  895. else {
  896. return FALSE;
  897. }
  898. }
  899. /**
  900. * Test identification cookie
  901. */
  902. function _bakery_taste_chocolatechip_cookie() {
  903. $cookie = _bakery_validate_cookie();
  904. // Continue if this is a valid cookie. That only happens for users who have
  905. // a current valid session on the master site.
  906. if ($cookie) {
  907. $destroy_cookie = FALSE;
  908. global $user;
  909. // Detect SSO cookie mismatch if there is already a valid session for user.
  910. if ($user->uid && $cookie['name'] !== $user->name) {
  911. // The SSO cookie doesn't match the existing session so force a logout.
  912. drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
  913. _bakery_user_logout();
  914. }
  915. // Bake a fresh cookie. Yum.
  916. _bakery_bake_chocolatechip_cookie($cookie['name'], $cookie['mail'], $cookie['init']);
  917. if (!$user->uid) {
  918. // Since this might happen in hook_boot we need to bootstrap first.
  919. // Note that this only runs if they have a valid session on the master
  920. // and do not have one on the slave so it only creates the extra load of
  921. // a bootstrap on one pageview per session on the site which is not much.
  922. drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
  923. // User is anonymous. If they do not have an account we'll create one by
  924. // requesting their information from the master site. If they do have an
  925. // account we may need to correct some disparant information.
  926. $account = user_load_multiple(array(), array('name' => $cookie['name'], 'mail' => $cookie['mail']));
  927. $account = reset($account);
  928. // Fix out of sync users with valid init.
  929. if (!$account && !variable_get('bakery_is_master', 0) && $cookie['master']) {
  930. $count = db_select('users', 'u')->fields('u', array('uid'))
  931. ->condition('init', $cookie['init'])
  932. ->countQuery()->execute()->fetchField();
  933. if ($count > 1) {
  934. // Uh oh.
  935. watchdog('bakery', 'Account uniqueness problem: Multiple users found with init %init.', array('%init' => $cookie['init']), WATCHDOG_ERROR);
  936. drupal_set_message(t('Account uniqueness problem detected. <a href="@contact">Please contact the site administrator.</a>', array('@contact' => variable_get('bakery_master', 'http://drupal.org/') .'contact')), 'error');
  937. }
  938. if ($count == 1) {
  939. $account = user_load_multiple(array(), array('init' => $cookie['init']));
  940. if (is_array($account)) {
  941. $account = reset($account);
  942. }
  943. if ($account) {
  944. watchdog('bakery', 'Fixing out of sync uid %uid. Changed name %name_old to %name_new, mail %mail_old to %mail_new.', array('%uid' => $account->uid, '%name_old' => $account->name, '%name_new' => $cookie['name'], '%mail_old' => $account->mail, '%mail_new' => $cookie['mail']));
  945. user_save($account, array('name' => $cookie['name'], 'mail' => $cookie['mail']));
  946. $account = user_load_multiple(array(), array('name' => $cookie['name'], 'mail' => $cookie['mail']));
  947. $account = reset($account);
  948. }
  949. }
  950. }
  951. // Create the account if it doesn't exist.
  952. if (!$account && !variable_get('bakery_is_master', 0) && $cookie['master']) {
  953. $checks = TRUE;
  954. $mail_count = db_select('users', 'u')->fields('u', array('uid'))
  955. ->condition('uid', $user->uid, '!=')
  956. ->condition('mail', '', '!=')
  957. ->where('LOWER(mail) = LOWER(:mail)', array(':mail' => $cookie['mail']))
  958. ->countQuery()->execute()->fetchField();
  959. if ($mail_count > 0) {
  960. $checks = FALSE;
  961. }
  962. $name_count = db_select('users', 'u')->fields('u', array('uid'))
  963. ->condition('uid', $user->uid, '!=')
  964. ->where('LOWER(name) = LOWER(:name)', array(':name' => $cookie['name']))
  965. ->countQuery()->execute()->fetchField();
  966. if ($name_count > 0) {
  967. $checks = FALSE;
  968. }
  969. $init_count = db_select('users', 'u')->fields('u', array('uid'))
  970. ->condition('uid', $user->uid, '!=')
  971. ->condition('init', $cookie['init'], '=')
  972. ->where('LOWER(name) = LOWER(:name)', array(':name' => $cookie['name']))
  973. ->countQuery()->execute()->fetchField();
  974. if ($init_count > 0) {
  975. $checks = FALSE;
  976. }
  977. if ($checks) {
  978. // Request information from master to keep data in sync.
  979. $uid = bakery_request_account($cookie['name']);
  980. // In case the account creation failed we want to make sure the user
  981. // gets their bad cookie destroyed by not returning too early.
  982. if ($uid) {
  983. $account = user_load($uid);
  984. }
  985. else {
  986. $destroy_cookie = TRUE;
  987. }
  988. }
  989. else {
  990. drupal_set_message(t('Your user account on %site appears to have problems. Would you like to try to <a href="@url">repair it yourself</a>?', array('%site' => variable_get('site_name', 'Drupal'), '@url' => url('bakery/repair'))));
  991. drupal_set_message(filter_xss_admin(variable_get('bakery_help_text', 'Otherwise you can contact the site administrators.')));
  992. $_SESSION['BAKERY_CRUMBLED'] = TRUE;
  993. }
  994. }
  995. if ($account && $cookie['master'] && $account->uid && !variable_get('bakery_is_master', 0) && $account->init != $cookie['init']) {
  996. // User existed previously but init is wrong. Fix it to ensure account
  997. // remains in sync.
  998. // Make sure that there aren't any OTHER accounts with this init already.
  999. $count = db_select('users', 'u')->fields('u', array('uid'))->condition('init', $cookie['init'], '=')
  1000. ->countQuery()->execute()->fetchField();
  1001. if ($count == 0) {
  1002. db_update('users')->fields(array('init' => $cookie['init']))
  1003. ->condition('uid', $account->uid)
  1004. ->execute();
  1005. watchdog('bakery', 'uid %uid out of sync. Changed init field from %oldinit to %newinit', array('%oldinit' => $account->init, '%newinit' => $cookie['init'], '%uid' => $account->uid));
  1006. }
  1007. else {
  1008. // Username and email matched, but init belonged to a DIFFERENT account.
  1009. // Something got seriously tangled up.
  1010. watchdog('bakery', 'Accounts mixed up! Username %user and init %init disagree with each other!', array('%user' => $account->name, '%init' => $cookie['init']), WATCHDOG_CRITICAL);
  1011. }
  1012. }
  1013. if ($account && $user->uid == 0) {
  1014. // If the login attempt fails we need to destroy the cookie to prevent
  1015. // infinite redirects (with infinite failed login messages).
  1016. $login = bakery_user_external_login($account);
  1017. if ($login) {
  1018. // If an anonymous user has just been logged in, trigger a 'refresh'
  1019. // of the current page, ensuring that drupal_goto() does not override
  1020. // the current page with the destination query.
  1021. $query = drupal_get_query_parameters();
  1022. unset($_GET['destination']);
  1023. drupal_goto(current_path(), array('query' => $query));
  1024. }
  1025. else {
  1026. $destroy_cookie = TRUE;
  1027. }
  1028. }
  1029. }
  1030. if ($destroy_cookie !== TRUE) {
  1031. return TRUE;
  1032. }
  1033. }
  1034. // Eat the bad cookie. Burp.
  1035. if ($cookie === FALSE) {
  1036. _bakery_eat_cookie();
  1037. }
  1038. // No cookie or invalid cookie
  1039. if (!$cookie) {
  1040. global $user;
  1041. // Log out users that have lost their SSO cookie, with the exception of
  1042. // UID 1 and any applied roles with permission to bypass.
  1043. if ($user->uid > 1) {
  1044. // This runs for logged in users. Those folks are going to get a full bootstrap anyway so this isn't a problem.
  1045. drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
  1046. if (!user_access('bypass bakery')) {
  1047. watchdog('bakery', 'Logging out the user with the bad cookie.');
  1048. _bakery_user_logout();
  1049. }
  1050. }
  1051. }
  1052. return FALSE;
  1053. }
  1054. /**
  1055. * Validate update request.
  1056. */
  1057. function bakery_taste_stroopwafel_cookie() {
  1058. $type = 'stroopwafel';
  1059. if (empty($_POST[$type])) {
  1060. return FALSE;
  1061. }
  1062. if (($payload = bakery_validate_data($_POST[$type], $type)) === FALSE) {
  1063. return FALSE;
  1064. }
  1065. $_SESSION['bakery'] = unserialize($payload['data']);
  1066. $_SESSION['bakery']['uid'] = $payload['uid'];
  1067. $_SESSION['bakery']['category'] = $payload['category'];
  1068. return TRUE;
  1069. }
  1070. /**
  1071. * Create a new cookie for identification
  1072. */
  1073. function _bakery_bake_chocolatechip_cookie($name, $mail, $init) {
  1074. $key = variable_get('bakery_key', '');
  1075. if (!empty($key)) {
  1076. $cookie = array();
  1077. $cookie['name'] = $name;
  1078. $cookie['mail'] = $mail;
  1079. $cookie['init'] = $init;
  1080. $cookie['master'] = variable_get('bakery_is_master', 0);
  1081. $cookie['calories'] = 480;
  1082. $cookie['timestamp'] = $_SERVER['REQUEST_TIME'];
  1083. $cookie_secure = !variable_get('bakery_loose_ssl', FALSE) && ini_get('session.cookie_secure');
  1084. $type = _bakery_cookie_name('CHOCOLATECHIP');
  1085. $cookie['type'] = $type;
  1086. $data = bakery_bake_data($cookie);
  1087. // Allow cookies to expire when the browser closes.
  1088. $expire = (variable_get('bakery_freshness', '3600') > 0) ? $_SERVER['REQUEST_TIME'] + variable_get('bakery_freshness', '3600') : '0';
  1089. setcookie($type, $data, $expire, '/', variable_get('bakery_domain', ''), $cookie_secure);
  1090. }
  1091. }
  1092. function bakery_taste_oatmeal_cookie() {
  1093. $key = variable_get('bakery_key', '');
  1094. $type = _bakery_cookie_name('OATMEAL');
  1095. if (!isset($_COOKIE[$type]) || !$key || !variable_get('bakery_domain', '')) {
  1096. return FALSE;
  1097. }
  1098. if (($data = bakery_validate_data($_COOKIE[$type], $type)) !== FALSE) {
  1099. return $data;
  1100. }
  1101. return FALSE;
  1102. }
  1103. /**
  1104. * Create a cookie for passing information between sites for registration and login.
  1105. */
  1106. function bakery_bake_oatmeal_cookie($name, $data) {
  1107. $key = variable_get('bakery_key', '');
  1108. if (!empty($key)) {
  1109. global $base_url;
  1110. $cookie = array(
  1111. 'data' => $data,
  1112. 'name' => $name,
  1113. 'calories' => 320,
  1114. 'timestamp' => $_SERVER['REQUEST_TIME'],
  1115. );
  1116. if (variable_get('bakery_is_master', FALSE)) {
  1117. $cookie['master'] = 1;
  1118. }
  1119. else {
  1120. $cookie['master'] = 0;
  1121. $cookie['slave'] = $base_url . '/'; // Match the way slaves are set in Bakery settings, with ending slash.
  1122. }
  1123. $cookie_secure = !variable_get('bakery_loose_ssl', FALSE) && ini_get('session.cookie_secure');
  1124. $type = _bakery_cookie_name('OATMEAL');
  1125. $cookie['type'] = $type;
  1126. $data = bakery_bake_data($cookie);
  1127. // Allow cookies to expire when the browser closes.
  1128. $expire = (variable_get('bakery_freshness', '3600') > 0) ? $_SERVER['REQUEST_TIME'] + variable_get('bakery_freshness', '3600') : '0';
  1129. setcookie($type, $data, $expire, '/', variable_get('bakery_domain', ''), (empty($cookie_secure) ? FALSE : TRUE));
  1130. }
  1131. }
  1132. /**
  1133. * Menu callback, invoked on the slave
  1134. */
  1135. function bakery_eat_stroopwafel_cookie() {
  1136. // the session got set during validation
  1137. $stroopwafel = $_SESSION['bakery'];
  1138. unset($_SESSION['bakery']);
  1139. $init = _bakery_init_field($stroopwafel['uid']);
  1140. // check if the user exists.
  1141. $account = user_load_multiple(array(), array('init' => $init));
  1142. if (empty($account)) {
  1143. // user not present
  1144. $message = t('Account not found on %slave.', array('%slave' => variable_get('site_name', '')));
  1145. }
  1146. else {
  1147. $account = reset($account);
  1148. drupal_add_http_header('X-Drupal-bakery-UID', $account->uid);
  1149. // If profile field is enabled we manually save profile fields along the way.
  1150. $fields = array();
  1151. foreach (variable_get('bakery_supported_fields', array('mail' => 'mail', 'name' => 'name')) as $type => $value) {
  1152. if ($value) {
  1153. // If the field is set in the cookie it's being updated, otherwise we'll
  1154. // populate $fields with the existing values so nothing is lost.
  1155. if (isset($stroopwafel[$type])) {
  1156. $fields[$type] = $stroopwafel[$type];
  1157. }
  1158. else {
  1159. $fields[$type] = $account->$type;
  1160. }
  1161. }
  1162. }
  1163. $status = user_save($account, $fields);
  1164. if ($status === FALSE) {
  1165. watchdog('bakery', 'User update from name %name_old to %name_new, mail %mail_old to %mail_new failed.', array('%name_old' => $account->name, '%name_new' => $stroopwafel['name'], '%mail_old' => $account->mail, '%mail_new' => $stroopwafel['mail']), WATCHDOG_ERROR);
  1166. $message = t('There was a problem updating your account on %slave. Please contact the administrator.', array('%slave' => variable_get('site_name', '')));
  1167. header('HTTP/1.1 409 Conflict');
  1168. }
  1169. else {
  1170. watchdog('bakery', 'user updated name %name_old to %name_new, mail %mail_old to %mail_new.', array('%name_old' => $account->name, '%name_new' => $stroopwafel['name'], '%mail_old' => $account->mail, '%mail_new' => $stroopwafel['mail']));
  1171. $message = t('Successfully updated account on %slave.', array('%slave' => variable_get('site_name', '')));
  1172. }
  1173. // Invoke hook_bakery_receive().
  1174. module_invoke_all('bakery_receive', $account, $stroopwafel);
  1175. }
  1176. module_invoke_all('exit');
  1177. print $message;
  1178. exit();
  1179. }
  1180. /**
  1181. * Verify the validation request.
  1182. */
  1183. function bakery_taste_thinmint_cookie() {
  1184. $type = 'thinmint';
  1185. if (empty($_POST[$type])) {
  1186. return FALSE;
  1187. }
  1188. if (($cookie = bakery_validate_data($_POST[$type], $type)) === FALSE) {
  1189. return FALSE;
  1190. }
  1191. $_SESSION['bakery']['name'] = $cookie['name'];
  1192. $_SESSION['bakery']['slave'] = $cookie['slave'];
  1193. $_SESSION['bakery']['uid'] = $cookie['uid'];
  1194. return TRUE;
  1195. }
  1196. /**
  1197. * Update the user's login time to reflect them validating their email address.
  1198. */
  1199. function bakery_eat_thinmint_cookie() {
  1200. // Session was set in validate.
  1201. $name = $_SESSION['bakery']['name'];
  1202. unset($_SESSION['bakery']['name']);
  1203. $slave = $_SESSION['bakery']['slave'];
  1204. unset($_SESSION['bakery']['slave']);
  1205. $uid = $_SESSION['bakery']['uid'];
  1206. unset($_SESSION['bakery']['uid']);
  1207. $account = user_load_by_name($name);
  1208. if ($account) {
  1209. // @todo
  1210. db_query("UPDATE {users} SET login = :login WHERE uid = :uid", array(':login' => $_SERVER['REQUEST_TIME'], ':uid' => $account->uid));
  1211. // Save UID provided by slave site.
  1212. _bakery_save_slave_uid($account, $slave, $uid);
  1213. }
  1214. }
  1215. /**
  1216. * Request account information from master to create account locally.
  1217. *
  1218. * @param string $name the username or e-mail to request information for to create.
  1219. * @param boolean $or_email load account by name or email. Useful for getting
  1220. * account data from a password request where you get name or email.
  1221. * @return The newly created local UID or FALSE.
  1222. */
  1223. function bakery_request_account($name, $or_email = FALSE) {
  1224. global $base_url;
  1225. $existing_account = user_load_by_name($name);
  1226. if (!$existing_account && $or_email) {
  1227. $account = user_load_by_mail($name);
  1228. }
  1229. // We return FALSE in cases that the account already exists locally or if
  1230. // there was an error along the way of requesting and creating it.
  1231. if ($existing_account) {
  1232. return FALSE;
  1233. }
  1234. $master = variable_get('bakery_master', 'http://drupal.org/');
  1235. $key = variable_get('bakery_key', '');
  1236. // Save a stub account so we have a slave UID to send.
  1237. $new_account = array(
  1238. 'name' => $name,
  1239. 'pass' => user_password(),
  1240. 'status' => 1,
  1241. 'init' => 'bakery_temp/' . mt_rand(),
  1242. );
  1243. $account = user_save(NULL, $new_account);
  1244. if (!$account) {
  1245. watchdog('bakery', 'Unable to create stub account for @name', array('@name' => $name), WATCHDOG_ERROR);
  1246. return FALSE;
  1247. }
  1248. $stub_uid = $account->uid;
  1249. $type = 'gingerbread';
  1250. $payload = array();
  1251. $payload['name'] = $name;
  1252. $payload['or_email'] = $or_email;
  1253. $payload['slave'] = rtrim($base_url, '/') . '/'; // Match how slaves are set on the master.
  1254. $payload['uid'] = $account->uid;
  1255. $payload['timestamp'] = $_SERVER['REQUEST_TIME'];
  1256. $payload['type'] = $type;
  1257. $data = bakery_bake_data($payload);
  1258. $payload = drupal_http_build_query(array($type => $data));
  1259. // Make request to master for account information.
  1260. $http_options = array('method' => 'POST', 'data' => $payload, 'headers' => array('Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'));
  1261. $result = drupal_http_request($master . 'bakery/create', $http_options);
  1262. // Parse result and create account.
  1263. if ($result->code != 200) {
  1264. $message = $result->data;
  1265. watchdog('bakery', 'Received response !code from master with message @message', array('!code' => $result->code, '@message' => $message), WATCHDOG_ERROR);
  1266. user_delete($stub_uid);
  1267. return FALSE;
  1268. }
  1269. if (($cookie = bakery_validate_data($result->data)) === FALSE) {
  1270. // Invalid response.
  1271. watchdog('bakery', 'Invalid response from master when attempting to create local account for @name', array('@name' => $name), WATCHDOG_ERROR);
  1272. user_delete($stub_uid);
  1273. return FALSE;
  1274. }
  1275. // Valid response. Fill in details from master.
  1276. $new_account = array(
  1277. 'name' => $cookie['name'],
  1278. 'pass' => user_password(),
  1279. 'mail' => $cookie['mail'],
  1280. 'init' => _bakery_init_field($cookie['uid']),
  1281. );
  1282. // Add any supported sync fields.
  1283. foreach (variable_get('bakery_supported_fields', array('mail' => 'mail', 'name' => 'name')) as $type => $enabled) {
  1284. if ($enabled && isset($cookie[$type])) {
  1285. $new_account[$type] = $cookie[$type];
  1286. }
  1287. }
  1288. // Create account.
  1289. $account = user_save($account, $new_account);
  1290. if ($account) {
  1291. watchdog('bakery', 'Created account for @name', array('@name' => $name));
  1292. // Invoke hook_bakery_receive().
  1293. module_invoke_all('bakery_receive', $account, $cookie);
  1294. return $account->uid;
  1295. }
  1296. watchdog('bakery', 'Unable to create account for @name', array('@name' => $name), WATCHDOG_ERROR);
  1297. user_delete($stub_uid);
  1298. return FALSE;
  1299. }
  1300. /**
  1301. * Validate the account information request.
  1302. */
  1303. function bakery_taste_gingerbread_cookie() {
  1304. $type = 'gingerbread';
  1305. if (empty($_POST[$type])) {
  1306. return FALSE;
  1307. }
  1308. if (($cookie = bakery_validate_data($_POST[$type], $type)) === FALSE) {
  1309. return FALSE;
  1310. }
  1311. $_SESSION['bakery']['name'] = $cookie['name'];
  1312. $_SESSION['bakery']['or_email'] = $cookie['or_email'];
  1313. $_SESSION['bakery']['slave'] = $cookie['slave'];
  1314. $_SESSION['bakery']['uid'] = $cookie['uid'];
  1315. return TRUE;
  1316. }
  1317. /**
  1318. * Respond with account information.
  1319. */
  1320. function bakery_eat_gingerbread_cookie() {
  1321. // Session was set in validate.
  1322. $name = $_SESSION['bakery']['name'];
  1323. unset($_SESSION['bakery']['name']);
  1324. $or_email = $_SESSION['bakery']['or_email'];
  1325. unset($_SESSION['bakery']['or_email']);
  1326. $slave = $_SESSION['bakery']['slave'];
  1327. unset($_SESSION['bakery']['slave']);
  1328. $slave_uid = $_SESSION['bakery']['uid'];
  1329. unset($_SESSION['bakery']['uid']);
  1330. $key = variable_get('bakery_key', '');
  1331. $account = user_load_by_name($name);
  1332. if (!$account && $or_email) {
  1333. $account = user_load_by_mail($name);
  1334. }
  1335. if ($account) {
  1336. _bakery_save_slave_uid($account, $slave, $slave_uid);
  1337. $payload = array();
  1338. $payload['name'] = $account->name;
  1339. $payload['mail'] = $account->mail;
  1340. $payload['uid'] = $account->uid; // For use in slave init field.
  1341. // Add any synced fields.
  1342. foreach (variable_get('bakery_supported_fields', array('mail' => 'mail', 'name' => 'name')) as $type => $enabled) {
  1343. if ($enabled && $account->$type) {
  1344. $payload[$type] = $account->$type;
  1345. }
  1346. }
  1347. // Invoke implementations of hook_bakery_transmit() for syncing arbitrary
  1348. // data.
  1349. $payload['data'] = module_invoke_all('bakery_transmit', NULL, $account);
  1350. $payload['timestamp'] = $_SERVER['REQUEST_TIME'];
  1351. // Respond with encrypted and signed account information.
  1352. $message = bakery_bake_data($payload);
  1353. }
  1354. else {
  1355. $message = t('No account found');
  1356. header('HTTP/1.1 409 Conflict');
  1357. }
  1358. module_invoke_all('exit');
  1359. print $message;
  1360. exit();
  1361. }
  1362. /**
  1363. * Destroy unwanted cookies
  1364. */
  1365. function _bakery_eat_cookie($type = 'CHOCOLATECHIP') {
  1366. $cookie_secure = ini_get('session.cookie_secure');
  1367. $type = _bakery_cookie_name($type);
  1368. setcookie($type, '', $_SERVER['REQUEST_TIME'] - 3600, '/', '', (empty($cookie_secure) ? FALSE : TRUE));
  1369. setcookie($type, '', $_SERVER['REQUEST_TIME'] - 3600, '/', variable_get('bakery_domain', ''), (empty($cookie_secure) ? FALSE : TRUE));
  1370. }
  1371. /**
  1372. * Build internal init url (without scheme).
  1373. */
  1374. function _bakery_init_field($uid) {
  1375. $url = variable_get('bakery_master', 'http://drupal.org/');
  1376. $scheme = parse_url($url, PHP_URL_SCHEME);
  1377. return str_replace($scheme . '://', '', $url) . 'user/' . $uid . '/edit';
  1378. }
  1379. /**
  1380. * Build full init url to master.
  1381. */
  1382. function _bakery_init_field_url($init) {
  1383. $scheme = parse_url(variable_get('bakery_master', 'http://drupal.org/'), PHP_URL_SCHEME);
  1384. return $scheme . '://'. $init;
  1385. }
  1386. /**
  1387. * Encryption handler.
  1388. *
  1389. * @param $text, The text to be encrypted.
  1390. *
  1391. * @return Encryped text.
  1392. */
  1393. function bakery_encrypt($text) {
  1394. $key = variable_get('bakery_key', '');
  1395. $td = mcrypt_module_open('rijndael-128', '', 'ecb', '');
  1396. $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
  1397. $key = substr($key, 0, mcrypt_enc_get_key_size($td));
  1398. mcrypt_generic_init($td, $key, $iv);
  1399. $data = mcrypt_generic($td, $text);
  1400. mcrypt_generic_deinit($td);
  1401. mcrypt_module_close($td);
  1402. return $data;
  1403. }
  1404. /**
  1405. * Decryption handler.
  1406. *
  1407. * @param $text, The data to be decrypted.
  1408. *
  1409. * @return Decrypted text.
  1410. */
  1411. function bakery_decrypt($text) {
  1412. $key = variable_get('bakery_key', '');
  1413. $td = mcrypt_module_open('rijndael-128', '', 'ecb', '');
  1414. $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
  1415. $key = substr($key, 0, mcrypt_enc_get_key_size($td));
  1416. mcrypt_generic_init($td, $key, $iv);
  1417. $data = mdecrypt_generic($td, $text);
  1418. mcrypt_generic_deinit($td);
  1419. mcrypt_module_close($td);
  1420. return $data;
  1421. }
  1422. /**
  1423. * Perform standard Drupal login operations for a user object.
  1424. *
  1425. * The user object must already be authenticated. This function verifies
  1426. * that the user account is not blocked/denied and then performs the login,
  1427. * updates the login timestamp in the database, invokes hook_user('login'),
  1428. * and regenerates the session.
  1429. *
  1430. * @param $account
  1431. * An authenticated user object to be set as the currently logged
  1432. * in user.
  1433. * @param $edit
  1434. * The array of form values submitted by the user, if any.
  1435. * This array is passed to hook_user op login.
  1436. * @return boolean
  1437. * TRUE if the login succeeds, FALSE otherwise.
  1438. */
  1439. function bakery_user_external_login($account, $edit = array()) {
  1440. $form = drupal_get_form('user_login');
  1441. $state['values'] = $edit;
  1442. if (empty($state['values']['name'])) {
  1443. $state['values']['name'] = $account->name;
  1444. }
  1445. // Check if user is blocked or denied by access rules.
  1446. user_login_name_validate($form, $state, (array)$account);
  1447. if (form_get_errors()) {
  1448. // Invalid login.
  1449. return FALSE;
  1450. }
  1451. // Valid login.
  1452. global $user;
  1453. $user = $account;
  1454. bakery_user_authenticate_finalize($state['values']);
  1455. return TRUE;
  1456. }
  1457. /**
  1458. * Finalize the login process. Must be called when logging in a user.
  1459. *
  1460. * The function records a watchdog message about the new session, saves the
  1461. * login timestamp, calls hook_user op 'login' and generates a new session.
  1462. *
  1463. * $param $edit
  1464. * This array is passed to hook_user op login.
  1465. */
  1466. function bakery_user_authenticate_finalize(&$edit) {
  1467. global $user;
  1468. watchdog('user', 'Session opened for %name.', array('%name' => $user->name));
  1469. // Update the user table timestamp noting user has logged in.
  1470. // This is also used to invalidate one-time login links.
  1471. $user->login = time();
  1472. db_update('users')->fields(array('login' => $user->login))
  1473. ->condition('uid', $user->uid, '=')
  1474. ->execute();
  1475. // Regenerate the session ID to prevent against session fixation attacks.
  1476. drupal_session_regenerate();
  1477. user_module_invoke('login', $edit, $user);
  1478. }
  1479. /**
  1480. * Custom logout function modified from user_logout.
  1481. */
  1482. function _bakery_user_logout() {
  1483. global $user;
  1484. watchdog('user', 'Session closed for %name.', array('%name' => $user->name));
  1485. // Destroy the current session:
  1486. session_destroy();
  1487. module_invoke_all('user_logout', $user);
  1488. // Load the anonymous user
  1489. $user = drupal_anonymous_user();
  1490. // We want to redirect the user to his original destination.
  1491. $get = $_GET;
  1492. $destination = !empty($get['q']) ? $get['q'] : '';
  1493. unset($get['q']);
  1494. // We append a GET parameter so that the browser reloads the page.
  1495. $get['no_cache'] = time();
  1496. // Build the URL we'll redirect to. We set alias to TRUE so as not to try and
  1497. // hit the unavailable database looking for an alias.
  1498. $url = url($destination, array('query' => $get, 'absolute' => TRUE, 'alias' => TRUE));
  1499. // Remove newlines from the URL to avoid header injection attacks.
  1500. $url = str_replace(array("\n", "\r"), '', $url);
  1501. // We can't use drupal_goto because it assumes it's in a later boot phase. Set
  1502. // the status code to be temporary redirect because of the no_cache time.
  1503. header('Location: ' . $url, TRUE, 307);
  1504. exit();
  1505. }
  1506. /**
  1507. * Only let people with actual problems mess with uncrumble.
  1508. */
  1509. function bakery_uncrumble_access() {
  1510. global $user;
  1511. $access = FALSE;
  1512. if (!$user->uid) {
  1513. if (isset($_SESSION['BAKERY_CRUMBLED']) && $_SESSION['BAKERY_CRUMBLED']) {
  1514. $access = TRUE;
  1515. }
  1516. }
  1517. return $access;
  1518. }
  1519. /**
  1520. * Form to let users repair minor problems themselves.
  1521. */
  1522. function bakery_uncrumble($form, &$form_state) {
  1523. $site_name = variable_get('site_name', 'Drupal');
  1524. $cookie = _bakery_validate_cookie();
  1525. // Analyze.
  1526. $query = db_select('users', 'u')
  1527. ->fields('u', array('uid', 'name', 'mail'))
  1528. ->condition('u.uid', 0, '!=')
  1529. ->condition('u.mail', '', '!=')
  1530. ->where("LOWER(u.mail) = LOWER(:mail)", array(':mail' => $cookie['mail']));
  1531. $result = $query->execute();
  1532. $samemail = $result->fetchObject();
  1533. $query = db_select('users', 'u')
  1534. ->fields('u', array('uid', 'name', 'mail'))
  1535. ->condition('u.uid', 0, '!=')
  1536. ->where("LOWER(u.name) = LOWER(:name)", array(':name' => $cookie['name']));
  1537. $result = $query->execute();
  1538. $samename = $result->fetchObject();
  1539. $form['name'] = array(
  1540. '#type' => 'textfield',
  1541. '#title' => t('Username'),
  1542. '#value' => $cookie['name'],
  1543. '#disabled' => TRUE,
  1544. '#required' => TRUE,
  1545. );
  1546. $form['mail'] = array(
  1547. '#type' => 'item',
  1548. '#title' => t('Email address'),
  1549. '#value' => $cookie['mail'],
  1550. '#required' => TRUE,
  1551. );
  1552. $form['pass'] = array(
  1553. '#type' => 'password',
  1554. '#title' => t('Password'),
  1555. '#description' => t('Enter the password that accompanies your username.'),
  1556. '#required' => TRUE,
  1557. );
  1558. $form['submit'] = array('#type' => 'submit', '#value' => t('Repair account'), '#weight' => 2);
  1559. $help = '';
  1560. $count = db_select('users', 'u')->fields('u', array('uid'))->condition('init', $cookie['init'], '=')
  1561. ->countQuery()->execute()->fetchField();
  1562. if ($count > 1) {
  1563. drupal_set_message(t('Multiple accounts are associated with your master account. This must be fixed manually. <a href="@contact">Please contact the site administrator.</a>', array('%email' => $cookie['mail'], '@contact' => variable_get('bakery_master', 'http://drupal.org/') .'contact')));
  1564. $form['pass']['#disabled'] = TRUE;
  1565. $form['submit']['#disabled'] = TRUE;
  1566. }
  1567. else if ($samename && $samemail && $samename->uid != $samemail->uid) {
  1568. drupal_set_message(t('Both an account with matching name and an account with matching email address exist, but they are different accounts. This must be fixed manually. <a href="@contact">Please contact the site administrator.</a>', array('%email' => $cookie['mail'], '@contact' => variable_get('bakery_master', 'http://drupal.org/') .'contact')));
  1569. $form['pass']['#disabled'] = TRUE;
  1570. $form['submit']['#disabled'] = TRUE;
  1571. }
  1572. else if ($samename) {
  1573. $help = t("An account with a matching username was found. Repairing it will reset the email address to match your master account. If this is the correct account, please enter your %site password.", array('%site' => $site_name));
  1574. // This is a borderline information leak.
  1575. //$form['mail']['#value'] = $samename->mail;
  1576. $form['mail']['#value'] = t('<em>*hidden*</em>');
  1577. $form['mail']['#description'] = t('Will change to %new.', array('%new' => $cookie['mail']));
  1578. }
  1579. else if ($samemail) {
  1580. $help = t("An account with a matching email address was found. Repairing it will reset the username to match your master account. If this is the correct account, please enter your %site password.", array('%site' => $site_name));
  1581. $form['name']['#value'] = $samemail->name;
  1582. $form['name']['#description'] = t('Will change to %new.', array('%new' => $cookie['name']));
  1583. }
  1584. $form['help'] = array('#weight' => -10, '#markup' => $help);
  1585. return $form;
  1586. }
  1587. /**
  1588. * Validation for bakery_uncrumble form.
  1589. */
  1590. function bakery_uncrumble_validate($form, &$form_state) {
  1591. // Have to include password.inc for user_check_password().
  1592. require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
  1593. // We are ignoring blocked status on purpose. The user is being repaired, not logged in.
  1594. $account = user_load_by_name($form_state['values']['name']);
  1595. if (!($account && $account->uid) || !user_check_password($form_state['values']['pass'], $account)) {
  1596. watchdog('bakery', 'Login attempt failed for %user while running uncrumble.', array('%user' => $form_state['values']['name']));
  1597. // Can't pretend that it was the "username or password" so let's be helpful instead.
  1598. form_set_error('pass', t('Sorry, unrecognized password. If you have forgotten your %site password, please <a href="@contact">contact the site administrator.</a>', array('%site' => variable_get('site_name', 'Drupal'), '@contact' => variable_get('bakery_master', 'http://drupal.org/') .'contact')));
  1599. }
  1600. else {
  1601. $form_state['bakery_uncrumble_account'] = $account;
  1602. }
  1603. }
  1604. function bakery_uncrumble_submit($form, &$form_state) {
  1605. $account = $form_state['bakery_uncrumble_account'];
  1606. unset($form_state['bakery_uncrumble_account']);
  1607. $cookie = _bakery_validate_cookie();
  1608. db_update('users')->fields(array('init' => $cookie['init']))
  1609. ->condition('uid', $account->uid, '=')
  1610. ->execute();
  1611. watchdog('bakery', 'uncrumble changed init field for uid %uid from %oldinit to %newinit', array('%oldinit' => $account->init, '%newinit' => $cookie['init'], '%uid' => $account->uid));
  1612. user_save($account, array('name' => $cookie['name'], 'mail' => $cookie['mail']));
  1613. watchdog('bakery', 'uncrumble updated name %name_old to %name_new, mail %mail_old to %mail_new on uid %uid.', array('%name_old' => $account->name, '%name_new' => $cookie['name'], '%mail_old' => $account->mail, '%mail_new' => $cookie['mail'], '%uid' => $account->uid));
  1614. drupal_set_message(t('Your account has been repaired.'));
  1615. $form_state['redirect'] = 'user';
  1616. }
  1617. /**
  1618. * Save UID provided by a slave site. Should only be used on the master site.
  1619. *
  1620. * @param $account
  1621. * A local user object.
  1622. * @param $slave
  1623. * The URL of the slave site.
  1624. * @param $slave_uid
  1625. * The corresponding UID on the slave site.
  1626. */
  1627. function _bakery_save_slave_uid($account, $slave, $slave_uid) {
  1628. $slave_user_exists = db_query_range("SELECT 1 FROM {bakery_user} WHERE uid = :uid AND slave = :slave", 0, 1, array(':uid' => $account->uid, ':slave' => $slave))->fetchField();
  1629. if (variable_get('bakery_is_master', 0) && !empty($slave_uid) && in_array($slave, variable_get('bakery_slaves', array())) && !$slave_user_exists) {
  1630. $row = array(
  1631. 'uid' => $account->uid,
  1632. 'slave' => $slave,
  1633. 'slave_uid' => $slave_uid,
  1634. );
  1635. drupal_write_record('bakery_user', $row);
  1636. };
  1637. }
  1638. /**
  1639. * Form for admins to pull accounts.
  1640. */
  1641. function bakery_pull_form($form, &$form_state) {
  1642. $form['or_email'] = array(
  1643. '#type' => 'radios',
  1644. '#options' => array(
  1645. 0 => t('Username'),
  1646. 1 => t('Username or email'),
  1647. ),
  1648. '#default_value' => 0,
  1649. );
  1650. $form['name'] = array(
  1651. '#type' => 'textfield',
  1652. '#required' => TRUE,
  1653. );
  1654. $form['submit'] = array(
  1655. '#type' => 'submit',
  1656. '#value' => t('Request account'),
  1657. );
  1658. return $form;
  1659. }
  1660. /**
  1661. * Make sure we are not trying to request an existing user.
  1662. */
  1663. function bakery_pull_form_validate($form, &$form_state) {
  1664. $existing_account = user_load_by_name($form_state['values']['name']);
  1665. if (!$existing_account && $form_state['values']['or_email']) {
  1666. $existing_account = user_load_by_mail($form_state['values']['name']);
  1667. }
  1668. // Raise an error in case the account already exists locally.
  1669. if ($existing_account) {
  1670. form_set_error('name', t('Account !link exists.', array('!link' => theme('username', array('account' => $existing_account)))));
  1671. }
  1672. }
  1673. /**
  1674. * If the request succeeds, go to the user page. Otherwise, show an error.
  1675. */
  1676. function bakery_pull_form_submit($form, &$form_state) {
  1677. $result = bakery_request_account($form_state['values']['name'], $form_state['values']['or_email']);
  1678. if ($result === FALSE) {
  1679. drupal_set_message(t("Pulling account %name failed: maybe there is a typo or they don't exist on the master site.", array('%name' => $form_state['values']['name'])), 'error');
  1680. }
  1681. else {
  1682. $form_state['redirect'] = 'user/' . $result;
  1683. }
  1684. }
Error | ELMSLN API

Error

×

Error message

  • Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/elmsln_community/api.elmsln.org/includes/common.inc:2791) in drupal_send_headers() (line 1499 of /var/www/html/elmsln_community/api.elmsln.org/includes/bootstrap.inc).
  • Error: Call to undefined function apc_delete() in DrupalAPCCache->clear() (line 289 of /var/www/html/elmsln_community/api.elmsln.org/sites/all/modules/apc/drupal_apc_cache.inc).
The website encountered an unexpected error. Please try again later.