Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
17.41% covered (danger)
17.41%
78 / 448
10.53% covered (danger)
10.53%
2 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
auth
17.41% covered (danger)
17.41%
78 / 448
10.53% covered (danger)
10.53%
2 / 19
19465.34
0.00% covered (danger)
0.00%
0 / 1
 acl
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
210
 obtain_user_data
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 _fill_acl
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
42
 acl_get
53.85% covered (warning)
53.85%
7 / 13
0.00% covered (danger)
0.00%
0 / 1
19.83
 acl_getf
23.33% covered (danger)
23.33%
7 / 30
0.00% covered (danger)
0.00%
0 / 1
181.68
 acl_getf_global
28.57% covered (danger)
28.57%
4 / 14
0.00% covered (danger)
0.00%
0 / 1
46.44
 acl_gets
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 acl_get_list
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
90
 acl_cache
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 build_bitstring
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
110
 acl_clear_prefetch
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
5
 acl_role_data
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
72
 acl_raw_data
0.00% covered (danger)
0.00%
0 / 57
0.00% covered (danger)
0.00%
0 / 1
702
 acl_user_raw_data
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
110
 acl_group_raw_data
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
11
 acl_raw_data_single_user
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
132
 _set_group_hold_ary
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
56
 login
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 1
132
 build_auth_option_statement
52.94% covered (warning)
52.94%
9 / 17
0.00% covered (danger)
0.00%
0 / 1
14.67
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14namespace phpbb\auth;
15
16/**
17* Permission/Auth class
18*/
19class auth
20{
21    var $acl = array();
22    var $cache = array();
23    var $acl_options = array();
24    var $acl_forum_ids = false;
25
26    /**
27    * Init permissions
28    */
29    function acl(&$userdata)
30    {
31        global $db, $cache;
32
33        $this->acl = $this->cache = $this->acl_options = array();
34        $this->acl_forum_ids = false;
35
36        if (($this->acl_options = $cache->get('_acl_options')) === false)
37        {
38            $sql = 'SELECT auth_option_id, auth_option, is_global, is_local
39                FROM ' . ACL_OPTIONS_TABLE . '
40                ORDER BY auth_option_id';
41            $result = $db->sql_query($sql);
42
43            $global = $local = 0;
44            $this->acl_options = array();
45            while ($row = $db->sql_fetchrow($result))
46            {
47                if ($row['is_global'])
48                {
49                    $this->acl_options['global'][$row['auth_option']] = $global++;
50                }
51
52                if ($row['is_local'])
53                {
54                    $this->acl_options['local'][$row['auth_option']] = $local++;
55                }
56
57                $this->acl_options['id'][$row['auth_option']] = (int) $row['auth_option_id'];
58                $this->acl_options['option'][(int) $row['auth_option_id']] = $row['auth_option'];
59            }
60            $db->sql_freeresult($result);
61
62            $cache->put('_acl_options', $this->acl_options);
63        }
64
65        if (!trim($userdata['user_permissions']))
66        {
67            $this->acl_cache($userdata);
68        }
69
70        // Fill ACL array
71        $this->_fill_acl($userdata['user_permissions']);
72
73        // Verify bitstring length with options provided...
74        $renew = false;
75        $global_length = count($this->acl_options['global']);
76        $local_length = count($this->acl_options['local']);
77
78        // Specify comparing length (bitstring is padded to 31 bits)
79        $global_length = ($global_length % 31) ? ($global_length - ($global_length % 31) + 31) : $global_length;
80        $local_length = ($local_length % 31) ? ($local_length - ($local_length % 31) + 31) : $local_length;
81
82        // You thought we are finished now? Noooo... now compare them.
83        foreach ($this->acl as $forum_id => $bitstring)
84        {
85            if (($forum_id && strlen($bitstring) != $local_length) || (!$forum_id && strlen($bitstring) != $global_length))
86            {
87                $renew = true;
88                break;
89            }
90        }
91
92        // If a bitstring within the list does not match the options, we have a user with incorrect permissions set and need to renew them
93        if ($renew)
94        {
95            $this->acl_cache($userdata);
96            $this->_fill_acl($userdata['user_permissions']);
97        }
98    }
99
100    /**
101    * Retrieves data wanted by acl function from the database for the
102    * specified user.
103    *
104    * @param int $user_id User ID
105    * @return array User attributes
106    */
107    public function obtain_user_data($user_id)
108    {
109        global $db;
110
111        $sql = 'SELECT user_id, username, user_permissions, user_type
112            FROM ' . USERS_TABLE . '
113            WHERE user_id = ' . $user_id;
114        $result = $db->sql_query($sql);
115        $user_data = $db->sql_fetchrow($result);
116        $db->sql_freeresult($result);
117        return $user_data;
118    }
119
120    /**
121    * Fill ACL array with relevant bitstrings from user_permissions column
122    * @access private
123    */
124    function _fill_acl($user_permissions)
125    {
126        $seq_cache = array();
127        $this->acl = array();
128        $user_permissions = explode("\n", $user_permissions);
129
130        foreach ($user_permissions as $f => $seq)
131        {
132            if ($seq)
133            {
134                $i = 0;
135
136                if (!isset($this->acl[$f]))
137                {
138                    $this->acl[$f] = '';
139                }
140
141                while ($subseq = substr($seq, $i, 6))
142                {
143                    if (isset($seq_cache[$subseq]))
144                    {
145                        $converted = $seq_cache[$subseq];
146                    }
147                    else
148                    {
149                        $converted = $seq_cache[$subseq] = str_pad(base_convert($subseq, 36, 2), 31, 0, STR_PAD_LEFT);
150                    }
151
152                    // We put the original bitstring into the acl array
153                    $this->acl[$f] .= $converted;
154                    $i += 6;
155                }
156            }
157        }
158    }
159
160    /**
161    * Look up an option
162    * if the option is prefixed with !, then the result becomes negated
163    *
164    * If a forum id is specified the local option will be combined with a global option if one exist.
165    * If a forum id is not specified, only the global option will be checked.
166    */
167    function acl_get($opt, $f = 0)
168    {
169        $negate = false;
170
171        if (strpos($opt, '!') === 0)
172        {
173            $negate = true;
174            $opt = substr($opt, 1);
175        }
176
177        if (!isset($this->cache[$f][$opt]))
178        {
179            // We combine the global/local option with an OR because some options are global and local.
180            // If the user has the global permission the local one is true too and vice versa
181            $this->cache[$f][$opt] = false;
182
183            // Is this option a global permission setting?
184            if (isset($this->acl_options['global'][$opt]))
185            {
186                if (isset($this->acl[0]))
187                {
188                    $this->cache[$f][$opt] = $this->acl[0][$this->acl_options['global'][$opt]];
189                }
190            }
191
192            // Is this option a local permission setting?
193            // But if we check for a global option only, we won't combine the options...
194            if ($f != 0 && isset($this->acl_options['local'][$opt]))
195            {
196                if (isset($this->acl[$f]) && isset($this->acl[$f][$this->acl_options['local'][$opt]]))
197                {
198                    $this->cache[$f][$opt] |= $this->acl[$f][$this->acl_options['local'][$opt]];
199                }
200            }
201        }
202
203        // Founder always has all global options set to true...
204        return ($negate) ? !$this->cache[$f][$opt] : $this->cache[$f][$opt];
205    }
206
207    /**
208    * Get forums with the specified permission setting
209    *
210    * @param string $opt The permission name to lookup. If prefixed with !, the result is negated.
211    * @param bool    $clean set to true if only values needs to be returned which are set/unset
212    *
213    * @return array Contains the forum ids with the specified permission set to true.
214    *                This is a nested array: array => forum_id => permission => true
215    */
216    function acl_getf($opt, $clean = false)
217    {
218        $acl_f = array();
219        $negate = false;
220
221        if (strpos($opt, '!') === 0)
222        {
223            $negate = true;
224            $opt = substr($opt, 1);
225        }
226
227        // If we retrieve a list of forums not having permissions in, we need to get every forum_id
228        if ($negate)
229        {
230            if ($this->acl_forum_ids === false)
231            {
232                global $db;
233
234                $sql = 'SELECT forum_id
235                    FROM ' . FORUMS_TABLE;
236
237                if (count($this->acl))
238                {
239                    $sql .= ' WHERE ' . $db->sql_in_set('forum_id', array_keys($this->acl), true);
240                }
241                $result = $db->sql_query($sql);
242
243                $this->acl_forum_ids = array();
244                while ($row = $db->sql_fetchrow($result))
245                {
246                    $this->acl_forum_ids[] = $row['forum_id'];
247                }
248                $db->sql_freeresult($result);
249            }
250        }
251
252        if (isset($this->acl_options['local'][$opt]))
253        {
254            foreach ($this->acl as $f => $bitstring)
255            {
256                // Skip global settings
257                if (!$f)
258                {
259                    continue;
260                }
261
262                $allowed = (!isset($this->cache[$f][$opt])) ? $this->acl_get($opt, $f) : $this->cache[$f][$opt];
263
264                if (!$clean)
265                {
266                    $acl_f[$f][$opt] = ($negate) ? !$allowed : $allowed;
267                }
268                else
269                {
270                    if (($negate && !$allowed) || (!$negate && $allowed))
271                    {
272                        $acl_f[$f][$opt] = 1;
273                    }
274                }
275            }
276        }
277
278        // If we get forum_ids not having this permission, we need to fill the remaining parts
279        if ($negate && count($this->acl_forum_ids))
280        {
281            foreach ($this->acl_forum_ids as $f)
282            {
283                $acl_f[$f][$opt] = 1;
284            }
285        }
286
287        return $acl_f;
288    }
289
290    /**
291    * Get local permission state for any forum.
292    *
293    * Returns true if user has the permission in one or more forums, false if in no forum.
294    * If global option is checked it returns the global state (same as acl_get($opt))
295    * Local option has precedence...
296    */
297    function acl_getf_global($opt)
298    {
299        if (is_array($opt))
300        {
301            // evaluates to true as soon as acl_getf_global is true for one option
302            foreach ($opt as $check_option)
303            {
304                if ($this->acl_getf_global($check_option))
305                {
306                    return true;
307                }
308            }
309
310            return false;
311        }
312
313        if (isset($this->acl_options['local'][$opt]))
314        {
315            foreach ($this->acl as $f => $bitstring)
316            {
317                // Skip global settings
318                if (!$f)
319                {
320                    continue;
321                }
322
323                // as soon as the user has any permission we're done so return true
324                if ((!isset($this->cache[$f][$opt])) ? $this->acl_get($opt, $f) : $this->cache[$f][$opt])
325                {
326                    return true;
327                }
328            }
329        }
330        else if (isset($this->acl_options['global'][$opt]))
331        {
332            return $this->acl_get($opt);
333        }
334
335        return false;
336    }
337
338    /**
339    * Get permission settings (more than one)
340    */
341    function acl_gets()
342    {
343        $args = func_get_args();
344        $f = array_pop($args);
345
346        if (!is_numeric($f))
347        {
348            $args[] = $f;
349            $f = 0;
350        }
351
352        // alternate syntax: acl_gets(array('m_', 'a_'), $forum_id)
353        if (is_array($args[0]))
354        {
355            $args = $args[0];
356        }
357
358        $acl = 0;
359        foreach ($args as $opt)
360        {
361            $acl |= $this->acl_get($opt, $f);
362        }
363
364        return $acl;
365    }
366
367    /**
368    * Get permission listing based on user_id/options/forum_ids
369    *
370    * Be careful when using this function with permissions a_, m_, u_ and f_ !
371    * It may not work correctly. When a user group grants an a_* permission,
372    * e.g. a_foo, but the user's a_foo permission is set to "Never", then
373    * the user does not in fact have the a_ permission.
374    * But the user will still be listed as having the a_ permission.
375    *
376    * For more information see: http://tracker.phpbb.com/browse/PHPBB3-10252
377    */
378    function acl_get_list($user_id = false, $opts = false, $forum_id = false)
379    {
380        if ($user_id !== false && !is_array($user_id) && $opts === false && $forum_id === false)
381        {
382            $hold_ary = array($user_id => $this->acl_raw_data_single_user($user_id));
383        }
384        else
385        {
386            $hold_ary = $this->acl_raw_data($user_id, $opts, $forum_id);
387        }
388
389        $auth_ary = array();
390        foreach ($hold_ary as $user_id => $forum_ary)
391        {
392            foreach ($forum_ary as $forum_id => $auth_option_ary)
393            {
394                foreach ($auth_option_ary as $auth_option => $auth_setting)
395                {
396                    if ($auth_setting)
397                    {
398                        $auth_ary[$forum_id][$auth_option][] = $user_id;
399                    }
400                }
401            }
402        }
403
404        return $auth_ary;
405    }
406
407    /**
408    * Cache data to user_permissions row
409    */
410    function acl_cache(&$userdata)
411    {
412        global $db;
413
414        // Empty user_permissions
415        $userdata['user_permissions'] = '';
416
417        $hold_ary = $this->acl_raw_data_single_user($userdata['user_id']);
418
419        // Key 0 in $hold_ary are global options, all others are forum_ids
420
421        // If this user is founder we're going to force fill the admin options ...
422        if ($userdata['user_type'] == USER_FOUNDER)
423        {
424            foreach ($this->acl_options['global'] as $opt => $id)
425            {
426                if (strpos($opt, 'a_') === 0)
427                {
428                    $hold_ary[0][$this->acl_options['id'][$opt]] = ACL_YES;
429                }
430            }
431        }
432
433        $hold_str = $this->build_bitstring($hold_ary);
434
435        if ($hold_str)
436        {
437            $userdata['user_permissions'] = $hold_str;
438
439            $sql = 'UPDATE ' . USERS_TABLE . "
440                SET user_permissions = '" . $db->sql_escape($userdata['user_permissions']) . "',
441                    user_perm_from = 0
442                WHERE user_id = " . $userdata['user_id'];
443            $db->sql_query($sql);
444        }
445
446        return;
447    }
448
449    /**
450    * Build bitstring from permission set
451    */
452    function build_bitstring(&$hold_ary)
453    {
454        $hold_str = '';
455
456        if (count($hold_ary))
457        {
458            ksort($hold_ary);
459
460            $last_f = 0;
461
462            foreach ($hold_ary as $f => $auth_ary)
463            {
464                $ary_key = (!$f) ? 'global' : 'local';
465
466                $bitstring = array();
467                foreach ($this->acl_options[$ary_key] as $opt => $id)
468                {
469                    if (isset($auth_ary[$this->acl_options['id'][$opt]]))
470                    {
471                        $bitstring[$id] = $auth_ary[$this->acl_options['id'][$opt]];
472
473                        $option_key = substr($opt, 0, strpos($opt, '_') + 1);
474
475                        // If one option is allowed, the global permission for this option has to be allowed too
476                        // example: if the user has the a_ permission this means he has one or more a_* permissions
477                        if ($auth_ary[$this->acl_options['id'][$opt]] == ACL_YES && (!isset($bitstring[$this->acl_options[$ary_key][$option_key]]) || $bitstring[$this->acl_options[$ary_key][$option_key]] == ACL_NEVER))
478                        {
479                            $bitstring[$this->acl_options[$ary_key][$option_key]] = ACL_YES;
480                        }
481                    }
482                    else
483                    {
484                        $bitstring[$id] = ACL_NEVER;
485                    }
486                }
487
488                // Now this bitstring defines the permission setting for the current forum $f (or global setting)
489                $bitstring = implode('', $bitstring);
490
491                // The line number indicates the id, therefore we have to add empty lines for those ids not present
492                $hold_str .= str_repeat("\n", $f - $last_f);
493
494                // Convert bitstring for storage - we do not use binary/bytes because PHP's string functions are not fully binary safe
495                for ($i = 0, $bit_length = strlen($bitstring); $i < $bit_length; $i += 31)
496                {
497                    $hold_str .= str_pad(base_convert(str_pad(substr($bitstring, $i, 31), 31, 0, STR_PAD_RIGHT), 2, 36), 6, 0, STR_PAD_LEFT);
498                }
499
500                $last_f = $f;
501            }
502            unset($bitstring);
503
504            $hold_str = rtrim($hold_str);
505        }
506
507        return $hold_str;
508    }
509
510    /**
511    * Clear one or all users cached permission settings
512    */
513    function acl_clear_prefetch($user_id = false)
514    {
515        global $db, $cache, $phpbb_dispatcher;
516
517        // Rebuild options cache
518        $cache->destroy('_role_cache');
519
520        $sql = 'SELECT *
521            FROM ' . ACL_ROLES_DATA_TABLE . '
522            ORDER BY role_id ASC';
523        $result = $db->sql_query($sql);
524
525        $role_cache = array();
526        while ($row = $db->sql_fetchrow($result))
527        {
528            $role_cache[$row['role_id']][$row['auth_option_id']] = (int) $row['auth_setting'];
529        }
530        $db->sql_freeresult($result);
531
532        foreach ($role_cache as $role_id => $role_options)
533        {
534            $role_cache[$role_id] = serialize($role_options);
535        }
536
537        $cache->put('_role_cache', $role_cache);
538
539        // Now empty user permissions
540        $where_sql = '';
541
542        if ($user_id !== false)
543        {
544            $user_id = (!is_array($user_id)) ? $user_id = array((int) $user_id) : array_map('intval', $user_id);
545            $where_sql = ' WHERE ' . $db->sql_in_set('user_id', $user_id);
546        }
547
548        $sql = 'UPDATE ' . USERS_TABLE . "
549            SET user_permissions = '',
550                user_perm_from = 0
551            $where_sql";
552        $db->sql_query($sql);
553
554        /**
555        * Event is triggered after user(s) permission settings cache has been cleared
556        *
557        * @event core.acl_clear_prefetch_after
558        * @var    mixed    user_id    User ID(s)
559        * @since 3.1.11-RC1
560        */
561        $vars = array('user_id');
562        extract($phpbb_dispatcher->trigger_event('core.acl_clear_prefetch_after', compact($vars)));
563    }
564
565    /**
566    * Get assigned roles
567    */
568    function acl_role_data($user_type, $role_type, $ug_id = false, $forum_id = false)
569    {
570        global $db;
571
572        $roles = array();
573
574        $sql_id = ($user_type == 'user') ? 'user_id' : 'group_id';
575
576        $sql_ug = ($ug_id !== false) ? ((!is_array($ug_id)) ? "AND a.$sql_id = $ug_id" : 'AND ' . $db->sql_in_set("a.$sql_id", $ug_id)) : '';
577        $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? "AND a.forum_id = $forum_id" : 'AND ' . $db->sql_in_set('a.forum_id', $forum_id)) : '';
578
579        // Grab assigned roles...
580        $sql = 'SELECT a.auth_role_id, a.' . $sql_id . ', a.forum_id
581            FROM ' . (($user_type == 'user') ? ACL_USERS_TABLE : ACL_GROUPS_TABLE) . ' a, ' . ACL_ROLES_TABLE . " r
582            WHERE a.auth_role_id = r.role_id
583                AND r.role_type = '" . $db->sql_escape($role_type) . "'
584                $sql_ug
585                $sql_forum
586            ORDER BY r.role_order ASC";
587        $result = $db->sql_query($sql);
588
589        while ($row = $db->sql_fetchrow($result))
590        {
591            $roles[$row[$sql_id]][$row['forum_id']] = $row['auth_role_id'];
592        }
593        $db->sql_freeresult($result);
594
595        return $roles;
596    }
597
598    /**
599    * Get raw acl data based on user/option/forum
600    */
601    function acl_raw_data($user_id = false, $opts = false, $forum_id = false)
602    {
603        global $db;
604
605        $sql_user = ($user_id !== false) ? ((!is_array($user_id)) ? 'user_id = ' . (int) $user_id : $db->sql_in_set('user_id', array_map('intval', $user_id))) : '';
606        $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : '';
607
608        $sql_opts = $sql_opts_select = $sql_opts_from = '';
609        $hold_ary = array();
610
611        if ($opts !== false)
612        {
613            $sql_opts_select = ', ao.auth_option';
614            $sql_opts_from = ', ' . ACL_OPTIONS_TABLE . ' ao';
615            $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts);
616        }
617
618        $sql_ary = array();
619
620        // Grab non-role settings - user-specific
621        $sql_ary[] = 'SELECT a.user_id, a.forum_id, a.auth_setting, a.auth_option_id' . $sql_opts_select . '
622            FROM ' . ACL_USERS_TABLE . ' a' . $sql_opts_from . '
623            WHERE a.auth_role_id = 0 ' .
624                (($sql_opts_from) ? 'AND a.auth_option_id = ao.auth_option_id ' : '') .
625                (($sql_user) ? 'AND a.' . $sql_user : '') . "
626                $sql_forum
627                $sql_opts";
628
629        // Now the role settings - user-specific
630        $sql_ary[] = 'SELECT a.user_id, a.forum_id, r.auth_option_id, r.auth_setting, r.auth_option_id' . $sql_opts_select . '
631            FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r' . $sql_opts_from . '
632            WHERE a.auth_role_id = r.role_id ' .
633                (($sql_opts_from) ? 'AND r.auth_option_id = ao.auth_option_id ' : '') .
634                (($sql_user) ? 'AND a.' . $sql_user : '') . "
635                $sql_forum
636                $sql_opts";
637
638        foreach ($sql_ary as $sql)
639        {
640            $result = $db->sql_query($sql);
641
642            while ($row = $db->sql_fetchrow($result))
643            {
644                $option = ($sql_opts_select) ? $row['auth_option'] : $this->acl_options['option'][$row['auth_option_id']];
645                $hold_ary[$row['user_id']][$row['forum_id']][$option] = $row['auth_setting'];
646            }
647            $db->sql_freeresult($result);
648        }
649
650        $sql_ary = array();
651
652        // Now grab group settings - non-role specific...
653        $sql_ary[] = 'SELECT ug.user_id, a.forum_id, a.auth_setting, a.auth_option_id' . $sql_opts_select . '
654            FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g' . $sql_opts_from . '
655            WHERE a.auth_role_id = 0 ' .
656                (($sql_opts_from) ? 'AND a.auth_option_id = ao.auth_option_id ' : '') . '
657                AND a.group_id = ug.group_id
658                AND g.group_id = ug.group_id
659                AND ug.user_pending = 0
660                AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1)
661                ' . (($sql_user) ? 'AND ug.' . $sql_user : '') . "
662                $sql_forum
663                $sql_opts";
664
665        // Now grab group settings - role specific...
666        $sql_ary[] = 'SELECT ug.user_id, a.forum_id, r.auth_setting, r.auth_option_id' . $sql_opts_select . '
667            FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g, ' . ACL_ROLES_DATA_TABLE . ' r' . $sql_opts_from . '
668            WHERE a.auth_role_id = r.role_id ' .
669                (($sql_opts_from) ? 'AND r.auth_option_id = ao.auth_option_id ' : '') . '
670                AND a.group_id = ug.group_id
671                AND g.group_id = ug.group_id
672                AND ug.user_pending = 0
673                AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1)
674                ' . (($sql_user) ? 'AND ug.' . $sql_user : '') . "
675                $sql_forum
676                $sql_opts";
677
678        foreach ($sql_ary as $sql)
679        {
680            $result = $db->sql_query($sql);
681
682            while ($row = $db->sql_fetchrow($result))
683            {
684                $option = ($sql_opts_select) ? $row['auth_option'] : $this->acl_options['option'][$row['auth_option_id']];
685
686                if (!isset($hold_ary[$row['user_id']][$row['forum_id']][$option]) || (isset($hold_ary[$row['user_id']][$row['forum_id']][$option]) && $hold_ary[$row['user_id']][$row['forum_id']][$option] != ACL_NEVER))
687                {
688                    $hold_ary[$row['user_id']][$row['forum_id']][$option] = $row['auth_setting'];
689
690                    // If we detect ACL_NEVER, we will unset the flag option (within building the bitstring it is correctly set again)
691                    if ($row['auth_setting'] == ACL_NEVER)
692                    {
693                        $flag = substr($option, 0, strpos($option, '_') + 1);
694
695                        if (isset($hold_ary[$row['user_id']][$row['forum_id']][$flag]) && $hold_ary[$row['user_id']][$row['forum_id']][$flag] == ACL_YES)
696                        {
697                            unset($hold_ary[$row['user_id']][$row['forum_id']][$flag]);
698
699/*                            if (in_array(ACL_YES, $hold_ary[$row['user_id']][$row['forum_id']]))
700                            {
701                                $hold_ary[$row['user_id']][$row['forum_id']][$flag] = ACL_YES;
702                            }
703*/
704                        }
705                    }
706                }
707            }
708            $db->sql_freeresult($result);
709        }
710
711        return $hold_ary;
712    }
713
714    /**
715    * Get raw user based permission settings
716    */
717    function acl_user_raw_data($user_id = false, $opts = false, $forum_id = false)
718    {
719        global $db;
720
721        $sql_user = ($user_id !== false) ? ((!is_array($user_id)) ? 'user_id = ' . (int) $user_id : $db->sql_in_set('user_id', array_map('intval', $user_id))) : '';
722        $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : '';
723
724        $sql_opts = '';
725        $hold_ary = $sql_ary = array();
726
727        if ($opts !== false)
728        {
729            $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts);
730        }
731
732        // Grab user settings - non-role specific...
733        $sql_ary[] = 'SELECT a.user_id, a.forum_id, a.auth_setting, a.auth_option_id, ao.auth_option
734            FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . ' ao
735            WHERE a.auth_role_id = 0
736                AND a.auth_option_id = ao.auth_option_id ' .
737                (($sql_user) ? 'AND a.' . $sql_user : '') . "
738                $sql_forum
739                $sql_opts
740            ORDER BY a.forum_id, ao.auth_option";
741
742        // Now the role settings - user-specific
743        $sql_ary[] = 'SELECT a.user_id, a.forum_id, r.auth_option_id, r.auth_setting, r.auth_option_id, ao.auth_option
744            FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' ao
745            WHERE a.auth_role_id = r.role_id
746                AND r.auth_option_id = ao.auth_option_id ' .
747                (($sql_user) ? 'AND a.' . $sql_user : '') . "
748                $sql_forum
749                $sql_opts
750            ORDER BY a.forum_id, ao.auth_option";
751
752        foreach ($sql_ary as $sql)
753        {
754            $result = $db->sql_query($sql);
755
756            while ($row = $db->sql_fetchrow($result))
757            {
758                $hold_ary[$row['user_id']][$row['forum_id']][$row['auth_option']] = $row['auth_setting'];
759            }
760            $db->sql_freeresult($result);
761        }
762
763        return $hold_ary;
764    }
765
766    /**
767    * Get raw group based permission settings
768    */
769    function acl_group_raw_data($group_id = false, $opts = false, $forum_id = false)
770    {
771        global $db;
772
773        $sql_group = ($group_id !== false) ? ((!is_array($group_id)) ? 'group_id = ' . (int) $group_id : $db->sql_in_set('group_id', array_map('intval', $group_id))) : '';
774        $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : '';
775        $sql_is_local = $forum_id !== false ? 'AND ao.is_local <> 0' : '';
776
777        $sql_opts = '';
778        $hold_ary = $sql_ary = array();
779
780        if ($opts !== false)
781        {
782            $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts);
783        }
784
785        // Grab group settings - non-role specific...
786        $sql_ary[] = 'SELECT a.group_id, a.forum_id, a.auth_setting, a.auth_option_id, ao.auth_option
787            FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . " ao
788            WHERE a.auth_role_id = 0
789                AND a.auth_option_id = ao.auth_option_id
790                $sql_is_local " .
791                (($sql_group) ? 'AND a.' . $sql_group : '') . "
792                $sql_forum
793                $sql_opts
794            ORDER BY a.forum_id, ao.auth_option";
795
796        // Now grab group settings - role specific...
797        $sql_ary[] = 'SELECT a.group_id, a.forum_id, r.auth_setting, r.auth_option_id, ao.auth_option
798            FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . " ao
799            WHERE a.auth_role_id = r.role_id
800                $sql_is_local
801                AND r.auth_option_id = ao.auth_option_id " .
802                (($sql_group) ? 'AND a.' . $sql_group : '') . "
803                $sql_forum
804                $sql_opts
805            ORDER BY a.forum_id, ao.auth_option";
806
807        foreach ($sql_ary as $sql)
808        {
809            $result = $db->sql_query($sql);
810
811            while ($row = $db->sql_fetchrow($result))
812            {
813                $hold_ary[$row['group_id']][$row['forum_id']][$row['auth_option']] = $row['auth_setting'];
814            }
815            $db->sql_freeresult($result);
816        }
817
818        return $hold_ary;
819    }
820
821    /**
822    * Get raw acl data based on user for caching user_permissions
823    * This function returns the same data as acl_raw_data(), but without the user id as the first key within the array.
824    */
825    function acl_raw_data_single_user($user_id)
826    {
827        global $db, $cache;
828
829        // Check if the role-cache is there
830        if (($role_cache = $cache->get('_role_cache')) === false)
831        {
832            $role_cache = array();
833
834            // We pre-fetch roles
835            $sql = 'SELECT *
836                FROM ' . ACL_ROLES_DATA_TABLE . '
837                ORDER BY role_id ASC';
838            $result = $db->sql_query($sql);
839
840            while ($row = $db->sql_fetchrow($result))
841            {
842                $role_cache[$row['role_id']][$row['auth_option_id']] = (int) $row['auth_setting'];
843            }
844            $db->sql_freeresult($result);
845
846            foreach ($role_cache as $role_id => $role_options)
847            {
848                $role_cache[$role_id] = serialize($role_options);
849            }
850
851            $cache->put('_role_cache', $role_cache);
852        }
853
854        $hold_ary = array();
855
856        // Grab user-specific permission settings
857        $sql = 'SELECT forum_id, auth_option_id, auth_role_id, auth_setting
858            FROM ' . ACL_USERS_TABLE . '
859            WHERE user_id = ' . $user_id;
860        $result = $db->sql_query($sql);
861
862        while ($row = $db->sql_fetchrow($result))
863        {
864            // If a role is assigned, assign all options included within this role. Else, only set this one option.
865            if ($row['auth_role_id'])
866            {
867                $hold_ary[$row['forum_id']] = (empty($hold_ary[$row['forum_id']])) ? unserialize($role_cache[$row['auth_role_id']]) : $hold_ary[$row['forum_id']] + unserialize($role_cache[$row['auth_role_id']]);
868            }
869            else
870            {
871                $hold_ary[$row['forum_id']][$row['auth_option_id']] = $row['auth_setting'];
872            }
873        }
874        $db->sql_freeresult($result);
875
876        // Now grab group-specific permission settings
877        $sql = 'SELECT a.forum_id, a.auth_option_id, a.auth_role_id, a.auth_setting
878            FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g
879            WHERE a.group_id = ug.group_id
880                AND g.group_id = ug.group_id
881                AND ug.user_pending = 0
882                AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1)
883                AND ug.user_id = ' . $user_id;
884        $result = $db->sql_query($sql);
885
886        while ($row = $db->sql_fetchrow($result))
887        {
888            if (!$row['auth_role_id'])
889            {
890                $this->_set_group_hold_ary($hold_ary[$row['forum_id']], $row['auth_option_id'], $row['auth_setting']);
891            }
892            else if (!empty($role_cache[$row['auth_role_id']]))
893            {
894                foreach (unserialize($role_cache[$row['auth_role_id']]) as $option_id => $setting)
895                {
896                    $this->_set_group_hold_ary($hold_ary[$row['forum_id']], $option_id, $setting);
897                }
898            }
899        }
900        $db->sql_freeresult($result);
901
902        return $hold_ary;
903    }
904
905    /**
906    * Private function snippet for setting a specific piece of the hold_ary
907    */
908    function _set_group_hold_ary(&$hold_ary, $option_id, $setting)
909    {
910        if (!isset($hold_ary[$option_id]) || (isset($hold_ary[$option_id]) && $hold_ary[$option_id] != ACL_NEVER))
911        {
912            $hold_ary[$option_id] = $setting;
913
914            // If we detect ACL_NEVER, we will unset the flag option (within building the bitstring it is correctly set again)
915            if ($setting == ACL_NEVER)
916            {
917                $flag = substr($this->acl_options['option'][$option_id], 0, strpos($this->acl_options['option'][$option_id], '_') + 1);
918                $flag = (int) $this->acl_options['id'][$flag];
919
920                if (isset($hold_ary[$flag]) && $hold_ary[$flag] == ACL_YES)
921                {
922                    unset($hold_ary[$flag]);
923
924/*                    This is uncommented, because i suspect this being slightly wrong due to mixed permission classes being possible
925                    if (in_array(ACL_YES, $hold_ary))
926                    {
927                        $hold_ary[$flag] = ACL_YES;
928                    }*/
929                }
930            }
931        }
932    }
933
934    /**
935    * Authentication plug-ins is largely down to Sergey Kanareykin, our thanks to him.
936    */
937    function login($username, $password, $autologin = false, $viewonline = 1, $admin = 0)
938    {
939        global $db, $user, $phpbb_root_path, $phpEx, $phpbb_container;
940        global $phpbb_dispatcher;
941
942        /* @var $provider_collection \phpbb\auth\provider_collection */
943        $provider_collection = $phpbb_container->get('auth.provider_collection');
944
945        $provider = $provider_collection->get_provider();
946        if ($provider)
947        {
948            $login = $provider->login($username, $password);
949
950            // If the auth module wants us to create an empty profile do so and then treat the status as LOGIN_SUCCESS
951            if ($login['status'] == LOGIN_SUCCESS_CREATE_PROFILE)
952            {
953                // we are going to use the user_add function so include functions_user.php if it wasn't defined yet
954                if (!function_exists('user_add'))
955                {
956                    include($phpbb_root_path . 'includes/functions_user.' . $phpEx);
957                }
958
959                user_add($login['user_row'], (isset($login['cp_data'])) ? $login['cp_data'] : false);
960
961                $sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type
962                    FROM ' . USERS_TABLE . "
963                    WHERE username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'";
964                $result = $db->sql_query($sql);
965                $row = $db->sql_fetchrow($result);
966                $db->sql_freeresult($result);
967
968                if (!$row)
969                {
970                    return array(
971                        'status'        => LOGIN_ERROR_EXTERNAL_AUTH,
972                        'error_msg'        => 'AUTH_NO_PROFILE_CREATED',
973                        'user_row'        => array('user_id' => ANONYMOUS),
974                    );
975                }
976
977                $login = array(
978                    'status'    => LOGIN_SUCCESS,
979                    'error_msg'    => false,
980                    'user_row'    => $row,
981                );
982            }
983
984            // If the auth provider wants us to link an empty account do so and redirect
985            if ($login['status'] == LOGIN_SUCCESS_LINK_PROFILE)
986            {
987                // If this status exists a fourth field is in the $login array called 'redirect_data'
988                // This data is passed along as GET data to the next page allow the account to be linked
989
990                $params = array('mode' => 'login_link');
991                $url = append_sid($phpbb_root_path . 'ucp.' . $phpEx, array_merge($params, $login['redirect_data']));
992
993                redirect($url);
994            }
995
996            /**
997             * Event is triggered after checking for valid username and password, and before the actual session creation.
998             *
999             * @event core.auth_login_session_create_before
1000             * @var    array    login                Variable containing login array
1001             * @var    bool    admin                Boolean variable whether user is logging into the ACP
1002             * @var    string    username            Username of user to log in
1003             * @var    bool    autologin            Boolean variable signaling whether login is triggered via auto login
1004             * @since 3.1.7-RC1
1005             */
1006            $vars = array(
1007                'login',
1008                'admin',
1009                'username',
1010                'autologin',
1011            );
1012            extract($phpbb_dispatcher->trigger_event('core.auth_login_session_create_before', compact($vars)));
1013
1014            // If login succeeded, we will log the user in... else we pass the login array through...
1015            if ($login['status'] == LOGIN_SUCCESS)
1016            {
1017                $old_session_id = $user->session_id;
1018
1019                if ($admin)
1020                {
1021                    global $SID, $_SID;
1022
1023                    $cookie_expire = time() - 31536000;
1024                    $user->set_cookie('u', '', $cookie_expire);
1025                    $user->set_cookie('sid', '', $cookie_expire);
1026                    unset($cookie_expire);
1027
1028                    $SID = '?sid=';
1029                    $user->session_id = $_SID = '';
1030                }
1031
1032                $result = $user->session_create($login['user_row']['user_id'], $admin, $autologin, $viewonline);
1033
1034                // Successful session creation
1035                if ($result === true)
1036                {
1037                    // If admin re-authentication we remove the old session entry because a new one has been created...
1038                    if ($admin)
1039                    {
1040                        // the login array is used because the user ids do not differ for re-authentication
1041                        $sql = 'DELETE FROM ' . SESSIONS_TABLE . "
1042                            WHERE session_id = '" . $db->sql_escape($old_session_id) . "'
1043                            AND session_user_id = {$login['user_row']['user_id']}";
1044                        $db->sql_query($sql);
1045                    }
1046
1047                    return array(
1048                        'status'        => LOGIN_SUCCESS,
1049                        'error_msg'        => false,
1050                        'user_row'        => $login['user_row'],
1051                    );
1052                }
1053
1054                return array(
1055                    'status'        => LOGIN_BREAK,
1056                    'error_msg'        => $result,
1057                    'user_row'        => $login['user_row'],
1058                );
1059            }
1060
1061            return $login;
1062        }
1063
1064        trigger_error('Authentication method not found', E_USER_ERROR);
1065    }
1066
1067    /**
1068    * Fill auth_option statement for later querying based on the supplied options
1069    */
1070    function build_auth_option_statement($key, $auth_options, &$sql_opts)
1071    {
1072        global $db;
1073
1074        if (!is_array($auth_options))
1075        {
1076            if (strpos($auth_options, '%') !== false)
1077            {
1078                $sql_opts = "AND $key " . $db->sql_like_expression(str_replace('%', $db->get_any_char(), $auth_options));
1079            }
1080            else
1081            {
1082                $sql_opts = "AND $key = '" . $db->sql_escape($auth_options) . "'";
1083            }
1084        }
1085        else
1086        {
1087            $is_like_expression = false;
1088
1089            foreach ($auth_options as $option)
1090            {
1091                if (strpos($option, '%') !== false)
1092                {
1093                    $is_like_expression = true;
1094                }
1095            }
1096
1097            if (!$is_like_expression)
1098            {
1099                $sql_opts = 'AND ' . $db->sql_in_set($key, $auth_options);
1100            }
1101            else
1102            {
1103                $sql = array();
1104
1105                foreach ($auth_options as $option)
1106                {
1107                    if (strpos($option, '%') !== false)
1108                    {
1109                        $sql[] = $key . ' ' . $db->sql_like_expression(str_replace('%', $db->get_any_char(), $option));
1110                    }
1111                    else
1112                    {
1113                        $sql[] = $key . " = '" . $db->sql_escape($option) . "'";
1114                    }
1115                }
1116
1117                $sql_opts = 'AND (' . implode(' OR ', $sql) . ')';
1118            }
1119        }
1120    }
1121}