r2658 - last finish for ticket:181 (HP switches SNMP support)
[racktables] / inc / auth.php
CommitLineData
b325120a 1<?php
e673ee24
DO
2/*
3
4Authentication library for RackTables.
5
6*/
7
8// This function ensures that we don't continue without a legitimate
d4d873be
DO
9// username and password (also make sure, that both are present, this
10// is especially useful for LDAP auth code to not deceive itself with
204284ba
DO
11// anonymous binding). It also initializes $remote_username and $accounts.
12// Fatal errors are followed by exit (1) to aid in script debugging.
e673ee24
DO
13function authenticate ()
14{
d6d79c36 15 global $remote_username, $remote_displayname, $accounts, $user_auth_src, $require_valid_user, $script_mode;
204284ba
DO
16 if (!isset ($user_auth_src) or !isset ($require_valid_user))
17 {
18 showError ('secret.php misconfiguration: either user_auth_src or require_valid_user are missing', __FUNCTION__);
19 exit (1);
20 }
573214e0
DO
21 // This reindexing is necessary after switching to listCells(), which
22 // returns list indexed by id (while many other functions expect the
23 // user list to be indexed by username).
24 if (NULL === ($tmplist = listCells ('user')))
204284ba
DO
25 {
26 showError ('Failed to initialize access database.', __FUNCTION__);
27 exit (1);
28 }
573214e0
DO
29 $accounts = array();
30 foreach ($tmplist as $tmpval)
31 $accounts[$tmpval['user_name']] = $tmpval;
204284ba
DO
32 if (isset ($script_mode) and $script_mode === TRUE)
33 return;
dc9ea133 34 if (isset ($_REQUEST['logout']))
204284ba
DO
35 dieWith401(); // Reset browser credentials cache.
36 switch ($user_auth_src)
e673ee24 37 {
dc9ea133
DO
38 case 'database':
39 case 'ldap':
40 if
41 (
42 !isset ($_SERVER['PHP_AUTH_USER']) or
43 !strlen ($_SERVER['PHP_AUTH_USER']) or
44 !isset ($_SERVER['PHP_AUTH_PW']) or
45 !strlen ($_SERVER['PHP_AUTH_PW'])
46 )
47 dieWith401();
48 $remote_username = $_SERVER['PHP_AUTH_USER'];
49 break;
50 case 'httpd':
51 if
52 (
53 !isset ($_SERVER['REMOTE_USER']) or
54 !strlen ($_SERVER['REMOTE_USER'])
55 )
56 {
57 showError ('System misconfiguration. The web-server didn\'t authenticate the user, although ought to do.');
58 die;
59 }
60 $remote_username = $_SERVER['REMOTE_USER'];
61 break;
62 default:
63 showError ('Invalid authentication source!', __FUNCTION__);
64 die;
e673ee24 65 }
dc9ea133
DO
66 if ($require_valid_user and !isset ($accounts[$remote_username]))
67 dieWith401();
d6d79c36 68 $remote_displayname = $remote_username;
dc9ea133
DO
69 switch (TRUE)
70 {
71 // Just trust the server, because the password isn't known.
204284ba 72 case ('httpd' == $user_auth_src):
dc9ea133 73 if (authenticated_via_httpd ($remote_username))
d6d79c36
DO
74 {
75 $remote_displayname = "EXT: ${remote_username}";
dc9ea133 76 return;
d6d79c36 77 }
dc9ea133
DO
78 break;
79 // When using LDAP, leave a mean to fix things. Admin user is always authenticated locally.
204284ba 80 case ('database' == $user_auth_src or $accounts[$remote_username]['user_id'] == 1):
dc9ea133 81 if (authenticated_via_database ($remote_username, $_SERVER['PHP_AUTH_PW']))
d6d79c36
DO
82 {
83 if (!empty ($accounts[$remote_username]['user_realname']))
84 $remote_displayname = $accounts[$remote_username]['user_realname'];
dc9ea133 85 return;
d6d79c36 86 }
dc9ea133 87 break;
204284ba 88 case ('ldap' == $user_auth_src):
d6d79c36 89 // Call below also sets $remote_displayname.
dc9ea133 90 if (authenticated_via_ldap ($remote_username, $_SERVER['PHP_AUTH_PW']))
d6d79c36
DO
91 {
92 if (!empty ($accounts[$remote_username]['user_realname']))
93 $remote_displayname = $accounts[$remote_username]['user_realname'];
dc9ea133 94 return;
d6d79c36 95 }
dc9ea133
DO
96 break;
97 default:
98 showError ('Invalid authentication source!', __FUNCTION__);
99 die;
100 }
101 dieWith401();
102}
103
104function dieWith401 ()
105{
106 header ('WWW-Authenticate: Basic realm="' . getConfigVar ('enterprise') . ' RackTables access"');
107 header ('HTTP/1.0 401 Unauthorized');
108 showError ('This system requires authentication. You should use a username and a password.');
109 die();
e673ee24
DO
110}
111
da958e52
DO
112// Merge accumulated tags into a single chain, add location-specific
113// autotags and try getting access clearance. Page and tab are mandatory,
114// operation is optional.
46f92ff7 115function permitted ($p = NULL, $t = NULL, $o = NULL, $annex = array())
e673ee24 116{
da958e52 117 global $pageno, $tabno, $op;
7ddb2c05 118 global $auto_tags;
da958e52
DO
119
120 if ($p === NULL)
121 $p = $pageno;
122 if ($t === NULL)
123 $t = $tabno;
00f887f4
DO
124 $my_auto_tags = $auto_tags;
125 $my_auto_tags[] = array ('tag' => '$page_' . $p);
126 $my_auto_tags[] = array ('tag' => '$tab_' . $t);
54ecad0c 127 if ($o === NULL and !empty ($op)) // $op can be set to empty string
00f887f4
DO
128 {
129 $my_auto_tags[] = array ('tag' => '$op_' . $op);
130 $my_auto_tags[] = array ('tag' => '$any_op');
131 }
da958e52
DO
132 $subject = array_merge
133 (
00f887f4 134 $my_auto_tags,
46f92ff7 135 $annex
da958e52 136 );
1c9621a7
DO
137 // XXX: The solution below is only appropriate for a corner case of a more universal
138 // problem: to make the decision for an entity belonging to a cascade of nested
139 // containers. Each container being an entity itself, it may have own tags (explicit
140 // and implicit accordingly). There's a fixed set of rules (RackCode) with each rule
141 // being able to evaluate any built and given context and produce either a decision
142 // or a lack of decision.
143 // There are several levels of context for the target entity, at least one for entities
144 // belonging directly to the tree root. Each level's context is a union of given
145 // container's tags and the tags of the contained entities.
146 // The universal problem originates from the fact, that certain rules may change
147 // their product as context level changes, thus forcing some final decision (but not
148 // adding a lack of it). With rule code being principles and context cascade being
149 // circumstances, there are two uttermost approaches or moralities.
150 //
151 // Fundamentalism: principles over circumstances. When a rule doesn't produce any
152 // decision, go on to the next rule. When all rules are evaluated, go on to the next
153 // security context level.
154 //
155 // Opportunism: circumstances over principles. With a lack of decision, work with the
156 // same rule, trying to evaluate it against the next level (and next, and next...),
157 // until all levels are tried. Only then go on to the next rule.
158 //
159 // With the above being simple discrete algorythms, I believe, that they very reliably
160 // replicate human behavior. This gives a vast ground for further research, so I would
161 // only note, that the morale used in RackTables is "principles first".
da958e52 162 return gotClearanceForTagChain ($subject);
e673ee24
DO
163}
164
7dfd5e44
DO
165function authenticated_via_ldap ($username, $password)
166{
8c3bd904 167 global $ldap_server, $ldap_domain, $ldap_search_dn, $ldap_search_attr;
d6d79c36 168 global $remote_username, $remote_displayname, $ldap_displayname_attrs;
ae65938e 169 if ($connect = @ldap_connect ($ldap_server))
8c3bd904 170 {
d6d79c36
DO
171 if (isset ($ldap_domain) and !empty ($ldap_domain))
172 $auth_user_name = $username . "@" . $ldap_domain;
173 elseif
8c3bd904 174 (
d6d79c36
DO
175 isset ($ldap_search_dn) and
176 !empty ($ldap_search_dn) and
177 isset ($ldap_search_attr) and
178 !empty ($ldap_search_attr)
8c3bd904 179 )
8c3bd904
DO
180 {
181 $results = @ldap_search ($connect, $ldap_search_dn, "(${ldap_search_attr}=${username})", array("dn"));
182 if (@ldap_count_entries ($connect, $results) != 1)
183 {
184 @ldap_close ($connect);
185 return FALSE;
186 }
d6d79c36
DO
187 $info = @ldap_get_entries ($connect, $results);
188 ldap_free_result ($results);
189 $auth_user_name = $info[0]['dn'];
190 }
191 else
192 {
193 showError ('LDAP misconfiguration. Cannon build username for authentication.', __FUNCTION__);
194 die;
8c3bd904 195 }
d6d79c36 196 if ($bind = @ldap_bind ($connect, $auth_user_name, $password))
ae65938e 197 {
d6d79c36
DO
198 // Some servers deny anonymous search, thus search only after binding.
199 // Displayed name only makes sense for authenticated users anyway.
200 if
201 (
202 isset ($ldap_displayname_attrs) and
203 count ($ldap_displayname_attrs) and
204 isset ($ldap_search_dn) and
205 !empty ($ldap_search_dn) and
206 isset ($ldap_search_attr) and
207 !empty ($ldap_search_attr)
208 )
209 {
d5262485
DO
210 $results = @ldap_search
211 (
212 $connect,
213 $ldap_search_dn,
214 "(${ldap_search_attr}=${username})",
215 array_merge (array ('memberof'), $ldap_displayname_attrs)
216 );
d6d79c36
DO
217 if (@ldap_count_entries ($connect, $results) == 1 or TRUE)
218 {
219 $info = @ldap_get_entries ($connect, $results);
220 ldap_free_result ($results);
221 $remote_displayname = '';
222 $space = '';
223 foreach ($ldap_displayname_attrs as $attr)
224 {
225 $remote_displayname .= $space . $info[0][$attr][0];
226 $space = ' ';
227 }
d5262485
DO
228 // Pull group membership, if any was returned.
229 if (isset ($info[0]['memberof']))
230 {
231 global $auto_tags;
232 for ($i = 0; $i < $info[0]['memberof']['count']; $i++)
233 foreach (explode (',', $info[0]['memberof'][$i]) as $pair)
234 {
235 list ($attr_name, $attr_value) = explode ('=', $pair);
107facd1 236 if ($attr_name == 'CN' and validTagName ("\$lgcn_${attr_value}", TRUE))
d5262485
DO
237 {
238 $auto_tags[] = array ('tag' => "\$lgcn_${attr_value}");
239 break;
240 }
241 }
242 }
d6d79c36
DO
243 }
244 }
ae65938e
DO
245 @ldap_close ($connect);
246 return TRUE;
247 }
8c3bd904 248 }
ae65938e 249 @ldap_close ($connect);
7dfd5e44
DO
250 return FALSE;
251}
252
253function authenticated_via_database ($username, $password)
254{
255 global $accounts;
93bdb7ba 256 if (!function_exists ('sha1'))
7dfd5e44 257 {
93bdb7ba 258 showError ('Fatal error: PHP sha1() function is missing', __FUNCTION__);
7dfd5e44
DO
259 die();
260 }
261 if (!isset ($accounts[$username]['user_password_hash']))
262 return FALSE;
93bdb7ba 263 if ($accounts[$username]['user_password_hash'] == sha1 ($password))
e673ee24
DO
264 return TRUE;
265 return FALSE;
266}
267
dc9ea133
DO
268function authenticated_via_httpd ($username)
269{
270 // Reaching here means, that .htaccess authentication passed.
271 // Let's make sure, that user exists in the database, and give clearance.
272 global $accounts;
273 return isset ($accounts[$username]);
274}
275
e673ee24
DO
276// This function returns password hash for given user ID.
277function getHashByID ($user_id = 0)
278{
279 if ($user_id <= 0)
280 {
b09549b3 281 showError ('Invalid user_id', __FUNCTION__);
e673ee24
DO
282 return NULL;
283 }
284 global $accounts;
285 foreach ($accounts as $account)
286 if ($account['user_id'] == $user_id)
287 return $account['user_password_hash'];
288 return NULL;
289}
290
b9bd9897
DO
291// Likewise.
292function getUsernameByID ($user_id = 0)
293{
294 if ($user_id <= 0)
295 {
296 showError ('Invalid user_id', __FUNCTION__);
297 return NULL;
298 }
299 global $accounts;
300 foreach ($accounts as $account)
301 if ($account['user_id'] == $user_id)
302 return $account['user_name'];
303 showError ("User with ID '${user_id}' not found!");
304 return NULL;
305}
306
e673ee24 307?>