Don't look for pipes in the root node.
[lhc/web/wiklou.git] / includes / ExternalUser.php
1 <?php
2 /**
3 * Authentication with a foreign database
4 *
5 * Copyright © 2009 Aryeh Gregor
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * http://www.gnu.org/copyleft/gpl.html
21 */
22
23 /**
24 * @defgroup ExternalUser ExternalUser
25 */
26
27 /**
28 * A class intended to supplement, and perhaps eventually replace, AuthPlugin.
29 * See: http://www.mediawiki.org/wiki/ExternalAuth
30 *
31 * The class represents a user whose data is in a foreign database. The
32 * database may have entirely different conventions from MediaWiki, but it's
33 * assumed to at least support the concept of a user id (possibly not an
34 * integer), a user name (possibly not meeting MediaWiki's username
35 * requirements), and a password.
36 *
37 * @ingroup ExternalUser
38 */
39 abstract class ExternalUser {
40 protected function __construct() {}
41
42 /**
43 * Wrappers around initFrom*().
44 */
45
46 /**
47 * @param $name string
48 * @return mixed ExternalUser, or false on failure
49 */
50 public static function newFromName( $name ) {
51 global $wgExternalAuthType;
52 if ( is_null( $wgExternalAuthType ) ) {
53 return false;
54 }
55 $obj = new $wgExternalAuthType;
56 if ( !$obj->initFromName( $name ) ) {
57 return false;
58 }
59 return $obj;
60 }
61
62 /**
63 * @param $id string
64 * @return mixed ExternalUser, or false on failure
65 */
66 public static function newFromId( $id ) {
67 global $wgExternalAuthType;
68 if ( is_null( $wgExternalAuthType ) ) {
69 return false;
70 }
71 $obj = new $wgExternalAuthType;
72 if ( !$obj->initFromId( $id ) ) {
73 return false;
74 }
75 return $obj;
76 }
77
78 /**
79 * @return mixed ExternalUser, or false on failure
80 */
81 public static function newFromCookie() {
82 global $wgExternalAuthType;
83 if ( is_null( $wgExternalAuthType ) ) {
84 return false;
85 }
86 $obj = new $wgExternalAuthType;
87 if ( !$obj->initFromCookie() ) {
88 return false;
89 }
90 return $obj;
91 }
92
93 /**
94 * Creates the object corresponding to the given User object, assuming the
95 * user exists on the wiki and is linked to an external account. If either
96 * of these is false, this will return false.
97 *
98 * This is a wrapper around newFromId().
99 *
100 * @param $user User
101 * @return mixed ExternalUser or false
102 */
103 public static function newFromUser( $user ) {
104 global $wgExternalAuthType;
105 if ( is_null( $wgExternalAuthType ) ) {
106 # Short-circuit to avoid database query in common case so no one
107 # kills me
108 return false;
109 }
110
111 $dbr = wfGetDB( DB_SLAVE );
112 $id = $dbr->selectField( 'external_user', 'eu_external_id',
113 array( 'eu_local_id' => $user->getId() ), __METHOD__ );
114 if ( $id === false ) {
115 return false;
116 }
117 return self::newFromId( $id );
118 }
119
120 /**
121 * Given a name, which is a string exactly as input by the user in the
122 * login form but with whitespace stripped, initialize this object to be
123 * the corresponding ExternalUser. Return true if successful, otherwise
124 * false.
125 *
126 * @param $name string
127 * @return bool Success?
128 */
129 protected abstract function initFromName( $name );
130
131 /**
132 * Given an id, which was at some previous point in history returned by
133 * getId(), initialize this object to be the corresponding ExternalUser.
134 * Return true if successful, false otherwise.
135 *
136 * @param $id string
137 * @return bool Success?
138 */
139 protected abstract function initFromId( $id );
140
141 /**
142 * Try to magically initialize the user from cookies or similar information
143 * so he or she can be logged in on just viewing the wiki. If this is
144 * impossible to do, just return false.
145 *
146 * TODO: Actually use this.
147 *
148 * @return bool Success?
149 */
150 protected function initFromCookie() {
151 return false;
152 }
153
154 /**
155 * This must return some identifier that stably, uniquely identifies the
156 * user. In a typical web application, this could be an integer
157 * representing the "user id". In other cases, it might be a string. In
158 * any event, the return value should be a string between 1 and 255
159 * characters in length; must uniquely identify the user in the foreign
160 * database; and, if at all possible, should be permanent.
161 *
162 * This will only ever be used to reconstruct this ExternalUser object via
163 * newFromId(). The resulting object in that case should correspond to the
164 * same user, even if details have changed in the interim (e.g., renames or
165 * preference changes).
166 *
167 * @return string
168 */
169 abstract public function getId();
170
171 /**
172 * This must return the name that the user would normally use for login to
173 * the external database. It is subject to no particular restrictions
174 * beyond rudimentary sanity, and in particular may be invalid as a
175 * MediaWiki username. It's used to auto-generate an account name that
176 * *is* valid for MediaWiki, either with or without user input, but
177 * basically is only a hint.
178 *
179 * @return string
180 */
181 abstract public function getName();
182
183 /**
184 * Is the given password valid for the external user? The password is
185 * provided in plaintext.
186 *
187 * @param $password string
188 * @return bool
189 */
190 abstract public function authenticate( $password );
191
192 /**
193 * Retrieve the value corresponding to the given preference key. The most
194 * important values are:
195 *
196 * - emailaddress
197 * - language
198 *
199 * The value must meet MediaWiki's requirements for values of this type,
200 * and will be checked for validity before use. If the preference makes no
201 * sense for the backend, or it makes sense but is unset for this user, or
202 * is unrecognized, return null.
203 *
204 * $pref will never equal 'password', since passwords are usually hashed
205 * and cannot be directly retrieved. authenticate() is used for this
206 * instead.
207 *
208 * TODO: Currently this is only called for 'emailaddress'; generalize! Add
209 * some config option to decide which values are grabbed on user
210 * initialization.
211 *
212 * @param $pref string
213 * @return mixed
214 */
215 public function getPref( $pref ) {
216 return null;
217 }
218
219 /**
220 * Return an array of identifiers for all the foreign groups that this user
221 * has. The identifiers are opaque objects that only need to be
222 * specifiable by the administrator in LocalSettings.php when configuring
223 * $wgAutopromote. They may be, for instance, strings or integers.
224 *
225 * TODO: Support this in $wgAutopromote.
226 *
227 * @return array
228 */
229 public function getGroups() {
230 return array();
231 }
232
233 /**
234 * Given a preference key (e.g., 'emailaddress'), provide an HTML message
235 * telling the user how to change it in the external database. The
236 * administrator has specified that this preference cannot be changed on
237 * the wiki, and may only be changed in the foreign database. If no
238 * message is available, such as for an unrecognized preference, return
239 * false.
240 *
241 * TODO: Use this somewhere.
242 *
243 * @param $pref string
244 * @return mixed String or false
245 */
246 public static function getPrefMessage( $pref ) {
247 return false;
248 }
249
250 /**
251 * Set the given preference key to the given value. Two important
252 * preference keys that you might want to implement are 'password' and
253 * 'emailaddress'. If the set fails, such as because the preference is
254 * unrecognized or because the external database can't be changed right
255 * now, return false. If it succeeds, return true.
256 *
257 * If applicable, you should make sure to validate the new value against
258 * any constraints the external database may have, since MediaWiki may have
259 * more limited constraints (e.g., on password strength).
260 *
261 * TODO: Untested.
262 *
263 * @param $key string
264 * @param $value string
265 * @return bool Success?
266 */
267 public static function setPref( $key, $value ) {
268 return false;
269 }
270
271 /**
272 * Create a link for future reference between this object and the provided
273 * user_id. If the user was already linked, the old link will be
274 * overwritten.
275 *
276 * This is part of the core code and is not overridable by specific
277 * plugins. It's in this class only for convenience.
278 *
279 * @param $id int user_id
280 */
281 public final function linkToLocal( $id ) {
282 $dbw = wfGetDB( DB_MASTER );
283 $dbw->replace( 'external_user',
284 array( 'eu_local_id', 'eu_external_id' ),
285 array( 'eu_local_id' => $id,
286 'eu_external_id' => $this->getId() ),
287 __METHOD__ );
288 }
289
290 /**
291 * Check whether this external user id is already linked with
292 * a local user.
293 * @return Mixed User if the account is linked, Null otherwise.
294 */
295 public final function getLocalUser(){
296 $dbr = wfGetDb( DB_SLAVE );
297 $row = $dbr->selectRow(
298 'external_user',
299 '*',
300 array( 'eu_external_id' => $this->getId() )
301 );
302 return $row
303 ? User::newFromId( $row->eu_local_id )
304 : null;
305 }
306
307 }