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