Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.92% covered (warning)
72.92%
35 / 48
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserStatementProvider
72.92% covered (warning)
72.92%
35 / 48
50.00% covered (danger)
50.00%
2 / 4
10.61
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%
8 / 8
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            // Include some of the OpenID Connect attributes
81            // http://openid.net/specs/openid-connect-core-1_0.html (draft 14)
82            // Issuer Identifier for the Issuer of the response.
83            'iss' => $this->config->get( 'CanonicalServer' ),
84
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            'aud' => $this->consumer->getConsumerKey(),
89
90            // Expiration time on or after which the ID Token MUST NOT be accepted for processing.
91            'exp' => (int)wfTimestamp() + 100,
92
93            // Time at which the JWT was issued.
94            'iat' => (int)wfTimestamp(),
95        ];
96
97        // TODO: Add auth_time, if we start tracking last login timestamp
98
99        $statement += $this->getUserProfile();
100
101        return $statement;
102    }
103
104    /**
105     * Retrieve user profile information
106     *
107     * @return array
108     */
109    public function getUserProfile() {
110        $profile = [
111            // 'sub' should be a StringOrURI - https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.2
112            'sub' => (string)Utils::getCentralIdFromLocalUser( $this->user ),
113        ];
114        // Include some MediaWiki info about the user
115        if ( !$this->user->isHidden() ) {
116            $profile['username'] = $this->user->getName();
117            $profile['editcount'] = intval( $this->user->getEditCount() );
118            // Use confirmed_email for B/C and email_verified because it's in the OIDC spec
119            $profile['confirmed_email'] = $profile['email_verified'] = $this->user->isEmailConfirmed();
120            $profile['blocked'] = $this->user->getBlock() !== null;
121            $profile['registered'] = $this->user->getRegistration();
122            $profile['groups'] = $this->userGroupManager->getUserEffectiveGroups( $this->user );
123            $profile['rights'] = array_values( array_unique(
124                MediaWikiServices::getInstance()->getPermissionManager()->getUserPermissions( $this->user )
125            ) );
126            $profile['grants'] = $this->grants;
127
128            if ( in_array( 'mwoauth-authonlyprivate', $this->grants ) ||
129                in_array( 'viewmyprivateinfo', $this->grantsInfo->getGrantRights( $profile['grants'] ) )
130            ) {
131                // Paranoia - avoid showing the real name if the wiki is not configured to use
132                // it but it somehow exists (from past configuration, or some identity management
133                // extension). This is important as the viewmyprivateinfo grant is presented
134                // to the user differently when useRealNames() is false.
135                // Don't omit the field completely to avoid a breaking change.
136                $profile['realname'] = !in_array(
137                    'realname', $this->config->get( 'HiddenPrefs' ), true
138                ) ? $this->user->getRealName() : '';
139                $profile['email'] = $this->user->isEmailConfirmed() ? $this->user->getEmail() : '';
140            }
141        } else {
142            $profile['blocked'] = true;
143        }
144
145        return $profile;
146    }
147}