Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
ResourceServer
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 15
702
0.00% covered (danger)
0.00%
0 / 1
 factory
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 isOAuth2Request
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 verify
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getUser
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getClient
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getScopes
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getAccessTokenId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 isScopeAllowed
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 setVerifiedInfo
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 setUser
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 setClient
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 setScopes
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 setAccessTokenId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 assertVerified
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace MediaWiki\Extension\OAuth;
4
5use League\OAuth2\Server\Entities\ScopeEntityInterface;
6use League\OAuth2\Server\Middleware\ResourceServerMiddleware;
7use League\OAuth2\Server\ResourceServer as LeagueResourceServer;
8use MediaWiki\Extension\OAuth\Backend\Consumer;
9use MediaWiki\Extension\OAuth\Backend\MWOAuthException;
10use MediaWiki\Extension\OAuth\Backend\Utils;
11use MediaWiki\Extension\OAuth\Entity\ClientEntity;
12use MediaWiki\Extension\OAuth\Entity\ScopeEntity;
13use MediaWiki\Extension\OAuth\Repository\AccessTokenRepository;
14use MediaWiki\Extension\OAuth\Repository\ScopeRepository;
15use MediaWiki\MediaWikiServices;
16use MediaWiki\Request\WebRequest;
17use MediaWiki\Rest\HttpException;
18use MediaWiki\User\User;
19use MWException;
20use Psr\Http\Message\ResponseInterface;
21use Psr\Http\Message\ServerRequestInterface;
22
23class ResourceServer {
24    /** @var ResourceServerMiddleware */
25    protected $middleware;
26    /** @var User */
27    protected $user;
28    /** @var ClientEntity */
29    protected $client;
30    /** @var ScopeEntity[] */
31    protected $scopes;
32    /** @var string */
33    protected $accessTokenId;
34    /** @var bool */
35    protected $verified = false;
36
37    public static function factory() {
38        $config = MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'mwoauth' );
39        return new static( $config->get( 'OAuth2PublicKey' ), $config->get( 'CanonicalServer' ) );
40    }
41
42    /**
43     * @param string $publicKey
44     * @param string $canonicalServer
45     */
46    protected function __construct( string $publicKey, string $canonicalServer ) {
47        $accessTokenRepository = new AccessTokenRepository( $canonicalServer );
48
49        $server = new LeagueResourceServer(
50            $accessTokenRepository,
51            $publicKey
52        );
53        $this->middleware = new ResourceServerMiddleware( $server );
54    }
55
56    /**
57     * Check if the request is an OAuth2 request
58     *
59     * @param WebRequest|ServerRequestInterface $request
60     * @return bool
61     */
62    public static function isOAuth2Request( $request ) {
63        $authHeader = $request->getHeader( 'authorization' );
64
65        // Normalize to array
66        if ( is_string( $authHeader ) ) {
67            $authHeader = [ $authHeader ];
68        }
69        if ( $authHeader && strpos( $authHeader[0], 'Bearer' ) === 0 ) {
70            return true;
71        }
72        return false;
73    }
74
75    /**
76     * @param ServerRequestInterface $request
77     * @param ResponseInterface $response
78     * @param callable $callback
79     * @return ResponseInterface
80     */
81    public function verify( $request, $response, $callback ) {
82        $this->verified = false;
83
84        return $this->middleware->__invoke(
85            $request,
86            $response,
87            function ( $request, $response ) use ( $callback ) {
88                $this->setVerifiedInfo( $request );
89                return $callback( $request, $response );
90            }
91        );
92    }
93
94    /**
95     * @return User
96     * @throws MWOAuthException
97     */
98    public function getUser() {
99        $this->assertVerified();
100        return $this->user;
101    }
102
103    /**
104     * @return ClientEntity
105     * @throws MWOAuthException
106     */
107    public function getClient() {
108        $this->assertVerified();
109        return $this->client;
110    }
111
112    /**
113     * @return ScopeEntity[]
114     * @throws MWOAuthException
115     */
116    public function getScopes() {
117        $this->assertVerified();
118        return $this->scopes;
119    }
120
121    /**
122     * Get access token this request was made with
123     *
124     * @return string
125     * @throws MWOAuthException
126     */
127    public function getAccessTokenId() {
128        $this->assertVerified();
129        return $this->accessTokenId;
130    }
131
132    /**
133     * Check if the scope is allowed
134     *
135     * @param string|ScopeEntityInterface $scope
136     * @return bool
137     * @throws MWOAuthException
138     */
139    public function isScopeAllowed( $scope ) {
140        $this->assertVerified();
141
142        if ( $scope instanceof ScopeEntityInterface ) {
143            $scope = $scope->getIdentifier();
144        }
145
146        return isset( $this->scopes[$scope] );
147    }
148
149    /**
150     * Read out the verified request and get relevant information
151     *
152     * @param ServerRequestInterface $request
153     * @throws HttpException
154     */
155    public function setVerifiedInfo( ServerRequestInterface $request ) {
156        $this->setUser( $request );
157        $this->setClient( $request );
158        $this->setScopes( $request );
159        $this->setAccessTokenId( $request );
160
161        $this->verified = true;
162    }
163
164    /**
165     * Set authorized user to the global context
166     *
167     * @param ServerRequestInterface $request
168     * @throws HttpException
169     */
170    private function setUser( ServerRequestInterface $request ) {
171        $userId = $request->getAttribute( 'oauth_user_id', 0 );
172        if ( !$userId ) {
173            // Set anon user when no user id is present in the AT (machine grant)
174            $this->user = User::newFromId( 0 );
175            return;
176        }
177
178        try {
179            $user = Utils::getLocalUserFromCentralId( $userId );
180        } catch ( MWException $ex ) {
181            throw new HttpException( $ex->getMessage(), 403 );
182        }
183
184        $this->user = $user;
185    }
186
187    /**
188     * Set the ClientEntity from validated request
189     *
190     * @param ServerRequestInterface $request
191     * @throws HttpException
192     */
193    private function setClient( ServerRequestInterface $request ) {
194        $this->client = ClientEntity::newFromKey(
195            Utils::getCentralDB( DB_REPLICA ),
196            $request->getAttribute( 'oauth_client_id' )
197        );
198        if ( !$this->client || $this->client->getOAuthVersion() !== Consumer::OAUTH_VERSION_2 ) {
199            throw new HttpException( 'Client represented by given access token is invalid', 403 );
200        }
201    }
202
203    /**
204     * Set validated scopes
205     *
206     * @param ServerRequestInterface $request
207     */
208    private function setScopes( ServerRequestInterface $request ) {
209        $scopeNames = $request->getAttribute( 'oauth_scopes', [] );
210        $scopeRepo = new ScopeRepository();
211        foreach ( $scopeNames as $scopeName ) {
212            $scope = $scopeRepo->getScopeEntityByIdentifier( $scopeName );
213            if ( !$scope ) {
214                continue;
215            }
216            $this->scopes[$scope->getIdentifier()] = $scope;
217        }
218    }
219
220    /**
221     * Set the access token this request was made with
222     *
223     * @param ServerRequestInterface $request
224     */
225    private function setAccessTokenId( ServerRequestInterface $request ) {
226        $this->accessTokenId = $request->getAttribute( 'oauth_access_token_id' );
227    }
228
229    private function assertVerified() {
230        if ( !$this->verified ) {
231            throw new MWOAuthException( 'mwoauth-oauth2-error-request-not-verified', [
232                'consumer' => $this->getClient()->getConsumerKey(),
233                'consumer_name' => $this->getClient()->getName(),
234            ] );
235        }
236    }
237}