Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.34% covered (warning)
72.34%
34 / 47
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserStatementProvider
72.34% covered (warning)
72.34%
34 / 47
50.00% covered (danger)
50.00%
2 / 4
10.71
0.00% covered (danger)
0.00%
0 / 1
 factory
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getUserStatement
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getUserProfile
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace MediaWiki\Extension\OAuth;
4
5use MediaWiki\Config\Config;
6use MediaWiki\Extension\OAuth\Backend\Consumer;
7use MediaWiki\Extension\OAuth\Backend\Utils;
8use MediaWiki\MediaWikiServices;
9use MediaWiki\Permissions\GrantsInfo;
10use MediaWiki\User\User;
11use MediaWiki\User\UserGroupManager;
12
13class UserStatementProvider {
14    /** @var Config */
15    protected $config;
16    /** @var User */
17    protected $user;
18    /** @var Consumer */
19    protected $consumer;
20    /** @var string[] */
21    protected $grants;
22    /** @var UserGroupManager */
23    private $userGroupManager;
24    /** @var GrantsInfo */
25    private $grantsInfo;
26
27    /**
28     * @param User $user
29     * @param Consumer $consumer
30     * @param array $grants
31     * @return static
32     */
33    public static function factory( User $user, Consumer $consumer, $grants = [] ) {
34        $services = MediaWikiServices::getInstance();
35        $mainConfig = $services->getMainConfig();
36        $userGroupManager = $services->getUserGroupManager();
37        $grantsInfo = $services->getGrantsInfo();
38        return new static(
39            $mainConfig,
40            $user,
41            $consumer,
42            $grants,
43            $userGroupManager,
44            $grantsInfo
45        );
46    }
47
48    /**
49     * UserStatementProvider constructor.
50     * @param Config $config
51     * @param User $user
52     * @param Consumer $consumer
53     * @param array $grants
54     * @param UserGroupManager $userGroupManager
55     * @param GrantsInfo $grantsInfo
56     */
57    protected function __construct(
58        $config,
59        $user,
60        $consumer,
61        $grants,
62        $userGroupManager,
63        $grantsInfo
64    ) {
65        $this->config = $config;
66        $this->user = $user;
67        $this->consumer = $consumer;
68        $this->grants = $grants;
69        $this->userGroupManager = $userGroupManager;
70        $this->grantsInfo = $grantsInfo;
71    }
72
73    /**
74     * Retrieve user statement suitable for JWT encoding
75     *
76     * @return array
77     */
78    public function getUserStatement() {
79        $statement = [];
80
81        // Include some of the OpenID Connect attributes
82        // http://openid.net/specs/openid-connect-core-1_0.html (draft 14)
83        // Issuer Identifier for the Issuer of the response.
84        $statement['iss'] = $this->config->get( 'CanonicalServer' );
85        // Subject identifier. A locally unique and never reassigned identifier.
86        // T264560: sub added via $this->getUserProfile()
87        // Audience(s) that this ID Token is intended for.
88        $statement['aud'] = $this->consumer->getConsumerKey();
89        // Expiration time on or after which the ID Token MUST NOT be accepted for processing.
90        $statement['exp'] = (int)wfTimestamp() + 100;
91        // Time at which the JWT was issued.
92        $statement['iat'] = (int)wfTimestamp();
93        // TODO: Add auth_time, if we start tracking last login timestamp
94
95        $statement += $this->getUserProfile();
96
97        return $statement;
98    }
99
100    /**
101     * Retrieve user profile information
102     *
103     * @return array
104     */
105    public function getUserProfile() {
106        $profile = [
107            'sub' => Utils::getCentralIdFromLocalUser( $this->user ),
108        ];
109        // Include some MediaWiki info about the user
110        if ( !$this->user->isHidden() ) {
111            $profile['username'] = $this->user->getName();
112            $profile['editcount'] = intval( $this->user->getEditCount() );
113            // Use confirmed_email for B/C and email_verified because it's in the OIDC spec
114            $profile['confirmed_email'] = $profile['email_verified'] = $this->user->isEmailConfirmed();
115            $profile['blocked'] = $this->user->getBlock() !== null;
116            $profile['registered'] = $this->user->getRegistration();
117            $profile['groups'] = $this->userGroupManager->getUserEffectiveGroups( $this->user );
118            $profile['rights'] = array_values( array_unique(
119                MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this->user )
120            ) );
121            $profile['grants'] = $this->grants;
122
123            if ( in_array( 'mwoauth-authonlyprivate', $this->grants ) ||
124                in_array( 'viewmyprivateinfo', $this->grantsInfo->getGrantRights( $profile['grants'] ) )
125            ) {
126                // Paranoia - avoid showing the real name if the wiki is not configured to use
127                // it but it somehow exists (from past configuration, or some identity management
128                // extension). This is important as the viewmyprivateinfo grant is presented
129                // to the user differently when useRealNames() is false.
130                // Don't omit the field completely to avoid a breaking change.
131                $profile['realname'] = !in_array(
132                    'realname', $this->config->get( 'HiddenPrefs' ), true
133                ) ? $this->user->getRealName() : '';
134                $profile['email'] = $this->user->isEmailConfirmed() ? $this->user->getEmail() : '';
135            }
136        } else {
137            $profile['blocked'] = true;
138        }
139
140        return $profile;
141    }
142}