/[drupal]/contributions/modules/singlesignon/singlesignon.module
ViewVC logotype

Contents of /contributions/modules/singlesignon/singlesignon.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.33 - (show annotations) (download) (as text)
Mon May 19 03:08:12 2008 UTC (18 months, 1 week ago) by wayland76
Branch: MAIN
CVS Tags: HEAD
Changes since 1.32: +57 -110 lines
File MIME type: text/x-php
-	Drupal 6 port of cleanups in http://drupal.org/node/256015
1 <?php
2 // $Id$
3
4 /**
5 * @file
6 * Enables "Single Sign-Ons" between related Drupal sites on one server
7 * with a shared database.
8 */
9
10 /**
11 * @defgroup singlesignon_core Core functions.
12 * @{
13 */
14
15 include_once('sessions_extra.inc');
16
17 /**
18 * Implementation of hook_boot().
19 *
20 * Automatically invoked for each hit to a Drupal website if the
21 * "singlesignon" module is enabled.
22 */
23 function singlesignon_boot() {
24 global $user, $_singlesignon_bot_matches;
25
26 $variable_defaults = array(
27 'useragents_case' => '/Google|Yahoo|BlogPulse|ia_archiver|Pingdom|Teoma|Netcraft|Mnogosearch|page.store|libwww.perl|libcurl|del.icio.us|wiji/',
28 'useragents_nocase' => '/bot|spider/i',
29 // Digg
30 'client_IP' => '/64.191.203.34/',
31 // Allows remote cron, feed, and rss
32 'target_url' => '/\/cron.php$|\/feed$|\/rss.xml$/',
33 );
34
35 if (variable_get('singlesignon_use_domain_module', 0) && function_exists('domain_default')) {
36 $master_url = _singlesignon_get_default_domain();
37 }
38 else {
39 $master_url = variable_get('singlesignon_master_url', '');
40 }
41 $_singlesignon_bot_matches = variable_get('singlesignon_bot_matches', $variable_defaults);
42
43 // If no master URL is set or we are serving a bot, do nothing.
44 if (!$master_url || _singlesignon_is_bot()) {
45 return;
46 }
47
48 $extra_base_url = _singlesignon_base_url();
49 if (empty($_SESSION['singlesignon_prior_sid']) || $_SESSION['singlesignon_prior_sid'] != session_id()) {
50 if ($extra_base_url != $master_url) {
51 if (!empty($_GET['singlesignon_dest'])) {
52 // User was sent back to a slave site by this module but doesn't have
53 // a session. They clearly don't have cookies enabled.
54 drupal_set_message(t('Cookies are required.'), 'error');
55 return;
56 }
57 // This is the user's first hit to a slave site. Take note of their
58 // session ID, since that's how we tell if they've been here or not.
59 // Then go to the master site to see if they are logged in over there.
60 $_SESSION['singlesignon_prior_sid'] = session_id();
61
62 _singlesignon_goto_url($master_url, 'singlesignon/initial_check');
63 }
64 }
65
66 // arg() only available if bootstrap has reached PATH.
67 drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);
68
69 switch (arg(0)) {
70 case 'logout':
71 if ($user->uid) {
72 _singlesignon_session_logout($user->uid);
73 }
74 return;
75
76 case 'singlesignon':
77 if ($extra_base_url == $master_url) {
78 _singlesignon_master($master_url, arg(0), arg(1));
79 }
80 return;
81
82 default:
83 $op = isset($_POST['op']) ? $_POST['op'] : '';
84 if (function_exists('t') ? $op == t('Log in') : $op == 'Log in') {
85 // User is in the middle of logging in. Can't do the master/slave
86 // checking yet because the login process happens after this module
87 // called. Set a flag telling us to do the master/slave checking
88 // once the login process is done.
89 $_SESSION['singlesignon_just_loggged_in'] = TRUE;
90 return;
91 }
92 }
93
94 if (!empty($_SESSION['singlesignon_just_loggged_in'])) {
95 unset($_SESSION['singlesignon_just_loggged_in']);
96 if ($extra_base_url == $master_url) {
97 if (!$user->uid || empty($_SESSION['singlesignon_slave_sessions']) || !is_array($_SESSION['singlesignon_slave_sessions']) || !_singlesignon_validate_sid($_SESSION['singlesignon_slave_sessions'])) {
98 // Login failed or the user has no sessions on any slaves.
99 return;
100 }
101 // User just logged into the master server. Update the slave sessions'
102 // user ID's to be the user ID they have on the master server.
103 _singlesignon_session_update_all_uids($user->uid);
104 return;
105 }
106 else if ($user->uid) {
107 // Tell the master site the user just logged in.
108 _singlesignon_goto_url($master_url, 'singlesignon/login');
109 }
110 }
111 }
112
113 /**
114 * Contains the processes needed by the master server.
115 */
116 function _singlesignon_master($master_url, $arg0, $arg1) {
117 global $user;
118
119 if (empty($_GET['singlesignon_dest']) || is_array($_GET['singlesignon_dest']) || empty($_GET['slave_session']) || is_array($_GET['slave_session']) || !_singlesignon_validate_sid($_GET['slave_session'])) {
120 echo 'Invalid request.';
121 exit;
122 }
123
124 if (!isset($_SESSION['singlesignon_slave_sessions'])) {
125 $_SESSION['singlesignon_slave_sessions'] = array($_GET['slave_session']);
126 }
127 elseif (!in_array($_GET['slave_session'], $_SESSION['singlesignon_slave_sessions'])) {
128 $_SESSION['singlesignon_slave_sessions'][] = $_GET['slave_session'];
129 }
130
131 switch ($arg1) {
132 case 'initial_check':
133 // User had first hit on given slave site so came here.
134 if ($user->uid) {
135 // User is already logged into the master server. Update the slave
136 // session's user ID to be the user ID they have on the master server.
137 _singlesignon_session_update_user();
138 }
139 _singlesignon_goto($_GET['singlesignon_dest']);
140 break;
141
142 case 'login':
143 // User is coming to the master site to say they just logged on to a
144 // slave. Set master site's user ID to be their one from the slave.
145 _singlesignon_session_login($user);
146 _singlesignon_goto($_GET['singlesignon_dest']);
147 }
148 }
149
150 /**
151 * @} End of "defgroup singlesignon_core".
152 */
153
154 /**
155 * @defgroup singlesignon_helpers Helper functions.
156 * @{
157 */
158
159 /**
160 * Sets up the URL and goes to it
161 */
162 function _singlesignon_goto_url($master_url, $url) {
163 // url() only available if bootstrap has reached FULL.
164 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
165
166 $query = 'slave_session='. session_id() .'&singlesignon_dest='. _singlesignon_get_dest();
167 _singlesignon_goto($master_url . url($url, $query));
168 }
169
170 /**
171 * Gets the base url and fixess it up a bit
172 */
173 function _singlesignon_base_url() {
174 return preg_replace('@^(https?://[^/]+).*@', '\\1', $GLOBALS['base_url']);
175 }
176
177 /**
178 * Combines $base_url and request_uri() in a safe, portable way.
179 */
180 function _singlesignon_get_dest() {
181 $scheme_authority = preg_replace('@^(https?://[^/]+).*@', '\\1', $GLOBALS['base_url']);
182 return rawurlencode($scheme_authority . request_uri());
183 }
184
185 /**
186 * Sanitizes the given url then forwards the user to it.
187 *
188 * This function is used because drupal_goto() doesn't allow redirects to
189 * other sites and uses $_REQUEST['destination'] at inappropriate times.
190 */
191 function _singlesignon_goto($uri) {
192 header('Location: '. str_replace(array("\r", "\n"), '', $uri));
193 exit;
194 }
195
196 /**
197 * Makes sure sid values are legitimate.
198 *
199 * This is a safeguard against SQL injection and overflow attacks.
200 */
201 function _singlesignon_validate_sid($sid) {
202 if (is_array($sid)) {
203 if (count($sid) > 100) {
204 return FALSE;
205 }
206 foreach ($sid as $value) {
207 if (!ereg('^[A-Za-z0-9]{1,100}$', $value)) {
208 return FALSE;
209 }
210 }
211 }
212 else {
213 if (!ereg('^[A-Za-z0-9]{1,100}$', $sid)) {
214 return FALSE;
215 }
216 }
217 return TRUE;
218 }
219
220 /**
221 * Internal function: Tries to determine whether the client is a bot or not.
222 */
223 function _singlesignon_is_bot() {
224 global $_singlesignon_bot_matches;
225
226 return (
227 preg_match($_singlesignon_bot_matches['useragents_nocase'], $_SERVER['HTTP_USER_AGENT'])
228 || preg_match($_singlesignon_bot_matches['useragents_case'], $_SERVER['HTTP_USER_AGENT'])
229 || preg_match($_singlesignon_bot_matches['client_IP'], ip_address())
230 || preg_match($_singlesignon_bot_matches['target_url'], request_uri())
231 );
232 }
233
234 /**
235 * 3rd party integration: get master url from Domain Access module.
236 */
237 function _singlesignon_get_default_domain() {
238 $domain = domain_default();
239 return $domain['scheme'] .'://'. $domain['subdomain'];
240 }
241
242 + /**
243 + * @} End of "defgroup singlesignon_helpers".
244 + */
245
246 /**
247 * Implementation of hook_menu().
248 */
249 function singlesignon_menu() {
250 $items['admin/settings/singlesignon'] = array(
251 'title' => 'Shared Sign-on',
252 'description' => 'Shares users and sign-ons between sites (previously called "Single Sign-on"',
253 'page callback' => 'drupal_get_form',
254 'page arguments' => array('singlesignon_admin_settings'),
255 'access arguments' => array('access administration pages'),
256 'type' => MENU_NORMAL_ITEM,
257 );
258
259 return $items;
260 }
261
262 /**
263 * Provides user interface necessary to administer this module's settings.
264 */
265 function singlesignon_admin_settings() {
266 $form = array();
267 $use_domain = variable_get('singlesignon_use_domain_module', 0);
268 if (module_exists('domain')) {
269 $form['singlesignon_use_domain_module'] = array(
270 '#type' => 'checkbox',
271 '#title' => t('Integrate with Domain module'),
272 '#default_value' => $use_domain,
273 '#description' => t('Synchronizes the master URL with the configured root domain of the <a href="!domain-settings">Domain</a> module.', array('!domain-settings' => url('admin/build/domain'))),
274 );
275 }
276 $master_url = $use_domain ? _singlesignon_get_default_domain() : variable_get('singlesignon_master_url', '');
277 $form['singlesignon_master_url'] = array(
278 '#type' => 'textfield',
279 '#title' => t('Master URL'),
280 '#default_value' => $master_url,
281 '#description' => t('Enter the URL of your master Shared Sign-On server, in the form of <b>http://www.example.com</b>. Leave the trailing slash off. Do NOT include any path information except to the root of the drupal install.'),
282 '#maxlength' => '300',
283 '#size' => '80',
284 '#disabled' => $use_domain,
285 );
286
287 $form['singlesignon_bot_matches'] = array(
288 '#type' => 'fieldset',
289 '#title' => t('Bot matches'),
290 '#collapsible' => TRUE,
291 '#collapsed' => TRUE,
292 '#tree' => TRUE,
293 '#description' => t('Single sign-on does not play well with bots (ie. search engines).
294 The data below will hopefully help the single sign-on module to
295 recognise bots and let them through (ie. it plays nicely with the recognised bots).'),
296 );
297 $form['singlesignon_bot_matches']['useragents_case'] = array(
298 '#type' => 'textarea',
299 '#title' => t('Case-sensitive User Agents'),
300 '#rows' => 5,
301 '#cols' => 40,
302 '#default_value' => _singlesignon_get_bm_variable('useragents_case'),
303 '#description' => t('A list of case-sensitive strings that might match a referrer. <b>This is the recommended method</b>'),
304 );
305 $form['singlesignon_bot_matches']['useragents_nocase'] = array(
306 '#type' => 'textarea',
307 '#title' => t('Case-insensitive User Agents'),
308 '#rows' => 5,
309 '#cols' => 40,
310 '#default_value' => _singlesignon_get_bm_variable('useragents_nocase'),
311 '#description' => t('A list of case-INsensitive strings that might match a referrer'),
312 );
313 $form['singlesignon_bot_matches']['client_IP'] = array(
314 '#type' => 'textarea',
315 '#title' => t('Client IP'),
316 '#rows' => 5,
317 '#cols' => 40,
318 '#default_value' => _singlesignon_get_bm_variable('client_IP'),
319 '#description' => t("A list of IPs that might match the bot's IP"),
320 );
321 $form['singlesignon_bot_matches']['target_url'] = array(
322 '#type' => 'textarea',
323 '#title' => t('Target URL'),
324 '#rows' => 5,
325 '#cols' => 40,
326 '#default_value' => _singlesignon_get_bm_variable('target_url'),
327 '#description' => t('A list of case-sensitive strings that might match a referrer. <b>Not recommended</b> (in general; we have a few specific cases here)'),
328 );
329 return system_settings_form($form);
330 }
331
332 /**
333 * Internal function for use of singlesignon_admin_settings; turns | separated string into \n separated string.
334 *
335 * @param $variable
336 * The short name of the singlesignon bot matching variable
337 * @param $text
338 * The default text for the variable
339 */
340 function _singlesignon_get_bm_variable($variable) {
341 return (preg_replace(
342 array("/^\/(.*?)\/i?$/", "/\|/"),
343 array("$1", "\n"),
344 $GLOBALS['_singlesignon_bot_matches'][$variable]
345 ));
346 }
347
348 /**
349 * Hook for validating a form; verifies the values for singlesignon bot recognition.
350 */
351 function singlesignon_admin_settings_validate($form, &$form_state) {
352 $s = array();
353 $s['useragents_case'] = _singlesignon_verify_value($form_state['values'], 'useragents_case');
354 $s['useragents_nocase'] = _singlesignon_verify_value($form_state['values'], 'useragents_nocase', '', 'i');
355 $s['client_IP'] = _singlesignon_verify_value($form_state['values'], 'client_IP', '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}');
356 $s['target_url'] = _singlesignon_verify_value($form_state['values'], 'target_url', '\\\\\/[A-Za-z0-9_\.\*\/\\\\-]*\$$');
357
358 $form_state['values']['singlesignon_bot_matches'] = $s;
359 }
360
361 /**
362 * Internal function: Verifies one singlesignon bot recognition value.
363 *
364 * @param $form_values:
365 * The values we're validating
366 * @param $value:
367 * The name of the value we're validation
368 * @param $allowed:
369 * A regex specifying what values are allowed
370 * @param $extras:
371 * The regex parameters (ie. 'i' is case insensitive)
372 */
373 function _singlesignon_verify_value($form_values, $value, $allowed = '', $extras = '') {
374 if ($allowed == '') {
375 $allowed = '[A-Za-z0-9_\.]*';
376 }
377 if ($form_values['singlesignon_bot_matches'][$value] != '') {
378 $vals = explode("\n", $form_values['singlesignon_bot_matches'][$value]);
379 foreach ($vals as $val) {
380 $val = preg_replace("/^\s*(.*?)\s*$/", "$1", $val);
381 if (preg_match("/^$allowed$/", $val)) {
382 $rvals[] = $val;
383 }
384 else {
385 form_set_error('', t("The strings in @value contain non-word characters (we allow @allowed at the moment, and '@wrong-value' is a problem)", array('@value' => $value, '@allowed' => $allowed, '@wrong-value' => $val)));
386 }
387 }
388 return ('/'. join('|', $rvals) ."/$extras");
389 }
390 return ($form_values['singlesignon_bot_matches'][$value]);
391 }
392
393

  ViewVC Help
Powered by ViewVC 1.1.2