final move of files
[web.mtrack] / MTrack / Auth.php
1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
3
4 require_once 'MTrack/auth/http.php';
5 //require_once 'auth/openid.php';
6
7 require_once 'MTrack/DB.php';
8 require_once 'MTrack/Config.php';
9
10 require_once 'MTrack/Interface/Auth.php';
11  
12 class MTrackAuth
13 {
14   static $stack = array();
15   static $mechs = array();
16   static $group_assoc = array();
17
18   public static function registerMech(IMTrackAuth $mech) {
19     self::$mechs[] = $mech;
20   }
21
22   /** switch user */
23   public static function su($user) {
24     if (!strlen($user)) throw new Exception("invalid user");
25     array_unshift(self::$stack, $user);
26   }
27
28   /** returns the instance of an auth mechanism given its class name */
29   public static function getMech($name) {
30     foreach (self::$mechs as $inst) {
31       if ($inst instanceof $name) {
32         return $inst;
33       }
34     }
35     return null;
36   }
37
38   /** drop identity set by last su */
39   public static function drop() {
40     if (count(self::$stack) == 0) {
41       throw new Exception("no privs to drop");
42     }
43     return array_shift(self::$stack);
44   }
45
46   /** returns the authenticated user, or null if authentication
47    * is required */
48   public static function authenticate() {
49     foreach (self::$mechs as $mech) {
50       $name = $mech->authenticate();
51       if ($name !== null) {
52         return $name;
53       }
54     }
55
56     /* always fall back on the unix username when running from
57      * the console */
58     if (php_sapi_name() == 'cli') {
59       static $envs = array('MTRACK_LOGNAME', 'LOGNAME', 'USER');
60       foreach ($envs as $name) {
61         if (isset($_ENV[$name])) {
62           return $_ENV[$name];
63         }
64       }
65     } elseif (count(self::$mechs) == 0 &&
66         MTrackConfig::get('core', 'admin_party') == 1
67         && ($_SERVER['REMOTE_ADDR'] == '127.0.0.1' ||
68           $_SERVER['REMOTE_ADDR'] == '::1')) {
69       return 'adminparty';
70     }
71
72     return null;
73   }
74
75   public static function isAuthConfigured() {
76     return count(self::$mechs) ? true : false;
77   }
78
79   /** determine the current identity.  If doauth is true (default),
80    * then the authentication hook will be invoked */
81   public static function whoami($doauth = true) {
82     if (count(self::$stack) == 0 && $doauth) {
83       try {
84         $who = self::authenticate();
85         if ($who === null) {
86           foreach (self::$mechs as $mech) {
87             $who = $mech->doAuthenticate();
88             if ($who !== null) {
89               break;
90             }
91           }
92         }
93         if ($who !== null) {
94           self::su($who);
95         }
96       } catch (Exception $e) {
97         if (php_sapi_name() != 'cli') {
98           header('HTTP/1.0 401 Unauthorized');
99           echo "<h1>Not authorized</h1>";
100           echo htmlentities($e->getMessage());
101         } else {
102           echo " ** Not authorized\n\n";
103           echo $e->getMessage() . "\n";
104         }
105         error_log($e->getMessage());
106         exit(1);
107       }
108     }
109     if (!count(self::$stack)) {
110       return "anonymous";
111     }
112     return self::$stack[0];
113   }
114
115   static function getUserClass($user = null) {
116     if ($user === null) {
117       $user = self::whoami();
118     }
119     if (MTrackConfig::get('core', 'admin_party') == 1
120         && $user == 'adminparty'
121         && ($_SERVER['REMOTE_ADDR'] == '127.0.0.1' ||
122           $_SERVER['REMOTE_ADDR'] == '::1')) {
123       return 'admin';
124     }
125
126     $user_class = MTrackConfig::get('user_classes', $user);
127     if ($user_class === null) {
128       if ($user == 'anonymous') {
129         return 'anonymous';
130       }
131       return 'authenticated';
132     }
133     return $user_class;
134   }
135
136   static $userdata_cache = array();
137   static function getUserData($username) {
138     $username = mtrack_canon_username($username);
139
140     if (array_key_exists($username, self::$userdata_cache)) {
141       return self::$userdata_cache[$username];
142     }
143     $data = null;
144     foreach (self::$mechs as $mech) {
145       $data = $mech->getUserData($username);
146       if ($data !== null) {
147         break;
148       }
149     }
150     if ($data === null) {
151       foreach (MTrackDB::q(
152           'select fullname, email from userinfo where userid = ?',
153           $username)->fetchAll(PDO::FETCH_ASSOC) as $row) {
154         $data = $row;
155         break;
156       }
157     }
158     if ($data === null) {
159       $data = array(
160         'fullname' => $username
161       );
162     }
163
164     if (!isset($data['email'])) {
165       if (preg_match('/<([a-z0-9_.+=-]+@[a-z0-9.-]+)>/', $username, $M)) {
166         // username contains an email address
167         $data['email'] = $M[1];
168       } else if (preg_match('/^([a-z0-9_.+=-]+@[a-z0-9.-]+)$/', $username)) {
169         // username is an email address
170         $data['email'] = $username;
171       } else if (preg_match('/^[a-z0-9_.+=-]+$/', $username)) {
172         // valid localpart; assume a domain and construct an email address
173         $dom = MTrackConfig::get('core', 'default_email_domain');
174         if ($dom !== null) {
175           $data['email'] = $username . '@' . $dom;
176         }
177       }
178     }
179
180     self::$userdata_cache[$username] = $data;
181
182     return $data;
183   }
184
185   /* enumerates possible groups from the auth plugin layer */
186   static function enumGroups() {
187     $groups = array();
188     foreach (self::$mechs as $mech) {
189       $g = $mech->enumGroups();
190       if (is_array($g)) {
191         foreach ($g as $i => $grp) {
192           if (is_integer($i)) {
193             $groups[$grp] = $grp;
194           } else {
195             $groups[$i] = $grp;
196           }
197         }
198       }
199     }
200     /* merge in our project groups */
201     foreach (MTrackDB::q('select project, g.name, p.name from groups g left join projects p on g.project = p.projid')
202         as $row) {
203       $gid = "project:$row[0]:$row[1]";
204       $groups[$gid] = "$row[1] ($row[2])";
205     }
206     return $groups;
207   }
208
209   /* returns groups of which the authenticated user is a member */
210   static function getGroups($user = null) {
211     if ($user === null) {
212       $user = self::whoami();
213     }
214     $canon = mtrack_canon_username($user);
215
216     if (isset(self::$group_assoc[$user])) {
217       return self::$group_assoc[$user];
218     }
219
220     $roles = array($canon => $canon);
221
222     $user_class = self::getUserClass($user); // FIXME: $canon?
223     $class_roles = MTrackConfig::get('user_class_roles', $user_class);
224     foreach (preg_split('/\s*,\s*/', $class_roles) as $role) {
225       $roles[$role] = $role;
226     }
227
228     foreach (self::$mechs as $mech) {
229       $g = $mech->getGroups($user);
230       if (is_array($g)) {
231         foreach ($g as $i => $grp) {
232           if (is_integer($i)) {
233             $roles[$grp] = $grp;
234           } else {
235             $roles[$i] = $grp;
236           }
237         }
238       }
239     }
240     /* merge in our project group membership */
241     foreach (MTrackDB::q('select project, groupname, p.name from group_membership gm left join projects p on gm.project = p.projid where username = ?',
242         $canon)->fetchAll() as $row) {
243       $gid = "project:$row[0]:$row[1]";
244       $roles[$gid] = "$row[1] ($row[2])";
245     }
246
247     self::$group_assoc[$user] = $roles;
248     return $roles;
249   }
250
251   static function forceAuthenticate() {
252     try {
253       $who = self::authenticate();
254       if ($who === null) {
255         foreach (self::$mechs as $mech) {
256           $who = $mech->doAuthenticate(true);
257           if ($who !== null) {
258             break;
259           }
260         }
261       }
262       if ($who !== null) {
263         self::su($who);
264       }
265     } catch (Exception $e) {
266     }
267   }
268 }
269