1<?php
2	/* libraries/user.php
3	 *
4	 * Copyright (C) by Hugo Leisink <hugo@leisink.net>
5	 * This file is part of the Banshee PHP framework
6	 * http://www.banshee-php.org/
7	 *
8	 * Don't change this file, unless you know what you are doing.
9	 */
10
11	final class user {
12		private $db = null;
13		private $settings = null;
14		private $session = null;
15		private $logged_in = false;
16		private $record = array();
17		private $is_admin = false;
18
19		/* Constructor
20		 *
21		 * INPUT:  object database, object settings, object session
22		 * OUTPUT: -
23		 * ERROR:  -
24		 */
25		public function __construct($db, $settings, $session) {
26			$this->db = $db;
27			$this->settings = $settings;
28			$this->session = $session;
29
30			/* Basic HTTP Authentication for web services
31			 */
32			if (isset($_SERVER["HTTP_AUTHORIZATION"])) {
33				list($method, $auth) = explode(" ", $_SERVER["HTTP_AUTHORIZATION"], 2);
34				if (($method == "Basic") && (($auth = base64_decode($auth)) !== false)) {
35					list($username, $password) = explode(":", $auth, 2);
36					if ($this->login_password($username, $password, false) == false) {
37						header("Status: 401");
38					} else {
39						$this->bind_to_ip();
40					}
41				}
42			}
43
44			if (isset($_SESSION["user_id"])) {
45				if (time() - $_SESSION["last_private_visit"] >= $this->settings->session_timeout) {
46					$this->logout();
47				} else if (($_SESSION["binded_ip"] === NO) || ($_SESSION["binded_ip"] === $_SERVER["REMOTE_ADDR"])) {
48					$this->load_user_record($_SESSION["user_id"]);
49				}
50			}
51		}
52
53		/* Magic method get
54		 *
55		 * INPUT:  string key
56		 * OUTPUT: mixed value
57		 * ERROR:  null
58		 */
59		public function __get($key) {
60			switch ($key) {
61				case "logged_in": return $this->logged_in;
62				case "is_admin": return $this->is_admin;
63				case "do_not_track": return $_SERVER["HTTP_DNT"] == 1;
64				case "session_via_database": return $this->session->using_database;
65				default:
66					if (isset($this->record[$key])) {
67						return $this->record[$key];
68					}
69			}
70
71			return null;
72		}
73
74		/* Store user information from database in $this->record
75		 *
76		 * INPUT:  int user identifier
77		 * OUTPUT: -
78		 * ERROR:  -
79		 */
80		private function load_user_record($user_id) {
81			if (($this->record = $this->db->entry("users", $user_id)) == false) {
82				$this->logout();
83			} else if ($this->record["status"] == USER_STATUS_DISABLED) {
84				$this->logout();
85			} else {
86				$this->logged_in = true;
87
88				$this->record["role_ids"] = array();
89				$query = "select role_id from user_role where user_id=%d";
90				if (($roles = $this->db->execute($query, $this->record["id"])) != false) {
91					foreach ($roles as $role) {
92						array_push($this->record["role_ids"], $role["role_id"]);
93						if ((int)$role["role_id"] === (int)ADMIN_ROLE_ID) {
94							$this->is_admin = true;
95						}
96					}
97				}
98			}
99		}
100
101		/* Login user
102		 *
103		 * INPUT:  int user id
104		 * OUTPUT: -
105		 * ERROR:  -
106		 */
107		private function login($user_id) {
108			$this->load_user_record($user_id);
109			$this->log_action("user logged-in");
110
111			$_SESSION["user_id"] = $user_id;
112			$_SESSION["binded_ip"] = NO;
113			$_SESSION["last_private_visit"] = time();
114
115			$this->session->set_user_id($user_id);
116		}
117
118		/* Verify user credentials
119		 *
120		 * INPUT:  string username, string password
121		 * OUTPUT: boolean login correct
122		 * ERROR:  -
123		 */
124		public function login_password($username, $password) {
125			$query = "select * from users where username=%s and status!=%d limit 1";
126			if (($data = $this->db->execute($query, $username, USER_STATUS_DISABLED)) == false) {
127				header("X-Hiawatha-Monitor: failed_login");
128				sleep(1);
129				return false;
130			}
131			$user = $data[0];
132
133			usleep(rand(0, 10000));
134
135			if ($user["password"] === hash_password($password, $username)) {
136				$this->login((int)$user["id"]);
137			}
138
139			if ($this->logged_in == false) {
140				header("X-Hiawatha-Monitor: failed_login");
141				sleep(1);
142			}
143
144			return $this->logged_in;
145		}
146
147		/* Verify one time key
148		 *
149		 * INPUT:  string one time key
150		 * OUTPUT: boolean key valid
151		 * ERROR:  -
152		 */
153		public function login_one_time_key($key) {
154			if ($key == "") {
155				return false;
156			}
157
158			usleep(rand(0, 100000));
159
160			$query = "select * from users where one_time_key=%s and status!=%d limit 1";
161			if (($data = $this->db->execute($query, $key, USER_STATUS_DISABLED)) == false) {
162				header("X-Hiawatha-Monitor: failed_login");
163				sleep(1);
164				return false;
165			}
166			$user = $data[0];
167
168			$query = "update users set one_time_key=null where id=%d";
169			$this->db->query($query, $user["id"]);
170
171			$this->login((int)$user["id"]);
172			$this->bind_to_ip();
173
174			return true;
175		}
176
177		/* Login via SSL client authentication
178		 *
179		 * INPUT:  int certificate serial number
180		 * OUTPUT: boolean serial number valid
181		 * ERROR:  -
182		 */
183		public function login_ssl_auth($cert_serial) {
184			$query = "select * from users where cert_serial=%d and status!=%d limit 1";
185			if (($data = $this->db->execute($query, $cert_serial, USER_STATUS_DISABLED)) == false) {
186				return false;
187			}
188			$user = $data[0];
189
190			$this->login((int)$user["id"]);
191
192			return true;
193		}
194
195		/* Logout current user
196		 *
197		 * INPUT:  -
198		 * OUTPUT: -
199		 * ERROR:  -
200		 */
201		public function logout() {
202			$this->log_action("user logged-out");
203
204			$this->session->reset();
205
206			$this->logged_in = false;
207			$this->record = array();
208			$this->is_admin = false;
209		}
210
211		/* Checks if user has access to page
212		 *
213		 * INPUT:  string page identifier
214		 * OUTPUT: boolean user has access to page
215		 * ERROR:  -
216		 */
217		public function access_allowed($page) {
218			static $access = array();
219
220			/* Always access
221			 */
222			$allowed = array(LOGOUT_MODULE);
223			if ($this->is_admin || in_array($page, $allowed)) {
224				return true;
225			}
226
227			/* Public module
228			 */
229			if (in_array($page, page_to_module(config_file("public_pages")))) {
230				return true;
231			}
232
233			/* Public page in database
234			*/
235			$query = "select count(*) as count from pages where url=%s and private=%d";
236			if (($result = $this->db->execute($query, "/".$page, NO)) == false) {
237				return false;
238			} else if ($result[0]["count"] > 0) {
239				return true;
240			}
241
242			/* No roles, no access
243			 */
244			if (is_array($this->record["role_ids"]) == false) {
245				return false;
246			} else if (count($this->record["role_ids"]) == 0) {
247				return false;
248			}
249
250			/* Cached?
251			 */
252			if (isset($access[$page])) {
253				return $access[$page];
254			}
255
256			/* Check access
257			 */
258			$conditions = $rids = array();
259			foreach ($this->record["role_ids"] as $rid) {
260				array_push($conditions, "%d");
261				array_push($rids, $rid);
262			}
263
264			if (in_array($page, page_to_module(config_file("private_pages")))) {
265				/* Pages on disk (modules)
266				 */
267				$query = "select %S from roles where id in (".implode(", ", $conditions).")";
268				if (($access = $this->db->execute($query, $page, $rids)) == false) {
269					return false;
270				}
271			} else {
272				/* Pages in database
273				 */
274				$query = "select a.level from page_access a, pages p ".
275				         "where a.page_id=p.id and p.url=%s and a.level>0 ".
276				         "and a.role_id in (".implode(", ", $conditions).")";
277				if (($access = $this->db->execute($query, "/".$page, $rids)) == false) {
278					return false;
279				}
280			}
281
282			$access[$page] = max(array_flatten($access)) > 0;
283
284			return $access[$page];
285		}
286
287		/* Bind current session to IP address
288		 *
289		 * INPUT:  -
290		 * OUTPUT: -
291		 * ERROR:  -
292		 */
293		public function bind_to_ip() {
294			$_SESSION["binded_ip"] = $_SERVER["REMOTE_ADDR"];
295		}
296
297		/* Verify if user has a certain role
298		 *
299		 * INPUT:  int role identifier / string role name
300		 * OUTPUT: boolean user has role
301		 * ERROR:  -
302		 */
303		public function has_role($role) {
304			if (is_int($role)) {
305				return in_array($role, $this->record["role_ids"]);
306			} else if (is_string($role)) {
307				if (($entry = $this->db->entry("roles", $role, "name")) != false) {
308					return $this->has_role((int)$entry["id"]);
309				}
310			} else if (is_array($role)) {
311				foreach ($role as $item) {
312					if ($this->has_role($item)) {
313						return true;
314					}
315				}
316			}
317
318			return false;
319		}
320
321		/* Log user action
322		 *
323		 * INPUT:  string action
324		 * OUTPUT: true
325		 * ERROR:  false
326		 */
327		public function log_action($action) {
328			if (func_num_args() > 1) {
329				$args = func_get_args();
330				array_shift($args);
331				$action = vsprintf($action, $args);
332			}
333
334			$mesg = $_SERVER["REMOTE_ADDR"]."|".date("D d M Y H:i:s")."|";
335			if ($this->logged_in == false) {
336				$mesg .= "-";
337			} else if (isset($_SESSION["user_switch"]) == false) {
338				$mesg .= $this->id;
339			} else {
340				$mesg .= $_SESSION["user_switch"].":".$this->id;
341			}
342			$mesg .= "|".$action."\n";
343
344			if (($fp = fopen("../logfiles/actions.log", "a")) == false) {
345				return false;
346			}
347
348			fputs($fp, $mesg);
349			fclose($fp);
350
351			return true;
352		}
353	}
354?>
355