PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/auth.php

https://bitbucket.org/yoander/mtrack
PHP | 352 lines | 258 code | 40 blank | 54 comment | 57 complexity | f047c3637c7099f1ae60927f80ebd4d5 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. <?php # vim:ts=2:sw=2:et:
  2. /* For licensing and copyright terms, see the file named LICENSE */
  3. include_once MTRACK_INC_DIR . '/auth/mtrack.php';
  4. include_once MTRACK_INC_DIR . '/auth/anon.php';
  5. include_once MTRACK_INC_DIR . '/auth/http.php';
  6. include_once MTRACK_INC_DIR . '/auth/openid.php';
  7. include_once MTRACK_INC_DIR . '/auth/browserid.php';
  8. include_once MTRACK_INC_DIR . '/auth/facebook.php';
  9. interface IMTrackAuth {
  10. /** Returns the authenticated user, or null if authentication is
  11. * required */
  12. function authenticate();
  13. /** Called if the user is not authenticated as a registered
  14. * user and if the page requires it.
  15. * Should initiate whatever is appropriate to begin the authentication
  16. * process (eg: displaying logon information).
  17. * You may assume that no output has been sent to the client at
  18. * the time that this function is called.
  19. * Returns null if not supported, throw an exception if failed,
  20. * else return a the authenticated user (if it can be determined
  21. * by the time the function returns).
  22. * If an alternate login page is displayed, this function should
  23. * exit instead of returning.
  24. */
  25. 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();
  29. /** Returns a list of groups that a given user belongs to.
  30. * Returns null if not supported, throw an exception if failed. */
  31. function getGroups($username);
  32. /** Adds a user to a group.
  33. * Returns null if not supported, throw an exception if failed,
  34. * return true if succeeded */
  35. function addToGroup($username, $groupname);
  36. /** Removes a user from a group.
  37. * Returns null if not supported, throw an exception if failed,
  38. * return true if succeeded */
  39. function removeFromGroup($username, $groupname);
  40. /** Returns userdata for a given user id
  41. * Some authentication mechanisms outsource the storage of user data.
  42. * This function returns null if no additional information is available,
  43. * or an array containing the following keys:
  44. * email - the email address
  45. * fullname - the full name
  46. * avatar - URL to an avatar image
  47. */
  48. function getUserData($username);
  49. /** Returns true if this mechanism is one that is capable of signing
  50. * out under application control */
  51. function canLogOut();
  52. /** logs the user out */
  53. function LogOut();
  54. }
  55. class MTrackAuth
  56. {
  57. static $stack = array();
  58. static $mechs = array();
  59. static $group_assoc = array();
  60. public static function registerMech(IMTrackAuth $mech) {
  61. self::$mechs[] = $mech;
  62. }
  63. /** switch user */
  64. public static function su($user) {
  65. if (!strlen($user)) throw new Exception("invalid user");
  66. array_unshift(self::$stack, $user);
  67. putenv("MTRACK_LOGNAME=$user");
  68. }
  69. /** returns the instance of an auth mechanism given its class name */
  70. public static function getMech($name) {
  71. foreach (self::$mechs as $inst) {
  72. if ($inst instanceof $name) {
  73. return $inst;
  74. }
  75. }
  76. return null;
  77. }
  78. /** drop identity set by last su */
  79. public static function drop() {
  80. if (count(self::$stack) == 0) {
  81. throw new Exception("no privs to drop");
  82. }
  83. $user = array_shift(self::$stack);
  84. putenv("MTRACK_LOGNAME=$user");
  85. return $user;
  86. }
  87. /** returns the authenticated user, or null if authentication
  88. * is required */
  89. public static function authenticate() {
  90. /* admin party trumps all; we need to bypass all auth mechs
  91. * while we're configuring the system */
  92. if (!MTrackConfig::get('core', 'admin_party')) {
  93. foreach (self::$mechs as $mech) {
  94. $name = $mech->authenticate();
  95. if ($name !== null) {
  96. return $name;
  97. }
  98. }
  99. }
  100. /* always fall back on the unix username when running from
  101. * the console */
  102. if (php_sapi_name() == 'cli') {
  103. static $envs = array('MTRACK_LOGNAME', 'LOGNAME', 'USER');
  104. foreach ($envs as $name) {
  105. if (isset($_ENV[$name])) {
  106. return $_ENV[$name];
  107. }
  108. }
  109. } elseif (MTrackConfig::get('core', 'admin_party') == 1) {
  110. $party = MTrackConfig::get('core', 'admin_party_remote_address');
  111. if (in_array($_SERVER['REMOTE_ADDR'], explode(',', $party))) {
  112. return 'adminparty';
  113. }
  114. }
  115. return null;
  116. }
  117. public static function isAuthConfigured() {
  118. return count(self::$mechs) ? true : false;
  119. }
  120. /** determine the current identity. If doauth is true (default),
  121. * then the authentication hook will be invoked */
  122. public static function whoami($doauth = true) {
  123. if (count(self::$stack) == 0 && $doauth) {
  124. try {
  125. $who = self::authenticate();
  126. if ($who === null && !MTrackConfig::get('core', 'admin_party')) {
  127. foreach (self::$mechs as $mech) {
  128. $who = $mech->doAuthenticate();
  129. if ($who !== null) {
  130. break;
  131. }
  132. }
  133. }
  134. if ($who !== null) {
  135. self::su($who);
  136. }
  137. } catch (Exception $e) {
  138. if (php_sapi_name() != 'cli') {
  139. header('HTTP/1.0 401 Unauthorized');
  140. echo "<h1>Not authorized</h1>";
  141. echo htmlentities($e->getMessage());
  142. } else {
  143. echo " ** Not authorized\n\n";
  144. echo $e->getMessage() . "\n";
  145. }
  146. error_log($e->getMessage());
  147. exit(1);
  148. }
  149. }
  150. if (!count(self::$stack)) {
  151. return "anonymous";
  152. }
  153. return self::$stack[0];
  154. }
  155. static function getUserClass($user = null) {
  156. if ($user === null) {
  157. $user = self::whoami();
  158. }
  159. if (MTrackConfig::get('core', 'admin_party') == 1
  160. && $user == 'adminparty'
  161. && in_array($_SERVER['REMOTE_ADDR'], explode(',', MTrackConfig::get('core', 'admin_party_remote_address')))) {
  162. return 'admin';
  163. }
  164. $user_class = MTrackConfig::get('user_classes', $user);
  165. if ($user_class === null) {
  166. if ($user == 'anonymous') {
  167. return 'anonymous';
  168. }
  169. return 'authenticated';
  170. }
  171. return $user_class;
  172. }
  173. static $userdata_cache = array();
  174. static function getUserData($username) {
  175. $username = mtrack_canon_username($username);
  176. if (array_key_exists($username, self::$userdata_cache)) {
  177. return self::$userdata_cache[$username];
  178. }
  179. $data = null;
  180. foreach (self::$mechs as $mech) {
  181. $data = $mech->getUserData($username);
  182. if ($data !== null) {
  183. break;
  184. }
  185. }
  186. foreach (MTrackDB::q(
  187. 'select fullname, email from userinfo where userid = ?',
  188. $username)->fetchAll(PDO::FETCH_ASSOC) as $row) {
  189. if ($data === null) {
  190. $data = $row;
  191. break;
  192. }
  193. foreach ($row as $k => $v) {
  194. if (!isset($data[$k]) || empty($data[$k])) {
  195. $data[$k] = $v;
  196. }
  197. }
  198. break;
  199. }
  200. if ($data === null) {
  201. $data = array(
  202. 'fullname' => $username
  203. );
  204. }
  205. if (!isset($data['email'])) {
  206. if (preg_match('/<([a-z0-9_.+=-]+@[a-z0-9.-]+)>/', $username, $M)) {
  207. // username contains an email address
  208. $data['email'] = $M[1];
  209. } else if (preg_match('/^([a-z0-9_.+=-]+@[a-z0-9.-]+)$/', $username)) {
  210. // username is an email address
  211. $data['email'] = $username;
  212. } else if (preg_match('/^[a-z0-9_.+=-]+$/', $username)) {
  213. // valid localpart; assume a domain and construct an email address
  214. $dom = MTrackConfig::get('core', 'default_email_domain');
  215. if ($dom !== null) {
  216. $data['email'] = $username . '@' . $dom;
  217. }
  218. }
  219. }
  220. self::$userdata_cache[$username] = $data;
  221. return $data;
  222. }
  223. /* enumerates possible groups from the auth plugin layer */
  224. static function enumGroups() {
  225. $groups = array();
  226. foreach (self::$mechs as $mech) {
  227. $g = $mech->enumGroups();
  228. if (is_array($g)) {
  229. foreach ($g as $i => $grp) {
  230. if (is_integer($i)) {
  231. $groups[$grp] = $grp;
  232. } else {
  233. $groups[$i] = $grp;
  234. }
  235. }
  236. }
  237. }
  238. /* merge in our project groups */
  239. foreach (MTrackDB::q('select project, g.name, p.name from groups g left join projects p on g.project = p.projid')
  240. as $row) {
  241. $gid = "project:$row[0]:$row[1]";
  242. $groups[$gid] = "$row[1] ($row[2])";
  243. }
  244. return $groups;
  245. }
  246. /* returns groups of which the authenticated user is a member */
  247. static function getGroups($user = null) {
  248. if ($user === null) {
  249. $user = self::whoami();
  250. }
  251. $canon = mtrack_canon_username($user);
  252. if (isset(self::$group_assoc[$user])) {
  253. return self::$group_assoc[$user];
  254. }
  255. $roles = array($canon => $canon);
  256. $user_class = self::getUserClass($user); // FIXME: $canon?
  257. $class_roles = MTrackConfig::get('user_class_roles', $user_class);
  258. foreach (preg_split('/\s*,\s*/', $class_roles) as $role) {
  259. $roles[$role] = $role;
  260. }
  261. foreach (self::$mechs as $mech) {
  262. $g = $mech->getGroups($user);
  263. if (is_array($g)) {
  264. foreach ($g as $i => $grp) {
  265. if (is_integer($i)) {
  266. $roles[$grp] = $grp;
  267. } else {
  268. $roles[$i] = $grp;
  269. }
  270. }
  271. }
  272. }
  273. /* merge in our project group membership */
  274. foreach (MTrackDB::q('select project, groupname, p.name from group_membership gm left join projects p on gm.project = p.projid where username = ?',
  275. $canon)->fetchAll() as $row) {
  276. $gid = "project:$row[0]:$row[1]";
  277. $roles[$gid] = "$row[1] ($row[2])";
  278. }
  279. self::$group_assoc[$user] = $roles;
  280. return $roles;
  281. }
  282. static function forceAuthenticate() {
  283. try {
  284. $who = self::authenticate();
  285. if ($who === null && !MTrackConfig::get('core', 'admin_party')) {
  286. foreach (self::$mechs as $mech) {
  287. $who = $mech->doAuthenticate(true);
  288. if ($who !== null) {
  289. break;
  290. }
  291. }
  292. }
  293. if ($who !== null) {
  294. self::su($who);
  295. }
  296. } catch (Exception $e) {
  297. }
  298. }
  299. static function canLogOut() {
  300. foreach (self::$mechs as $mech) {
  301. if ($mech->canLogOut()) {
  302. return true;
  303. }
  304. }
  305. return false;
  306. }
  307. static function LogOut() {
  308. foreach (self::$mechs as $mech) {
  309. $mech->LogOut();
  310. }
  311. }
  312. }