1<?php
2
3namespace OAuth\OAuth2\Service;
4
5use OAuth\Common\Consumer\CredentialsInterface;
6use OAuth\Common\Http\Client\ClientInterface;
7use OAuth\Common\Http\Exception\TokenResponseException;
8use OAuth\Common\Http\Uri\Uri;
9use OAuth\Common\Http\Uri\UriInterface;
10use OAuth\Common\Storage\TokenStorageInterface;
11use OAuth\OAuth2\Token\StdOAuth2Token;
12
13class GitHub extends AbstractService
14{
15    /**
16     * Defined scopes, see http://developer.github.com/v3/oauth/ for definitions.
17     */
18
19    /**
20     * Public read-only access (includes public user profile info, public repo info, and gists).
21     */
22    const SCOPE_READONLY = '';
23
24    /**
25     * Read/write access to profile info only.
26     *
27     * Includes SCOPE_USER_EMAIL and SCOPE_USER_FOLLOW.
28     */
29    const SCOPE_USER = 'user';
30
31    /**
32     * Read access to a user’s email addresses.
33     */
34    const SCOPE_USER_EMAIL = 'user:email';
35
36    /**
37     * Access to follow or unfollow other users.
38     */
39    const SCOPE_USER_FOLLOW = 'user:follow';
40
41    /**
42     * Read/write access to public repos and organizations.
43     */
44    const SCOPE_PUBLIC_REPO = 'public_repo';
45
46    /**
47     * Read/write access to public and private repos and organizations.
48     *
49     * Includes SCOPE_REPO_STATUS.
50     */
51    const SCOPE_REPO = 'repo';
52
53    /**
54     * Grants access to deployment statuses for public and private repositories.
55     * This scope is only necessary to grant other users or services access to deployment statuses,
56     * without granting access to the code.
57     */
58    const SCOPE_REPO_DEPLOYMENT = 'repo_deployment';
59
60    /**
61     * Read/write access to public and private repository commit statuses. This scope is only necessary to grant other
62     * users or services access to private repository commit statuses without granting access to the code. The repo and
63     * public_repo scopes already include access to commit status for private and public repositories, respectively.
64     */
65    const SCOPE_REPO_STATUS = 'repo:status';
66
67    /**
68     * Delete access to adminable repositories.
69     */
70    const SCOPE_DELETE_REPO = 'delete_repo';
71
72    /**
73     * Read access to a user’s notifications. repo is accepted too.
74     */
75    const SCOPE_NOTIFICATIONS = 'notifications';
76
77    /**
78     * Write access to gists.
79     */
80    const SCOPE_GIST = 'gist';
81
82    /**
83     * Grants read and ping access to hooks in public or private repositories.
84     */
85    const SCOPE_HOOKS_READ = 'read:repo_hook';
86
87    /**
88     * Grants read, write, and ping access to hooks in public or private repositories.
89     */
90    const SCOPE_HOOKS_WRITE = 'write:repo_hook';
91
92    /**
93     * Grants read, write, ping, and delete access to hooks in public or private repositories.
94     */
95    const SCOPE_HOOKS_ADMIN = 'admin:repo_hook';
96
97    /**
98     * Read-only access to organization, teams, and membership.
99     */
100    const SCOPE_ORG_READ = 'read:org';
101
102    /**
103     * Publicize and unpublicize organization membership.
104     */
105    const SCOPE_ORG_WRITE = 'write:org';
106
107    /**
108     * Fully manage organization, teams, and memberships.
109     */
110    const SCOPE_ORG_ADMIN = 'admin:org';
111
112    /**
113     * List and view details for public keys.
114     */
115    const SCOPE_PUBLIC_KEY_READ = 'read:public_key';
116
117    /**
118     * Create, list, and view details for public keys.
119     */
120    const SCOPE_PUBLIC_KEY_WRITE = 'write:public_key';
121
122    /**
123     * Fully manage public keys.
124     */
125    const SCOPE_PUBLIC_KEY_ADMIN = 'admin:public_key';
126
127    public function __construct(
128        CredentialsInterface $credentials,
129        ClientInterface $httpClient,
130        TokenStorageInterface $storage,
131        $scopes = [],
132        ?UriInterface $baseApiUri = null
133    ) {
134        parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri);
135
136        if (null === $baseApiUri) {
137            $this->baseApiUri = new Uri('https://api.github.com/');
138        }
139    }
140
141    /**
142     * {@inheritdoc}
143     */
144    public function getAuthorizationEndpoint()
145    {
146        return new Uri('https://github.com/login/oauth/authorize');
147    }
148
149    /**
150     * {@inheritdoc}
151     */
152    public function getAccessTokenEndpoint()
153    {
154        return new Uri('https://github.com/login/oauth/access_token');
155    }
156
157    /**
158     * {@inheritdoc}
159     */
160    protected function getAuthorizationMethod()
161    {
162        return static::AUTHORIZATION_METHOD_HEADER_TOKEN;
163    }
164
165    /**
166     * {@inheritdoc}
167     */
168    protected function parseAccessTokenResponse($responseBody)
169    {
170        $data = json_decode($responseBody, true);
171
172        if (null === $data || !is_array($data)) {
173            throw new TokenResponseException('Unable to parse response.');
174        } elseif (isset($data['error'])) {
175            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
176        }
177
178        $token = new StdOAuth2Token();
179        $token->setAccessToken($data['access_token']);
180        // Github tokens evidently never expire...
181        $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES);
182        unset($data['access_token']);
183
184        $token->setExtraParams($data);
185
186        return $token;
187    }
188
189    /**
190     * Used to configure response type -- we want JSON from github, default is query string format.
191     *
192     * @return array
193     */
194    protected function getExtraOAuthHeaders()
195    {
196        return ['Accept' => 'application/json'];
197    }
198
199    /**
200     * Required for GitHub API calls.
201     *
202     * @return array
203     */
204    protected function getExtraApiHeaders()
205    {
206        return ['Accept' => 'application/vnd.github.v3+json'];
207    }
208
209    /**
210     * {@inheritdoc}
211     */
212    protected function getScopesDelimiter()
213    {
214        return ',';
215    }
216}
217