Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
54.39% covered (warning)
54.39%
31 / 57
40.00% covered (danger)
40.00%
4 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
AccessTokenRepository
54.39% covered (warning)
54.39%
31 / 57
40.00% covered (danger)
40.00%
4 / 10
44.43
0.00% covered (danger)
0.00%
0 / 1
 __construct
40.00% covered (danger)
40.00%
2 / 5
0.00% covered (danger)
0.00%
0 / 1
2.86
 getNewToken
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 persistNewAccessToken
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
 revokeAccessToken
14.29% covered (danger)
14.29%
1 / 7
0.00% covered (danger)
0.00%
0 / 1
4.52
 isAccessTokenRevoked
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 deleteForApprovalId
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getApprovalId
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 getDbDataFromTokenEntity
80.00% covered (warning)
80.00%
8 / 10
0.00% covered (danger)
0.00%
0 / 1
3.07
 getTableName
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIdentifierField
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\OAuth\Repository;
4
5use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
6use League\OAuth2\Server\Entities\ClientEntityInterface;
7use League\OAuth2\Server\Entities\ScopeEntityInterface;
8use League\OAuth2\Server\Exception\OAuthServerException;
9use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
10use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
11use MediaWiki\Extension\OAuth\Entity\AccessTokenEntity;
12use MediaWiki\Extension\OAuth\Entity\ClientEntity;
13use MediaWiki\MediaWikiServices;
14
15class AccessTokenRepository extends DatabaseRepository implements AccessTokenRepositoryInterface {
16    private const FIELD_EXPIRES = 'oaat_expires';
17    private const FIELD_ACCEPTANCE_ID = 'oaat_acceptance_id';
18    private const FIELD_REVOKED = 'oaat_revoked';
19
20    /** @var string */
21    private $issuer;
22
23    /**
24     * @param string|null $issuer
25     */
26    public function __construct(
27        string $issuer = null
28    ) {
29        if ( !$issuer ) {
30            // TODO: When the extension is converted to proper use of DI,
31            // this needs to be always injected.
32            $issuer = MediaWikiServices::getInstance()
33                ->getMainConfig()
34                ->get( 'CanonicalServer' );
35        }
36        $this->issuer = $issuer;
37    }
38
39    /**
40     * Create a new access token
41     *
42     * @param ClientEntityInterface|ClientEntity $clientEntity
43     * @param ScopeEntityInterface[] $scopes
44     * @param string|int|null $userIdentifier
45     * @return AccessTokenEntityInterface
46     * @throws OAuthServerException
47     */
48    public function getNewToken( ClientEntityInterface $clientEntity,
49        array $scopes, $userIdentifier = null ) {
50        return new AccessTokenEntity( $clientEntity, $scopes,
51            $this->issuer, $userIdentifier );
52    }
53
54    /**
55     * Persists a new access token to permanent storage.
56     *
57     * @param AccessTokenEntityInterface|AccessTokenEntity $accessTokenEntity
58     *
59     * @throws UniqueTokenIdentifierConstraintViolationException
60     */
61    public function persistNewAccessToken( AccessTokenEntityInterface $accessTokenEntity ) {
62        if ( $this->identifierExists( $accessTokenEntity->getIdentifier() ) ) {
63            throw UniqueTokenIdentifierConstraintViolationException::create();
64        }
65
66        $data = $this->getDbDataFromTokenEntity( $accessTokenEntity );
67
68        $this->getDB( DB_PRIMARY )->newInsertQueryBuilder()
69            ->insertInto( $this->getTableName() )
70            ->row( $data )
71            ->caller( __METHOD__ )
72            ->execute();
73    }
74
75    /**
76     * Revoke an access token.
77     *
78     * @param string $tokenId
79     */
80    public function revokeAccessToken( $tokenId ) {
81        if ( $this->identifierExists( $tokenId ) ) {
82            $this->getDB( DB_PRIMARY )->newUpdateQueryBuilder()
83                ->update( $this->getTableName() )
84                ->set( [ static::FIELD_REVOKED => 1 ] )
85                ->where( [ $this->getIdentifierField() => $tokenId ] )
86                ->caller( __METHOD__ )
87                ->execute();
88        }
89    }
90
91    /**
92     * Check if the access token has been revoked.
93     *
94     * @param string $tokenId
95     *
96     * @return bool Return true if this token has been revoked
97     */
98    public function isAccessTokenRevoked( $tokenId ) {
99        $row = $this->getDB()->selectRow(
100            $this->getTableName(),
101            [ static::FIELD_REVOKED ],
102            [ $this->getIdentifierField() => $tokenId ],
103            __METHOD__
104        );
105        if ( !$row ) {
106            return true;
107        }
108        return (bool)$row->{static::FIELD_REVOKED};
109    }
110
111    /**
112     * Delete all access tokens issued with provided approval
113     *
114     * @param int $approvalId
115     */
116    public function deleteForApprovalId( $approvalId ) {
117        $this->getDB( DB_PRIMARY )->newDeleteQueryBuilder()
118            ->deleteFrom( $this->getTableName() )
119            ->where( [ static::FIELD_ACCEPTANCE_ID => $approvalId ] )
120            ->caller( __METHOD__ )
121            ->execute();
122    }
123
124    /**
125     * Get ID of the approval bound to this AT
126     *
127     * @param string $tokenId
128     * @return bool|int
129     */
130    public function getApprovalId( $tokenId ) {
131        $row = $this->getDB()->selectRow(
132            $this->getTableName(),
133            [ static::FIELD_ACCEPTANCE_ID ],
134            [ $this->getIdentifierField() => $tokenId ],
135            __METHOD__
136        );
137
138        if ( $row ) {
139            return (int)$row->{static::FIELD_ACCEPTANCE_ID};
140        }
141
142        return false;
143    }
144
145    private function getDbDataFromTokenEntity( AccessTokenEntity $accessTokenEntity ) {
146        $expiry = $accessTokenEntity->getExpiryDateTime()->getTimestamp();
147        if ( $expiry > 9223371197536780800 ) {
148            $expiry = 'infinity';
149        }
150        return [
151            $this->getIdentifierField() => $accessTokenEntity->getIdentifier(),
152            static::FIELD_EXPIRES => $this->getDB()->encodeExpiry( $expiry ),
153            static::FIELD_ACCEPTANCE_ID => $accessTokenEntity->getApproval() ?
154                $accessTokenEntity->getApproval()->getId() :
155                0
156        ];
157    }
158
159    protected function getTableName(): string {
160        return 'oauth2_access_tokens';
161    }
162
163    protected function getIdentifierField(): string {
164        return 'oaat_identifier';
165    }
166}