1<?php 2## 3## Copyright 2013-2018 Opera Software AS 4## 5## Licensed under the Apache License, Version 2.0 (the "License"); 6## you may not use this file except in compliance with the License. 7## You may obtain a copy of the License at 8## 9## http://www.apache.org/licenses/LICENSE-2.0 10## 11## Unless required by applicable law or agreed to in writing, software 12## distributed under the License is distributed on an "AS IS" BASIS, 13## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14## See the License for the specific language governing permissions and 15## limitations under the License. 16## 17 18/** 19* Class that represents a user of this system. 20*/ 21class User extends Record { 22 /** 23 * Defines the database table that this object is stored in 24 */ 25 protected $table = 'user'; 26 /** 27 * LDAP connection object 28 */ 29 private $ldap; 30 31 public function __construct($id = null, $preload_data = array()) { 32 parent::__construct($id, $preload_data); 33 global $ldap; 34 $this->ldap = $ldap; 35 } 36 37 /** 38 * Add an alert to be displayed to this user on their next normal page load. 39 * @param UserAlert $alert to be displayed 40 */ 41 public function add_alert(UserAlert $alert) { 42 if(is_null($this->id)) throw new BadMethodCallException('User must be in directory before alerts can be added'); 43 $stmt = $this->database->prepare('INSERT INTO user_alert (user_id, class, content, escaping) VALUES (?, ?, ?, ?)'); 44 $stmt->bindParam(1, $this->id, PDO::PARAM_INT); 45 $stmt->bindParam(2, $alert->class, PDO::PARAM_STR); 46 $stmt->bindParam(3, $alert->content, PDO::PARAM_STR); 47 $stmt->bindParam(4, $alert->escaping, PDO::PARAM_INT); 48 $stmt->execute(); 49 $alert->id = $this->database->lastInsertId('user_alert_id_seq'); 50 } 51 52 /** 53 * List all alerts for this user *and* delete them. 54 * @return array of UserAlert objects 55 */ 56 public function pop_alerts() { 57 if(is_null($this->id)) throw new BadMethodCallException('User must be in directory before alerts can be listed'); 58 $stmt = $this->database->prepare('SELECT * FROM user_alert WHERE user_id = ?'); 59 $stmt->bindParam(1, $this->id, PDO::PARAM_INT); 60 $stmt->execute(); 61 $alerts = array(); 62 $alert_ids = array(); 63 while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { 64 $alerts[] = new UserAlert($row['id'], $row); 65 $alert_ids[] = $row['id']; 66 } 67 if(count($alert_ids) > 0) { 68 $this->database->query('DELETE FROM user_alert WHERE id IN ('.implode(', ', $alert_ids).')'); 69 } 70 return $alerts; 71 } 72 73 /** 74 * Return HTML containing this user's CSRF token for inclusion in a POST form. 75 * Also includes a random string of the same length to help guard against http://breachattack.com/ 76 * @return string HTML 77 */ 78 public function get_csrf_field() { 79 return '<input type="hidden" name="csrf_token" value="'.hesc($this->get_csrf_token()).'"><!-- '.hash("sha512", mt_rand(0, mt_getrandmax())).' -->'."\n"; 80 } 81 82 /** 83 * Return this user's CSRF token. Generate one if they do not yet have one. 84 * @return string CSRF token 85 */ 86 public function get_csrf_token() { 87 if(is_null($this->id)) throw new BadMethodCallException('User must be in directory before CSRF token can be generated'); 88 if(!isset($this->data['csrf_token'])) { 89 $this->data['csrf_token'] = hash("sha512", mt_rand(0, mt_getrandmax())); 90 $this->update(); 91 } 92 return $this->data['csrf_token']; 93 } 94 95 /** 96 * Check the given string against this user's CSRF token. 97 * @return bool true on string match 98 */ 99 public function check_csrf_token($token) { 100 return $token === $this->get_csrf_token(); 101 } 102 103 /** 104 * Retrieve this user's details from the configured data source. 105 * @throws UserDataSourceException if no user data source is configured 106 */ 107 public function get_details() { 108 global $config; 109 if(!empty($config['ldap']['enabled'])) { 110 $this->get_details_from_ldap(); 111 } elseif(!empty($config['php_auth']['enabled'])) { 112 $this->get_details_from_php_auth(); 113 } else { 114 throw new UserDataSourceException('User data source not configured.'); 115 } 116 } 117 118 /** 119 * Retrieve this user's details from PHP_AUTH variables. 120 * @throws UserNotFoundException if the user details are not found in PHP_AUTH variables 121 */ 122 public function get_details_from_php_auth() { 123 global $config; 124 if($this->uid == $_SERVER['PHP_AUTH_USER'] and isset($_SERVER['PHP_AUTH_NAME']) and isset($_SERVER['PHP_AUTH_EMAIL']) and isset($_SERVER['PHP_AUTH_GROUPS'])) { 125 $this->auth_realm = 'PHP_AUTH'; 126 $this->name = $_SERVER['PHP_AUTH_NAME']; 127 $this->email = $_SERVER['PHP_AUTH_EMAIL']; 128 $this->active = 1; 129 $this->admin = 0; 130 $groups = explode(' ', $_SERVER['PHP_AUTH_GROUPS']); 131 foreach($groups as $group) { 132 if($group == $config['php_auth']['admin_group']) $this->admin = 1; 133 } 134 } else { 135 throw new UserNotFoundException('User does not exist in PHP_AUTH variables.'); 136 } 137 } 138 139 /** 140 * Retrieve this user's details from LDAP. 141 * @throws UserNotFoundException if the user is not found in LDAP 142 */ 143 public function get_details_from_ldap() { 144 global $config; 145 $attributes = array(); 146 $attributes[] = 'dn'; 147 $attributes[] = $config['ldap']['user_id']; 148 $attributes[] = $config['ldap']['user_name']; 149 $attributes[] = $config['ldap']['user_email']; 150 $attributes[] = $config['ldap']['group_member_value']; 151 if(isset($config['ldap']['user_active'])) { 152 $attributes[] = $config['ldap']['user_active']; 153 } 154 $ldapusers = $this->ldap->search($config['ldap']['dn_user'], LDAP::escape($config['ldap']['user_id']).'='.LDAP::escape($this->uid), array_keys(array_flip($attributes))); 155 if($ldapuser = reset($ldapusers)) { 156 $this->auth_realm = 'LDAP'; 157 $this->uid = $ldapuser[strtolower($config['ldap']['user_id'])]; 158 $this->name = $ldapuser[strtolower($config['ldap']['user_name'])]; 159 $this->email = $ldapuser[strtolower($config['ldap']['user_email'])]; 160 if(isset($config['ldap']['user_active'])) { 161 $this->active = 0; 162 if(isset($config['ldap']['user_active_true'])) { 163 $this->active = intval($ldapuser[strtolower($config['ldap']['user_active'])] == $config['ldap']['user_active_true']); 164 } elseif(isset($config['ldap']['user_active_false'])) { 165 $this->active = intval($ldapuser[strtolower($config['ldap']['user_active'])] != $config['ldap']['user_active_false']); 166 } 167 } else { 168 $this->active = 1; 169 } 170 $this->admin = 0; 171 $group_member = $ldapuser[strtolower($config['ldap']['group_member_value'])]; 172 $ldapgroups = $this->ldap->search($config['ldap']['dn_group'], LDAP::escape($config['ldap']['group_member']).'='.LDAP::escape($group_member), array('cn')); 173 foreach($ldapgroups as $ldapgroup) { 174 if($ldapgroup['cn'] == $config['ldap']['admin_group_cn']) $this->admin = 1; 175 } 176 } else { 177 throw new UserNotFoundException('User does not exist in LDAP.'); 178 } 179 } 180 181 /** 182 * Return the access level of this user to the specified zone. 183 * @param Zone $zone to check for access 184 * @return string name of access level 185 */ 186 public function access_to(Zone $zone) { 187 $stmt = $this->database->prepare('SELECT level FROM zone_access WHERE user_id = ? AND zone_id = ?'); 188 $stmt->bindParam(1, $this->id, PDO::PARAM_INT); 189 $stmt->bindParam(2, $zone->id, PDO::PARAM_INT); 190 $stmt->execute(); 191 if($row = $stmt->fetch(PDO::FETCH_ASSOC)) { 192 return $row['level']; 193 } else { 194 return false; 195 } 196 } 197 198 /** 199 * List all zones that this user is an administrator of 200 * @param array $include list of extra data to include in response 201 * @return array of Zone objects 202 */ 203 public function list_admined_zones($include = array()) { 204 global $zone_dir; 205 $zones = $zone_dir->list_zones($include); 206 $admined_zones = array(); 207 foreach($zones as $zone) { 208 if($this->access_to($zone)) $admined_zones[$zone->pdns_id] = $zone; 209 } 210 return $admined_zones; 211 } 212 213 /** 214 * List all zones that this user has access to in some way 215 * @param array $include list of extra data to include in response 216 * @return array of Zone objects 217 */ 218 public function list_accessible_zones($include = array()) { 219 global $zone_dir; 220 if($this->admin) { 221 $zones = $zone_dir->list_zones($include); 222 } else { 223 $zones = $this->list_admined_zones($include); 224 } 225 return $zones; 226 } 227 228 /** 229 * List all changes that this user has made to any zones 230 * @return array of ChangeSet objects 231 */ 232 public function list_changesets() { 233 global $user_dir; 234 $stmt = $this->database->prepare(' 235 SELECT changeset.*, zone.pdns_id, zone.name, zone.serial, zone.account, zone.active 236 FROM changeset 237 INNER JOIN zone ON zone.id = changeset.zone_id 238 WHERE author_id = ? 239 ORDER BY id DESC 240 '); 241 $stmt->bindParam(1, $this->id, PDO::PARAM_INT); 242 $stmt->execute(); 243 $changesets = array(); 244 while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { 245 $row['author'] = $this; 246 $row['requester'] = (is_null($row['requester_id']) ? null : $user_dir->get_user_by_id($row['requester_id'])); 247 $row['zone'] = new Zone($row['id'], array('pdns_id' => $row['pdns_id'], 'name' => $row['name'], 'serial' => $row['serial'], 'account' => $row['account'], 'active' => $row['active'])); 248 unset($row['pdns_id']); 249 unset($row['name']); 250 unset($row['serial']); 251 unset($row['account']); 252 unset($row['active']); 253 $row['change_date'] = DateTime::createFromFormat('Y-m-d H:i:s.u', $row['change_date']); 254 $changesets[] = new ChangeSet($row['id'], $row); 255 } 256 return $changesets; 257 } 258} 259