1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
4 include_once MTRACK_INC_DIR . '/auth/http.php';
5 include_once MTRACK_INC_DIR . '/auth/openid.php';
7 interface IMTrackAuth {
8 /** Returns the authenticated user, or null if authentication is
10 function authenticate();
12 /** Called if the user is not authenticated as a registered
13 * user and if the page requires it.
14 * Should initiate whatever is appropriate to begin the authentication
15 * process (eg: displaying logon information).
16 * You may assume that no output has been sent to the client at
17 * the time that this function is called.
18 * Returns null if not supported, throw an exception if failed,
19 * else return a the authenticated user (if it can be determined
20 * by the time the function returns).
21 * If an alternate login page is displayed, this function should
22 * exit instead of returning.
24 function doAuthenticate($force = false);
26 /** Returns a list of available groups.
27 * Returns null if not supported, throw an exception if failed. */
28 function enumGroups();
30 /** Returns a list of groups that a given user belongs to.
31 * Returns null if not supported, throw an exception if failed. */
32 function getGroups($username);
34 /** Adds a user to a group.
35 * Returns null if not supported, throw an exception if failed,
36 * return true if succeeded */
37 function addToGroup($username, $groupname);
39 /** Removes a user from a group.
40 * Returns null if not supported, throw an exception if failed,
41 * return true if succeeded */
42 function removeFromGroup($username, $groupname);
44 /** Returns userdata for a given user id
45 * Some authentication mechanisms outsource the storage of user data.
46 * This function returns null if no additional information is available,
47 * or an array containing the following keys:
48 * email - the email address
49 * fullname - the full name
50 * avatar - URL to an avatar image
52 function getUserData($username);
57 static $stack = array();
58 static $mechs = array();
59 static $group_assoc = array();
61 public static function registerMech(IMTrackAuth $mech) {
62 self::$mechs[] = $mech;
66 public static function su($user) {
67 if (!strlen($user)) throw new Exception("invalid user");
68 array_unshift(self::$stack, $user);
71 /** returns the instance of an auth mechanism given its class name */
72 public static function getMech($name) {
73 foreach (self::$mechs as $inst) {
74 if ($inst instanceof $name) {
81 /** drop identity set by last su */
82 public static function drop() {
83 if (count(self::$stack) == 0) {
84 throw new Exception("no privs to drop");
86 return array_shift(self::$stack);
89 /** returns the authenticated user, or null if authentication
91 public static function authenticate() {
92 foreach (self::$mechs as $mech) {
93 $name = $mech->authenticate();
99 /* always fall back on the unix username when running from
101 if (php_sapi_name() == 'cli') {
102 static $envs = array('MTRACK_LOGNAME', 'LOGNAME', 'USER');
103 foreach ($envs as $name) {
104 if (isset($_ENV[$name])) {
108 } elseif (count(self::$mechs) == 0 &&
109 MTrackConfig::get('core', 'admin_party') == 1
110 && ($_SERVER['REMOTE_ADDR'] == '127.0.0.1' ||
111 $_SERVER['REMOTE_ADDR'] == '::1')) {
118 public static function isAuthConfigured() {
119 return count(self::$mechs) ? true : false;
122 /** determine the current identity. If doauth is true (default),
123 * then the authentication hook will be invoked */
124 public static function whoami($doauth = true) {
125 if (count(self::$stack) == 0 && $doauth) {
127 $who = self::authenticate();
129 foreach (self::$mechs as $mech) {
130 $who = $mech->doAuthenticate();
139 } catch (Exception $e) {
140 if (php_sapi_name() != 'cli') {
141 header('HTTP/1.0 401 Unauthorized');
142 echo "<h1>Not authorized</h1>";
143 echo htmlentities($e->getMessage());
145 echo " ** Not authorized\n\n";
146 echo $e->getMessage() . "\n";
148 error_log($e->getMessage());
152 if (!count(self::$stack)) {
155 return self::$stack[0];
158 static function getUserClass($user = null) {
159 if ($user === null) {
160 $user = self::whoami();
162 if (MTrackConfig::get('core', 'admin_party') == 1
163 && $user == 'adminparty'
164 && ($_SERVER['REMOTE_ADDR'] == '127.0.0.1' ||
165 $_SERVER['REMOTE_ADDR'] == '::1')) {
169 $user_class = MTrackConfig::get('user_classes', $user);
170 if ($user_class === null) {
171 if ($user == 'anonymous') {
174 return 'authenticated';
179 static $userdata_cache = array();
180 static function getUserData($username) {
181 $username = mtrack_canon_username($username);
183 if (array_key_exists($username, self::$userdata_cache)) {
184 return self::$userdata_cache[$username];
187 foreach (self::$mechs as $mech) {
188 $data = $mech->getUserData($username);
189 if ($data !== null) {
193 if ($data === null) {
194 foreach (MTrackDB::q(
195 'select fullname, email from userinfo where userid = ?',
196 $username)->fetchAll(PDO::FETCH_ASSOC) as $row) {
201 if ($data === null) {
203 'fullname' => $username
207 if (!isset($data['email'])) {
208 if (preg_match('/<([a-z0-9_.+=-]+@[a-z0-9.-]+)>/', $username, $M)) {
209 // username contains an email address
210 $data['email'] = $M[1];
211 } else if (preg_match('/^([a-z0-9_.+=-]+@[a-z0-9.-]+)$/', $username)) {
212 // username is an email address
213 $data['email'] = $username;
214 } else if (preg_match('/^[a-z0-9_.+=-]+$/', $username)) {
215 // valid localpart; assume a domain and construct an email address
216 $dom = MTrackConfig::get('core', 'default_email_domain');
218 $data['email'] = $username . '@' . $dom;
223 self::$userdata_cache[$username] = $data;
228 /* enumerates possible groups from the auth plugin layer */
229 static function enumGroups() {
231 foreach (self::$mechs as $mech) {
232 $g = $mech->enumGroups();
234 foreach ($g as $i => $grp) {
235 if (is_integer($i)) {
236 $groups[$grp] = $grp;
243 /* merge in our project groups */
244 foreach (MTrackDB::q('select project, g.name, p.name from groups g left join projects p on g.project = p.projid')
246 $gid = "project:$row[0]:$row[1]";
247 $groups[$gid] = "$row[1] ($row[2])";
252 /* returns groups of which the authenticated user is a member */
253 static function getGroups($user = null) {
254 if ($user === null) {
255 $user = self::whoami();
257 $canon = mtrack_canon_username($user);
259 if (isset(self::$group_assoc[$user])) {
260 return self::$group_assoc[$user];
263 $roles = array($canon => $canon);
265 $user_class = self::getUserClass($user); // FIXME: $canon?
266 $class_roles = MTrackConfig::get('user_class_roles', $user_class);
267 foreach (preg_split('/\s*,\s*/', $class_roles) as $role) {
268 $roles[$role] = $role;
271 foreach (self::$mechs as $mech) {
272 $g = $mech->getGroups($user);
274 foreach ($g as $i => $grp) {
275 if (is_integer($i)) {
283 /* merge in our project group membership */
284 foreach (MTrackDB::q('select project, groupname, p.name from group_membership gm left join projects p on gm.project = p.projid where username = ?',
285 $canon)->fetchAll() as $row) {
286 $gid = "project:$row[0]:$row[1]";
287 $roles[$gid] = "$row[1] ($row[2])";
290 self::$group_assoc[$user] = $roles;
294 static function forceAuthenticate() {
296 $who = self::authenticate();
298 foreach (self::$mechs as $mech) {
299 $who = $mech->doAuthenticate(true);
308 } catch (Exception $e) {