Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
33.33% covered (danger)
33.33%
4 / 12
CRAP
72.11% covered (warning)
72.11%
212 / 294
permission
0.00% covered (danger)
0.00%
0 / 1
33.33% covered (danger)
33.33%
4 / 12
188.62
72.11% covered (warning)
72.11%
212 / 294
 __construct
0.00% covered (danger)
0.00%
0 / 1
2.01
88.89% covered (warning)
88.89%
8 / 9
 get_name
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 1
 exists
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
12 / 12
 add
100.00% covered (success)
100.00%
1 / 1
9
100.00% covered (success)
100.00%
29 / 29
 remove
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
24 / 24
 role_exists
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
6 / 6
 role_add
0.00% covered (danger)
0.00%
0 / 1
3.00
92.86% covered (success)
92.86%
13 / 14
 role_update
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 7
 role_remove
0.00% covered (danger)
0.00%
0 / 1
7
95.00% covered (success)
95.00%
38 / 40
 permission_set
0.00% covered (danger)
0.00%
0 / 1
18.05
94.59% covered (success)
94.59%
70 / 74
 permission_unset
0.00% covered (danger)
0.00%
0 / 1
110.00
0.00% covered (danger)
0.00%
0 / 46
 reverse
0.00% covered (danger)
0.00%
0 / 1
34.41
37.50% covered (danger)
37.50%
12 / 32
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/
namespace phpbb\db\migration\tool;
/**
* Migration permission management tool
*/
class permission implements \phpbb\db\migration\tool\tool_interface
{
    /** @var \phpbb\auth\auth */
    protected $auth;
    /** @var \includes\acp\auth\auth_admin */
    protected $auth_admin;
    /** @var \phpbb\cache\service */
    protected $cache;
    /** @var \phpbb\db\driver\driver_interface */
    protected $db;
    /** @var string */
    protected $phpbb_root_path;
    /** @var string */
    protected $php_ext;
    /**
    * Constructor
    *
    * @param \phpbb\db\driver\driver_interface $db
    * @param \phpbb\cache\service $cache
    * @param \phpbb\auth\auth $auth
    * @param string $phpbb_root_path
    * @param string $php_ext
    */
    public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\auth\auth $auth, $phpbb_root_path, $php_ext)
    {
        $this->db = $db;
        $this->cache = $cache;
        $this->auth = $auth;
        $this->phpbb_root_path = $phpbb_root_path;
        $this->php_ext = $php_ext;
        if (!class_exists('auth_admin'))
        {
            include($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext);
        }
        $this->auth_admin = new \auth_admin();
    }
    /**
    * {@inheritdoc}
    */
    public function get_name()
    {
        return 'permission';
    }
    /**
    * Permission Exists
    *
    * Check if a permission (auth) setting exists
    *
    * @param string $auth_option The name of the permission (auth) option
    * @param bool $global True for checking a global permission setting,
    *     False for a local permission setting
    * @return bool true if it exists, false if not
    */
    public function exists($auth_option, $global = true)
    {
        if ($global)
        {
            $type_sql = ' AND is_global = 1';
        }
        else
        {
            $type_sql = ' AND is_local = 1';
        }
        $sql = 'SELECT auth_option_id
            FROM ' . ACL_OPTIONS_TABLE . "
            WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'"
                . $type_sql;
        $result = $this->db->sql_query($sql);
        $row = $this->db->sql_fetchrow($result);
        $this->db->sql_freeresult($result);
        if ($row)
        {
            return true;
        }
        return false;
    }
    /**
    * Permission Add
    *
    * Add a permission (auth) option
    *
    * @param string $auth_option The name of the permission (auth) option
    * @param bool $global True for checking a global permission setting,
    *     False for a local permission setting
    * @param int|false $copy_from If set, contains the id of the permission from which to copy the new one.
    * @return null
    */
    public function add($auth_option, $global = true, $copy_from = false)
    {
        if ($this->exists($auth_option, $global))
        {
            return;
        }
        // We've added permissions, so set to true to notify the user.
        $this->permissions_added = true;
        // We have to add a check to see if the !$global (if global, local, and if local, global) permission already exists.  If it does, acl_add_option currently has a bug which would break the ACL system, so we are having a work-around here.
        if ($this->exists($auth_option, !$global))
        {
            $sql_ary = array(
                'is_global'    => 1,
                'is_local'    => 1,
            );
            $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . '
                SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . "
                WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'";
            $this->db->sql_query($sql);
        }
        else
        {
            if ($global)
            {
                $this->auth_admin->acl_add_option(array('global' => array($auth_option)));
            }
            else
            {
                $this->auth_admin->acl_add_option(array('local' => array($auth_option)));
            }
        }
        // The permission has been added, now we can copy it if needed
        if ($copy_from && isset($this->auth_admin->acl_options['id'][$copy_from]))
        {
            $old_id = $this->auth_admin->acl_options['id'][$copy_from];
            $new_id = $this->auth_admin->acl_options['id'][$auth_option];
            $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE);
            foreach ($tables as $table)
            {
                $sql = 'SELECT *
                    FROM ' . $table . '
                    WHERE auth_option_id = ' . $old_id;
                $result = $this->db->sql_query($sql);
                $sql_ary = array();
                while ($row = $this->db->sql_fetchrow($result))
                {
                    $row['auth_option_id'] = $new_id;
                    $sql_ary[] = $row;
                }
                $this->db->sql_freeresult($result);
                if (!empty($sql_ary))
                {
                    $this->db->sql_multi_insert($table, $sql_ary);
                }
            }
            $this->auth_admin->acl_clear_prefetch();
        }
    }
    /**
    * Permission Remove
    *
    * Remove a permission (auth) option
    *
    * @param string $auth_option The name of the permission (auth) option
    * @param bool $global True for checking a global permission setting,
    *     False for a local permission setting
    * @return null
    */
    public function remove($auth_option, $global = true)
    {
        if (!$this->exists($auth_option, $global))
        {
            return;
        }
        if ($global)
        {
            $type_sql = ' AND is_global = 1';
        }
        else
        {
            $type_sql = ' AND is_local = 1';
        }
        $sql = 'SELECT auth_option_id, is_global, is_local
            FROM ' . ACL_OPTIONS_TABLE . "
            WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" .
                $type_sql;
        $result = $this->db->sql_query($sql);
        $row = $this->db->sql_fetchrow($result);
        $this->db->sql_freeresult($result);
        $id = (int) $row['auth_option_id'];
        // If it is a local and global permission, do not remove the row! :P
        if ($row['is_global'] && $row['is_local'])
        {
            $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . '
                SET ' . (($global) ? 'is_global = 0' : 'is_local = 0') . '
                WHERE auth_option_id = ' . $id;
            $this->db->sql_query($sql);
        }
        else
        {
            // Delete time
            $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE, ACL_OPTIONS_TABLE);
            foreach ($tables as $table)
            {
                $this->db->sql_query('DELETE FROM ' . $table . '
                    WHERE auth_option_id = ' . $id);
            }
        }
        // Purge the auth cache
        $this->cache->destroy('_acl_options');
        $this->auth->acl_clear_prefetch();
    }
    /**
     * Check if a permission role exists
     *
     * @param string $role_name The role name
     *
     * @return int The id of the role if it exists, 0 otherwise
     */
    public function role_exists($role_name)
    {
        $sql = 'SELECT role_id
            FROM ' . ACL_ROLES_TABLE . "
            WHERE role_name = '" . $this->db->sql_escape($role_name) . "'";
        $result = $this->db->sql_query($sql);
        $role_id = (int) $this->db->sql_fetchfield('role_id');
        $this->db->sql_freeresult($result);
        return $role_id;
    }
    /**
    * Add a new permission role
    *
    * @param string $role_name The new role name
    * @param string $role_type The type (u_, m_, a_)
    * @param string $role_description Description of the new role
    *
    * @return null
    */
    public function role_add($role_name, $role_type, $role_description = '')
    {
        if ($this->role_exists($role_name))
        {
            return;
        }
        $sql = 'SELECT MAX(role_order) AS max_role_order
            FROM ' . ACL_ROLES_TABLE . "
            WHERE role_type = '" . $this->db->sql_escape($role_type) . "'";
        $this->db->sql_query($sql);
        $role_order = (int) $this->db->sql_fetchfield('max_role_order');
        $role_order = (!$role_order) ? 1 : $role_order + 1;
        $sql_ary = array(
            'role_name'            => $role_name,
            'role_description'    => $role_description,
            'role_type'            => $role_type,
            'role_order'        => $role_order,
        );
        $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary);
        $this->db->sql_query($sql);
        return $this->db->sql_nextid();
    }
    /**
    * Update the name on a permission role
    *
    * @param string $old_role_name The old role name
    * @param string $new_role_name The new role name
    * @return null
    * @throws \phpbb\db\migration\exception
    */
    public function role_update($old_role_name, $new_role_name)
    {
        if (!$this->role_exists($old_role_name))
        {
            throw new \phpbb\db\migration\exception('ROLE_NOT_EXIST', $old_role_name);
        }
        $sql = 'UPDATE ' . ACL_ROLES_TABLE . "
            SET role_name = '" . $this->db->sql_escape($new_role_name) . "'
            WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'";
        $this->db->sql_query($sql);
    }
    /**
    * Remove a permission role
    *
    * @param string $role_name The role name to remove
    * @return null
    */
    public function role_remove($role_name)
    {
        if (!($role_id = $this->role_exists($role_name)))
        {
            return;
        }
        // Get the role type
        $sql = 'SELECT role_type
            FROM ' . ACL_ROLES_TABLE . '
            WHERE role_id = ' . (int) $role_id;
        $result = $this->db->sql_query($sql);
        $role_type = $this->db->sql_fetchfield('role_type');
        $this->db->sql_freeresult($result);
        // Get complete auth array
        $sql = 'SELECT auth_option, auth_option_id
            FROM ' . ACL_OPTIONS_TABLE . "
            WHERE auth_option " . $this->db->sql_like_expression($role_type . $this->db->get_any_char());
        $result = $this->db->sql_query($sql);
        $auth_settings = [];
        while ($row = $this->db->sql_fetchrow($result))
        {
            $auth_settings[$row['auth_option']] = ACL_NO;
        }
        $this->db->sql_freeresult($result);
        // Get the role auth settings we need to re-set...
        $sql = 'SELECT o.auth_option, r.auth_setting
            FROM ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' o
            WHERE o.auth_option_id = r.auth_option_id
                AND r.role_id = ' . (int) $role_id;
        $result = $this->db->sql_query($sql);
        while ($row = $this->db->sql_fetchrow($result))
        {
            $auth_settings[$row['auth_option']] = $row['auth_setting'];
        }
        $this->db->sql_freeresult($result);
        // Get role assignments
        $hold_ary = $this->auth_admin->get_role_mask($role_id);
        // Re-assign permissions
        foreach ($hold_ary as $forum_id => $forum_ary)
        {
            if (isset($forum_ary['users']))
            {
                $this->auth_admin->acl_set('user', $forum_id, $forum_ary['users'], $auth_settings, 0, false);
            }
            if (isset($forum_ary['groups']))
            {
                $this->auth_admin->acl_set('group', $forum_id, $forum_ary['groups'], $auth_settings, 0, false);
            }
        }
        // Remove role from users and groups just to be sure (happens through acl_set)
        $sql = 'DELETE FROM ' . ACL_USERS_TABLE . '
            WHERE auth_role_id = ' . $role_id;
        $this->db->sql_query($sql);
        $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . '
            WHERE auth_role_id = ' . $role_id;
        $this->db->sql_query($sql);
        $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . '
            WHERE role_id = ' . $role_id;
        $this->db->sql_query($sql);
        $sql = 'DELETE FROM ' . ACL_ROLES_TABLE . '
            WHERE role_id = ' . $role_id;
        $this->db->sql_query($sql);
        $this->auth->acl_clear_prefetch();
    }
    /**
    * Permission Set
    *
    * Allows you to set permissions for a certain group/role
    *
    * @param string $name The name of the role/group
    * @param string|array $auth_option The auth_option or array of
    *     auth_options you would like to set
    * @param string $type The type (role|group)
    * @param bool $has_permission True if you want to give them permission,
    *     false if you want to deny them permission
    * @return null
    * @throws \phpbb\db\migration\exception
    */
    public function permission_set($name, $auth_option, $type = 'role', $has_permission = true)
    {
        if (!is_array($auth_option))
        {
            $auth_option = array($auth_option);
        }
        $new_auth = array();
        $sql = 'SELECT auth_option_id
            FROM ' . ACL_OPTIONS_TABLE . '
            WHERE ' . $this->db->sql_in_set('auth_option', $auth_option);
        $result = $this->db->sql_query($sql);
        while ($row = $this->db->sql_fetchrow($result))
        {
            $new_auth[] = (int) $row['auth_option_id'];
        }
        $this->db->sql_freeresult($result);
        $type = (string) $type; // Prevent PHP bug.
        if (empty($new_auth) || !in_array($type, ['role','group']))
        {
            return;
        }
        $current_auth = array();
        switch ($type)
        {
            case 'role':
                if (!($role_id = $this->role_exists($name)))
                {
                    throw new \phpbb\db\migration\exception('ROLE_NOT_EXIST', $name);
                }
                $sql = 'SELECT auth_option_id, auth_setting
                    FROM ' . ACL_ROLES_DATA_TABLE . '
                    WHERE role_id = ' . $role_id;
                $result = $this->db->sql_query($sql);
                while ($row = $this->db->sql_fetchrow($result))
                {
                    $current_auth[$row['auth_option_id']] = $row['auth_setting'];
                }
                $this->db->sql_freeresult($result);
            break;
            case 'group':
                $sql = 'SELECT group_id
                    FROM ' . GROUPS_TABLE . "
                    WHERE group_name = '" . $this->db->sql_escape($name) . "'";
                $this->db->sql_query($sql);
                $group_id = (int) $this->db->sql_fetchfield('group_id');
                if (!$group_id)
                {
                    throw new \phpbb\db\migration\exception('GROUP_NOT_EXIST', $name);
                }
                // If the group has a role set for them we will add the requested permissions to that role.
                $sql = 'SELECT auth_role_id
                    FROM ' . ACL_GROUPS_TABLE . '
                    WHERE group_id = ' . $group_id . '
                        AND auth_role_id <> 0
                        AND forum_id = 0';
                $this->db->sql_query($sql);
                $role_id = (int) $this->db->sql_fetchfield('auth_role_id');
                if ($role_id)
                {
                    $sql = 'SELECT role_name, role_type
                        FROM ' . ACL_ROLES_TABLE . '
                        WHERE role_id = ' . $role_id;
                    $this->db->sql_query($sql);
                    $role_data = $this->db->sql_fetchrow();
                    if (!$role_data)
                    {
                        throw new \phpbb\db\migration\exception('ROLE_ASSIGNED_NOT_EXIST', $name, $role_id);
                    }
                    $role_name = $role_data['role_name'];
                    $role_type = $role_data['role_type'];
                    // Filter new auth options to match the role type: a_ | f_ | m_ | u_
                    // Set new auth options to the role only if options matching the role type were found
                    $auth_option = array_filter($auth_option,
                        function ($option) use ($role_type)
                        {
                            return strpos($option, $role_type) === 0;
                        }
                    );
                    if (count($auth_option))
                    {
                        return $this->permission_set($role_name, $auth_option, 'role', $has_permission);
                    }
                }
                $sql = 'SELECT auth_option_id, auth_setting
                    FROM ' . ACL_GROUPS_TABLE . '
                    WHERE group_id = ' . $group_id;
                $result = $this->db->sql_query($sql);
                while ($row = $this->db->sql_fetchrow($result))
                {
                    $current_auth[$row['auth_option_id']] = $row['auth_setting'];
                }
                $this->db->sql_freeresult($result);
            break;
        }
        $sql_ary = $auth_update_list = [];
        $table = $type == 'role' ? ACL_ROLES_DATA_TABLE : ACL_GROUPS_TABLE;
        foreach ($new_auth as $auth_option_id)
        {
            if (!isset($current_auth[$auth_option_id]))
            {
                $sql_ary[] = [
                    $type . '_id'        => ${$type . '_id'},
                    'auth_option_id'    => $auth_option_id,
                    'auth_setting'        => (int) $has_permission,
                ];
            }
            else
            {
                $auth_update_list[] = $auth_option_id;
            }
        }
        $this->db->sql_multi_insert($table, $sql_ary);
        if (count($auth_update_list))
        {
            $sql = 'UPDATE ' . $table . '
                SET auth_setting = ' . (int) $has_permission . '
                WHERE ' . $this->db->sql_in_set('auth_option_id', $auth_update_list) . '
                    AND ' . $type . '_id = ' .  (int) ${$type . '_id'};
            $this->db->sql_query($sql);
        }
        $this->auth->acl_clear_prefetch();
    }
    /**
    * Permission Unset
    *
    * Allows you to unset (remove) permissions for a certain group/role
    *
    * @param string $name The name of the role/group
    * @param string|array $auth_option The auth_option or array of
    *     auth_options you would like to set
    * @param string $type The type (role|group)
    * @return null
    * @throws \phpbb\db\migration\exception
    */
    public function permission_unset($name, $auth_option, $type = 'role')
    {
        if (!is_array($auth_option))
        {
            $auth_option = array($auth_option);
        }
        $to_remove = array();
        $sql = 'SELECT auth_option_id
            FROM ' . ACL_OPTIONS_TABLE . '
            WHERE ' . $this->db->sql_in_set('auth_option', $auth_option);
        $result = $this->db->sql_query($sql);
        while ($row = $this->db->sql_fetchrow($result))
        {
            $to_remove[] = (int) $row['auth_option_id'];
        }
        $this->db->sql_freeresult($result);
        if (empty($to_remove))
        {
            return;
        }
        $type = (string) $type; // Prevent PHP bug.
        switch ($type)
        {
            case 'role':
                if (!($role_id = $this->role_exists($name)))
                {
                    throw new \phpbb\db\migration\exception('ROLE_NOT_EXIST', $name);
                }
                $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . '
                    WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove) . '
                        AND role_id = ' . (int) $role_id;
                $this->db->sql_query($sql);
            break;
            case 'group':
                $sql = 'SELECT group_id
                    FROM ' . GROUPS_TABLE . "
                    WHERE group_name = '" . $this->db->sql_escape($name) . "'";
                $this->db->sql_query($sql);
                $group_id = (int) $this->db->sql_fetchfield('group_id');
                if (!$group_id)
                {
                    throw new \phpbb\db\migration\exception('GROUP_NOT_EXIST', $name);
                }
                // If the group has a role set for them we will remove the requested permissions from that role.
                $sql = 'SELECT auth_role_id
                    FROM ' . ACL_GROUPS_TABLE . '
                    WHERE group_id = ' . $group_id . '
                        AND auth_role_id <> 0';
                $this->db->sql_query($sql);
                $role_id = (int) $this->db->sql_fetchfield('auth_role_id');
                if ($role_id)
                {
                    $sql = 'SELECT role_name
                        FROM ' . ACL_ROLES_TABLE . '
                        WHERE role_id = ' . $role_id;
                    $this->db->sql_query($sql);
                    $role_name = $this->db->sql_fetchfield('role_name');
                    if (!$role_name)
                    {
                        throw new \phpbb\db\migration\exception('ROLE_ASSIGNED_NOT_EXIST', $name, $role_id);
                    }
                    return $this->permission_unset($role_name, $auth_option, 'role');
                }
                $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . '
                    WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove);
                $this->db->sql_query($sql);
            break;
        }
        $this->auth->acl_clear_prefetch();
    }
    /**
    * {@inheritdoc}
    */
    public function reverse()
    {
        $arguments = func_get_args();
        $original_call = array_shift($arguments);
        $call = false;
        switch ($original_call)
        {
            case 'add':
                $call = 'remove';
            break;
            case 'remove':
                $call = 'add';
            break;
            case 'permission_set':
                $call = 'permission_unset';
            break;
            case 'permission_unset':
                $call = 'permission_set';
            break;
            case 'role_add':
                $call = 'role_remove';
            break;
            case 'role_remove':
                $call = 'role_add';
            break;
            case 'role_update':
                // Set to the original value if the current value is what we compared to originally
                $arguments = array(
                    $arguments[1],
                    $arguments[0],
                );
            break;
            case 'reverse':
                // Reversing a reverse is just the call itself
                $call = array_shift($arguments);
            break;
        }
        if ($call)
        {
            return call_user_func_array(array(&$this, $call), $arguments);
        }
    }
}