source: http-authentication/trunk/http-authentication.php @ 522706

Revision 522706, 8.0 KB checked in by dwc, 2 months ago (diff)

Add option to support additional $_SERVER variables in authentication (fixes #1477)

Line 
1<?php
2/*
3Plugin Name: HTTP Authentication
4Version: 4.4
5Plugin URI: http://danieltwc.com/2011/http-authentication-4-0/
6Description: Authenticate users using basic HTTP authentication (<code>REMOTE_USER</code>). This plugin assumes users are externally authenticated, as with <a href="http://www.gatorlink.ufl.edu/">GatorLink</a>.
7Author: Daniel Westermann-Clark
8Author URI: http://danieltwc.com/
9*/
10
11require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'options-page.php');
12
13class HTTPAuthenticationPlugin {
14        var $db_version = 2;
15        var $option_name = 'http_authentication_options';
16        var $options;
17
18        function HTTPAuthenticationPlugin() {
19                $this->options = get_option($this->option_name);
20
21                if (is_admin()) {
22                        $options_page = new HTTPAuthenticationOptionsPage(&$this, $this->option_name, __FILE__, $this->options);
23                        add_action('admin_init', array(&$this, 'check_options'));
24                }
25
26                add_action('login_head', array(&$this, 'add_login_css'));
27                add_action('login_footer', array(&$this, 'add_login_link'));
28                add_action('check_passwords', array(&$this, 'generate_password'), 10, 3);
29                add_action('wp_logout', array(&$this, 'logout'));
30                add_filter('login_url', array(&$this, 'bypass_reauth'));
31                add_filter('show_password_fields', array(&$this, 'allow_wp_auth'));
32                add_filter('allow_password_reset', array(&$this, 'allow_wp_auth'));
33                add_filter('authenticate', array(&$this, 'authenticate'), 10, 3);
34        }
35
36        /*
37         * Check the options currently in the database and upgrade if necessary.
38         */
39        function check_options() {
40                if ($this->options === false || ! isset($this->options['db_version']) || $this->options['db_version'] < $this->db_version) {
41                        if (! is_array($this->options)) {
42                                $this->options = array();
43                        }
44
45                        $current_db_version = isset($this->options['db_version']) ? $this->options['db_version'] : 0;
46                        $this->upgrade($current_db_version);
47                        $this->options['db_version'] = $this->db_version;
48                        update_option($this->option_name, $this->options);
49                }
50        }
51
52        /*
53         * Upgrade options as needed depending on the current database version.
54         */
55        function upgrade($current_db_version) {
56                $default_options = array(
57                        'allow_wp_auth' => false,
58                        'auth_label' => 'HTTP authentication',
59                        'login_uri' => htmlspecialchars_decode(wp_login_url()),
60                        'logout_uri' => remove_query_arg('_wpnonce', htmlspecialchars_decode(wp_logout_url())),
61                        'additional_server_keys' => '',
62                        'auto_create_user' => false,
63                        'auto_create_email_domain' => '',
64                );
65
66                if ($current_db_version < 1) {
67                        foreach ($default_options as $key => $value) {
68                                // Handle migrating existing options from before we stored a db_version
69                                if (! isset($this->options[$key])) {
70                                        $this->options[$key] = $value;
71                                }
72                        }
73                }
74        }
75
76        function add_login_css() {
77?>
78<style type="text/css">
79p#http-authentication-link {
80  width: 100%;
81  height: 4em;
82  text-align: center;
83  margin-top: 2em;
84}
85p#http-authentication-link a {
86  margin: 0 auto;
87  float: none;
88}
89</style>
90<?php
91        }
92
93        /*
94         * Add a link to the login form to initiate external authentication.
95         */
96        function add_login_link() {
97                global $redirect_to;
98
99                $login_uri = $this->_generate_uri($this->options['login_uri'], wp_login_url($redirect_to));
100                $auth_label = $this->options['auth_label'];
101
102                echo "\t" . '<p id="http-authentication-link"><a class="button-primary" href="' . htmlspecialchars($login_uri) . '">Log In with ' . htmlspecialchars($auth_label) . '</a></p>' . "\n";
103        }
104
105        /*
106         * Generate a password for the user. This plugin does not require the
107         * administrator to enter this value, but we need to set it so that user
108         * creation and editing works.
109         */
110        function generate_password($username, $password1, $password2) {
111                if (! $this->allow_wp_auth()) {
112                        $password1 = $password2 = wp_generate_password();
113                }
114        }
115
116        /*
117         * Logout the user by redirecting them to the logout URI.
118         */
119        function logout() {
120                $logout_uri = $this->_generate_uri($this->options['logout_uri'], home_url());
121
122                wp_redirect($logout_uri);
123                exit();
124        }
125
126        /*
127         * Remove the reauth=1 parameter from the login URL, if applicable. This allows
128         * us to transparently bypass the mucking about with cookies that happens in
129         * wp-login.php immediately after wp_signon when a user e.g. navigates directly
130         * to wp-admin.
131         */
132        function bypass_reauth($login_url) {
133                $login_url = remove_query_arg('reauth', $login_url);
134
135                return $login_url;
136        }
137
138        /*
139         * Can we fallback to built-in WordPress authentication?
140         */
141        function allow_wp_auth() {
142                return (bool) $this->options['allow_wp_auth'];
143        }
144
145        /*
146         * Authenticate the user, first using the external authentication source.
147         * If allowed, fall back to WordPress password authentication.
148         */
149        function authenticate($user, $username, $password) {
150                $user = $this->check_remote_user();
151
152                if (! is_wp_error($user)) {
153                        // User was authenticated via REMOTE_USER
154                        $user = new WP_User($user->ID);
155                }
156                else {
157                        // REMOTE_USER is invalid; now what?
158
159                        if (! $this->allow_wp_auth()) {
160                                // Bail with the WP_Error when not falling back to WordPress authentication
161                                wp_die($user);
162                        }
163
164                        // Fallback to built-in hooks (see wp-includes/user.php)
165                }
166
167                return $user;
168        }
169
170        /*
171         * If the REMOTE_USER or REDIRECT_REMOTE_USER evironment variable is set, use it
172         * as the username. This assumes that you have externally authenticated the user.
173         */
174        function check_remote_user() {
175                $username = '';
176
177                $server_keys = $this->_get_server_keys();
178                foreach ($server_keys as $server_key) {
179                        if (! empty($_SERVER[$server_key])) {
180                                $username = $_SERVER[$server_key];
181                        }
182                }
183
184                if (! $username) {
185                        return new WP_Error('empty_username', '<strong>ERROR</strong>: No user found in server variables.');
186                }
187
188                // Create new users automatically, if configured
189                $user = get_userdatabylogin($username);
190                if (! $user)  {
191                        if ((bool) $this->options['auto_create_user']) {
192                                $user = $this->_create_user($username);
193                        }
194                        else {
195                                // Bail out to avoid showing the login form
196                                $user = new WP_Error('authentication_failed', __('<strong>ERROR</strong>: Invalid username or incorrect password.'));
197                        }
198                }
199
200                return $user;
201        }
202
203        /*
204         * Return the list of $_SERVER keys that we will check for a username. By
205         * default, these are REMOTE_USER and REDIRECT_REMOTE_USER. Additional keys
206         * can be configured from the options page.
207         */
208        function _get_server_keys() {
209                $server_keys = array('REMOTE_USER', 'REDIRECT_REMOTE_USER');
210
211                $additional_server_keys = $this->options['additional_server_keys'];
212                if (! empty($additional_server_keys)) {
213                        $keys = preg_split('/,\s*/', $additional_server_keys);
214                        $server_keys = array_merge($server_keys, $keys);
215                }
216
217                return $server_keys;
218        }
219
220        /*
221         * Create a new WordPress account for the specified username.
222         */
223        function _create_user($username) {
224                $password = wp_generate_password();
225                $email_domain = $this->options['auto_create_email_domain'];
226
227                require_once(WPINC . DIRECTORY_SEPARATOR . 'registration.php');
228                $user_id = wp_create_user($username, $password, $username . ($email_domain ? '@' . $email_domain : ''));
229                $user = get_user_by('id', $user_id);
230
231                return $user;
232        }
233
234        /*
235         * Fill the specified URI with the site URI and the specified return location.
236         */
237        function _generate_uri($uri, $redirect_to) {
238                // Support tags for staged deployments
239                $base = $this->_get_base_url();
240
241                $tags = array(
242                        'host' => $_SERVER['HTTP_HOST'],
243                        'base' => $base,
244                        'site' => home_url(),
245                        'redirect' => $redirect_to,
246                );
247
248                foreach ($tags as $tag => $value) {
249                        $uri = str_replace('%' . $tag . '%', $value, $uri);
250                        $uri = str_replace('%' . $tag . '_encoded%', urlencode($value), $uri);
251                }
252
253                // Support previous versions with only the %s tag
254                if (strstr($uri, '%s') !== false) {
255                        $uri = sprintf($uri, urlencode($redirect_to));
256                }
257
258                return $uri;
259        }
260
261        /*
262         * Return the base domain URL based on the WordPress home URL.
263         */
264        function _get_base_url() {
265                $home = parse_url(home_url());
266
267                $base = home_url();
268                foreach (array('path', 'query', 'fragment') as $key) {
269                        if (! isset($home[$key])) continue;
270                        $base = str_replace($home[$key], '', $base);
271                }
272
273                return $base;
274        }
275}
276
277// Load the plugin hooks, etc.
278$http_authentication_plugin = new HTTPAuthenticationPlugin();
279?>
Note: See TracBrowser for help on using the repository browser.