Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
41.51% covered (danger)
41.51%
22 / 53
16.67% covered (danger)
16.67%
1 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
AccessTokenEntity
41.51% covered (danger)
41.51%
22 / 53
16.67% covered (danger)
16.67%
1 / 6
100.04
0.00% covered (danger)
0.00%
0 / 1
 __construct
55.00% covered (warning)
55.00%
11 / 20
0.00% covered (danger)
0.00%
0 / 1
11.47
 getApproval
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setPrivateKeyFromConfig
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getClient
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setApprovalFromClientScopesUser
31.58% covered (danger)
31.58%
6 / 19
0.00% covered (danger)
0.00%
0 / 1
22.70
 confirmClientUsable
44.44% covered (danger)
44.44%
4 / 9
0.00% covered (danger)
0.00%
0 / 1
4.54
1<?php
2
3namespace MediaWiki\Extension\OAuth\Entity;
4
5use InvalidArgumentException;
6use League\OAuth2\Server\CryptKey;
7use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
8use League\OAuth2\Server\Entities\ScopeEntityInterface;
9use League\OAuth2\Server\Entities\Traits\AccessTokenTrait;
10use League\OAuth2\Server\Entities\Traits\EntityTrait;
11use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
12use League\OAuth2\Server\Exception\OAuthServerException;
13use MediaWiki\Extension\OAuth\Backend\ConsumerAcceptance;
14use MediaWiki\Extension\OAuth\Backend\Utils;
15use MediaWiki\MediaWikiServices;
16use MediaWiki\User\User;
17use MediaWiki\WikiMap\WikiMap;
18use Throwable;
19
20class AccessTokenEntity implements AccessTokenEntityInterface {
21    use AccessTokenTrait;
22    use EntityTrait;
23    use TokenEntityTrait;
24
25    /**
26     * @var ClientEntity
27     */
28    protected $client;
29
30    /**
31     * User approval of the client
32     *
33     * @var ConsumerAcceptance|false
34     */
35    private $approval;
36
37    /**
38     * @param ClientEntity $clientEntity
39     * @param ScopeEntityInterface[] $scopes
40     * @param string $issuer
41     * @param string|int|null $userIdentifier
42     * @throws OAuthServerException
43     */
44    public function __construct(
45        ClientEntity $clientEntity,
46        array $scopes,
47        string $issuer,
48        $userIdentifier = null
49    ) {
50        $this->approval = $this->setApprovalFromClientScopesUser(
51            $clientEntity, $scopes, $userIdentifier
52        );
53
54        $this->setClient( $clientEntity );
55        $this->setIssuer( $issuer );
56        if ( $clientEntity->getOwnerOnly() ) {
57            if ( $userIdentifier !== null && $userIdentifier !== $clientEntity->getUserId() ) {
58                throw new InvalidArgumentException(
59                    '$userIdentifier must be null, or match the client owner user id,' .
60                    ' for owner-only clients, ' . $userIdentifier . ' given'
61                );
62            }
63            foreach ( $clientEntity->getScopes() as $scope ) {
64                $this->addScope( $scope );
65            }
66            $this->setUserIdentifier( $clientEntity->getUserId() );
67        } else {
68            foreach ( $scopes as $scope ) {
69                if ( !in_array( $scope->getIdentifier(), $clientEntity->getGrants() ) ) {
70                    continue;
71                }
72                $this->addScope( $scope );
73            }
74            $this->setUserIdentifier( $userIdentifier );
75        }
76
77        $this->confirmClientUsable();
78    }
79
80    /**
81     * Get the approval that allows this AT to be created
82     *
83     * @return ConsumerAcceptance|false
84     */
85    public function getApproval() {
86        return $this->approval;
87    }
88
89    /**
90     * Set configured private key
91     */
92    public function setPrivateKeyFromConfig() {
93        $oauthConfig = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'mwoauth' );
94        // Private key to sign the token
95        $privateKey = new CryptKey( $oauthConfig->get( 'OAuth2PrivateKey' ) );
96        $this->setPrivateKey( $privateKey );
97    }
98
99    /**
100     * Get the client that the token was issued to.
101     *
102     * @return ClientEntity
103     */
104    public function getClient() {
105        return $this->client;
106    }
107
108    /**
109     * @param ClientEntity $clientEntity
110     * @param array $scopes
111     * @param string|int|null $userIdentifier
112     * @return ConsumerAcceptance|false
113     */
114    private function setApprovalFromClientScopesUser(
115        ClientEntity $clientEntity, array $scopes, $userIdentifier = null
116    ) {
117        if ( $clientEntity->getOwnerOnly() && $userIdentifier === null ) {
118            $userIdentifier = $clientEntity->getUserId();
119            $scopes = $clientEntity->getScopes();
120        }
121        if ( !$userIdentifier ) {
122            return false;
123        }
124        try {
125            $user = Utils::getLocalUserFromCentralId( $userIdentifier );
126            $approval = $clientEntity->getCurrentAuthorization( $user, WikiMap::getCurrentWikiId() );
127        } catch ( Throwable $ex ) {
128            return false;
129        }
130        if ( !$approval ) {
131            return $approval;
132        }
133
134        $approvedScopes = $approval->getGrants();
135        $notApproved = array_filter(
136            $scopes,
137            static function ( ScopeEntityInterface $scope ) use ( $approvedScopes ) {
138                return !in_array( $scope->getIdentifier(), $approvedScopes, true );
139            }
140        );
141
142        return !$notApproved ? $approval : false;
143    }
144
145    private function confirmClientUsable() {
146        $userId = $this->getUserIdentifier() ?? 0;
147        $user = Utils::getLocalUserFromCentralId( $userId );
148        if ( !$user ) {
149            $user = User::newFromId( 0 );
150        }
151
152        if ( !$this->getClient()->isUsableBy( $user ) ) {
153            throw OAuthServerException::accessDenied(
154                'Client ' . $this->getClient()->getIdentifier() .
155                ' is not usable by user with ID ' . $user->getId()
156            );
157        }
158    }
159
160}