Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
PostgresUtils
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 6
306
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 canCreateAccounts
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 isSuperUser
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 canCreateObjectsForWebUser
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 getInstallUserPermissions
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 isRoleMember
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2
3namespace MediaWiki\Installer\Task;
4
5use MediaWiki\MainConfigNames;
6use Wikimedia\Rdbms\IMaintainableDatabase;
7
8/**
9 * @internal For use by the installer
10 */
11class PostgresUtils {
12    private const MAX_ROLE_SEARCH_DEPTH = 5;
13
14    /** @var ITaskContext */
15    private $context;
16
17    public function __construct( ITaskContext $context ) {
18        $this->context = $context;
19    }
20
21    public function canCreateAccounts(): bool {
22        $perms = $this->getInstallUserPermissions();
23        return ( $perms && $perms->rolsuper ) || $perms->rolcreaterole;
24    }
25
26    public function isSuperUser(): bool {
27        $perms = $this->getInstallUserPermissions();
28        return $perms && $perms->rolsuper;
29    }
30
31    /**
32     * Returns true if the install user is able to create objects owned
33     * by the web user, false otherwise.
34     * @return bool
35     */
36    public function canCreateObjectsForWebUser() {
37        if ( $this->isSuperUser() ) {
38            return true;
39        }
40
41        $status = $this->context->getConnection( ITaskContext::CONN_CREATE_DATABASE );
42        if ( !$status->isOK() ) {
43            return false;
44        }
45        $conn = $status->getDB();
46        $installerId = $conn->selectField( 'pg_catalog.pg_roles', 'oid',
47            [ 'rolname' => $this->context->getOption( 'InstallUser' ) ], __METHOD__ );
48        $webId = $conn->selectField( 'pg_catalog.pg_roles', 'oid',
49            [ 'rolname' => $this->context->getConfigVar( MainConfigNames::DBuser ) ], __METHOD__ );
50
51        return self::isRoleMember( $conn, $installerId, $webId, self::MAX_ROLE_SEARCH_DEPTH );
52    }
53
54    /** @return \stdClass|false */
55    private function getInstallUserPermissions() {
56        $status = $this->context->getConnection( ITaskContext::CONN_CREATE_DATABASE );
57        if ( !$status->isOK() ) {
58            return false;
59        }
60        $conn = $status->getDB();
61        $superuser = $this->context->getOption( 'InstallUser' );
62
63        $row = $conn->selectRow( 'pg_catalog.pg_roles', '*',
64            [ 'rolname' => $superuser ], __METHOD__ );
65
66        return $row;
67    }
68
69    /**
70     * Recursive helper for canCreateObjectsForWebUser().
71     * @param IMaintainableDatabase $conn
72     * @param int $targetMember Role ID of the member to look for
73     * @param int $group Role ID of the group to look for
74     * @param int $maxDepth Maximum recursive search depth
75     * @return bool
76     */
77    private function isRoleMember( $conn, $targetMember, $group, $maxDepth ) {
78        if ( $targetMember === $group ) {
79            // A role is always a member of itself
80            return true;
81        }
82        // Get all members of the given group
83        $res = $conn->select( 'pg_catalog.pg_auth_members', [ 'member' ],
84            [ 'roleid' => $group ], __METHOD__ );
85        foreach ( $res as $row ) {
86            if ( $row->member == $targetMember ) {
87                // Found target member
88                return true;
89            }
90            // Recursively search each member of the group to see if the target
91            // is a member of it, up to the given maximum depth.
92            if ( $maxDepth > 0 &&
93                $this->isRoleMember( $conn, $targetMember, $row->member, $maxDepth - 1 )
94            ) {
95                // Found member of member
96                return true;
97            }
98        }
99
100        return false;
101    }
102}