Skip to content

Commit df54758

Browse files
committed
Design doc: use GitHub Application for more granular permissions
Design document to communicate how to support GitHub Application for new users while keeping OAuth Application for old users. The main benefit is that GitHub Application allows us more granular permissions than OAuth Application. With GitHub Application most of the permission we request are Read-only and that's a win for everybody!
1 parent 01e0d77 commit df54758

File tree

1 file changed

+306
-0
lines changed

1 file changed

+306
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
==================
2+
GitHub Application
3+
==================
4+
5+
GitHub offers two different ways to access users' data from its own platform:
6+
OAuth Application and GitHub Application. Depending on the applications
7+
needings, GitHub recommends using one or the other.
8+
9+
Read the Docs currently uses the OAuth Application to support "Login with
10+
GitHub" and "Import Project". They require different level of permissions, but
11+
OAuth Application does not really allow us to manage them in a granular way.
12+
13+
Based in our needings and the GitHub recommendations in
14+
https://docs.github.com/en/developers/apps/getting-started-with-apps/about-apps#determining-which-integration-to-build
15+
we should use a GitHub Application for Read the Docs::
16+
17+
Only as me? -> NO
18+
Act as an App -> NO
19+
Access Everything? -> NO
20+
-> GitHub Application
21+
22+
Or in case we need to act as an App, we end in the same route::
23+
24+
Only as me? -> NO
25+
Act as an App -> YES
26+
-> GitHub Application
27+
28+
This document explains some problems we have found in our current implementation
29+
using OAuth Application and explore the recommended way using GitHub
30+
Application. It goes through the work required and touches a little about its
31+
implementation.
32+
33+
34+
Problems with current implementation
35+
------------------------------------
36+
37+
* We request too permisives scope at Sign Up
38+
39+
Even if our application will never require some of these scopes (because it's
40+
a read user --and will never import a project), we ask for too many permissions.
41+
42+
* OAuth Application does not provide granular permissions
43+
44+
We have to ask for ``read`` scope. It gives us read & write permissions to
45+
*all the repositories of this user*. This makes some users/organizations to
46+
don't want to user our platform.
47+
48+
Besides, ``admin:repo_hook`` scope is also required to be able to create a
49+
webhook for pushes and trigger a new build.
50+
51+
* Repositories from an GitHub Organization does not appear in the "Import
52+
Project" list
53+
54+
Organizations with OAuth App access restriction enabled, require users using
55+
our OAuth Application to request permissions to the GitHub Organization's
56+
owner to accept our OAuth App.
57+
58+
59+
Goals
60+
-----
61+
62+
* Reduce as much as possible granted permissions for our GitHub Application
63+
* Do not require access to *all the users'/organizations' repositories*
64+
* Allow Read the Docs GitHub Application only to specific repositories
65+
* Support GitHub Application *and* OAuth Application at the same time during the
66+
migration time
67+
* Support all the GitHub features current using OAuth Application
68+
* GitHub SSO
69+
* Pull Request builder
70+
* Trigger a build on push
71+
* Don't require SSH key to clone private repositories
72+
73+
74+
Non Goals
75+
---------
76+
77+
* Escalate permissions as they are needed
78+
* Migrate all current OAuth Application users to GitHub Application users at
79+
once
80+
* Implement something similar to GitHub Application for other VCS providers
81+
(GitLab and Bitbucket)
82+
83+
84+
Context related to GitHub Application
85+
-------------------------------------
86+
87+
Considering there are too much confusion around GitHub Application and OAuth
88+
Application, and copy&paste some context from its official documentation trying
89+
to mitigate it and have a better understanding of this design doc.
90+
91+
GitHub Apps are first-class actors within GitHub. A GitHub App acts on its own
92+
behalf, taking actions via the API directly using its own identity, which
93+
means you don't need to maintain a bot or service account as a separate user.
94+
95+
GitHub Apps can be installed directly on organizations and user accounts and
96+
granted access to specific repositories. They come with built-in webhooks and
97+
narrow, specific permissions. When you set up your GitHub App, you can select
98+
the repositories you want it to access
99+
100+
To install a GitHub App, you must be an organization owner or have admin
101+
permissions in a repository.
102+
103+
Don't build an OAuth App if you want your application to act on a single
104+
repository. With the repo OAuth scope, OAuth Apps can act on all of the
105+
authenticated user's repositories.
106+
107+
(from https://docs.github.com/en/developers/apps/getting-started-with-apps/about-apps)
108+
109+
You can install GitHub Apps in your personal account or organizations you own.
110+
If you have admin permissions in a repository, you can install GitHub Apps on
111+
organization accounts. If a GitHub App is installed in a repository and requires
112+
organization permissions, the organization owner must approve the application.
113+
114+
Account owners can use a GitHub App in one account without granting access to
115+
another ... A GitHub App remains installed if the person who set it up leaves
116+
the organization.
117+
118+
The installation token from a GitHub App loses access to resources if an admin
119+
removes repositories from the installation.
120+
121+
Installation access tokens are limited to specified repositories with the
122+
permissions chosen by the creator of the app.
123+
124+
GitHub Apps can request separate access to issues and pull requests without
125+
accessing the actual contents of the repository.
126+
127+
A GitHub App receives a webhook event when an installation is changed or
128+
removed. This tells the app creator when they've received more or less access to
129+
an organization's resources.
130+
131+
A GitHub App can request an installation access token by using a private key
132+
with a JSON web token format out-of-band.
133+
134+
An installation token identifies the app as the GitHub Apps bot, such as
135+
@jenkins-bot.
136+
137+
GitHub Apps can authenticate on behalf of the user, which is called
138+
user-to-server requests. The flow to authorize is the same as the OAuth App
139+
authorization flow. User-to-server tokens can expire and be renewed with a
140+
refresh token
141+
142+
(from https://docs.github.com/en/developers/apps/getting-started-with-apps/differences-between-github-apps-and-oauth-apps)
143+
144+
145+
GitHub Application requirements
146+
-------------------------------
147+
148+
:Repository permissions:
149+
- Metadata: Read-only (mandatory)
150+
- Contents: Read-only
151+
- Commit statuses: Read & Write
152+
- Pull requests: Read-only
153+
154+
:Organization permissions:
155+
- Members: Read-only
156+
157+
:User permissions:
158+
- Email addresses: Read-only
159+
160+
:Events subscribed:
161+
- Meta (When this App is deleted and the associated hook is removed)
162+
- Create (Branch or tag created)
163+
- Delete (Branch or tag deleted)
164+
- Push (Git push to a repository)
165+
- Pull request (Pull request opened, closed, reopened, ...)
166+
167+
168+
Authentication as GitHub Application
169+
------------------------------------
170+
171+
https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps
172+
173+
Generate the JWT on Python:
174+
175+
.. code:: python
176+
177+
import datetime
178+
import jwt
179+
GITHUB_APP_ID = 134302
180+
181+
# content of PEM file downloaded from GH Application settings
182+
private_key = b'''-----BEGIN RSA PRIVATE KEY-----'''
183+
184+
jwt.encode({"iat": datetime.datetime.utcnow() - datetime.timedelta(seconds=60), "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=5), "iss": GITHUB_APP_ID}, private_key, algorithm="RS256")
185+
186+
Use the token generated in the previous step as authorization in the cURL command:
187+
188+
.. code:: bash
189+
190+
$ curl -H "Authorization: Bearer <JWT>" -H "Accept: application/vnd.github.v3+json" https://api.github.com/app
191+
192+
193+
.. note::
194+
195+
For some reason this is not working and I'm getting:
196+
197+
.. code:: text
198+
199+
'Issued at' claim ('iat') must be an Integer representing the time that the assertion was issued
200+
201+
202+
SSH keys are not required to clone private repositories
203+
-------------------------------------------------------
204+
205+
When asking for ``contents`` (repository permission) we are able to clone
206+
private repositories by using GitHub Application installation access tokens:
207+
208+
.. code:: bash
209+
210+
git clone https://x-access-token:<token>@github.com/owner/repo.git
211+
212+
(from
213+
https://docs.github.com/en/developers/apps/building-github-apps/authenticating-with-github-apps#http-based-git-access-by-an-installation)
214+
215+
216+
Handling webhooks
217+
-----------------
218+
219+
GitHub Application has *only one webhook* where it will receive all the events
220+
for all the installations. The body contains all the information about the
221+
particular installation that triggered the event. With this data, we will create
222+
an access token and perform the query/actions we need.
223+
224+
There are some events that need to map to particular ``Project`` in our
225+
database. For example, "a push to ``main`` branch in repository
226+
``readthedocs/blog``" should "trigger a build for ``latest`` version on
227+
``read-the-docs-blog`` project". For these cases we can use use
228+
``repository.id`` field from the body to get the ``RemoteRepository.remote_id``
229+
and from there get the ``Project``.
230+
231+
232+
Remote* models re-sync
233+
----------------------
234+
235+
Currently, we are using 2 endpoints to sync all the ``Remote*`` models:
236+
237+
* ``https://api.github.com/user/repos`` (https://docs.github.com/en/rest/reference/repos#list-repositories-for-the-authenticated-user)
238+
* ``https://api.github.com/org/{org}/repos`` (https://docs.github.com/en/rest/reference/repos#list-organization-repositories)
239+
240+
However, this endpoints won't return the same data when using GitHub Application
241+
since we won't be authenticated as a user anymore and the ``permission.admin:
242+
boolean`` field won't come in the response.
243+
244+
Instead, we will have to iterate over,
245+
246+
* all repositories accessible to the app installation
247+
248+
* ``https://api.github.com/installation/repositories`` (https://docs.github.com/en/rest/reference/apps#list-repositories-accessible-to-the-app-installation)
249+
* iterate over each repository checking for user's permission
250+
251+
* ``https://api.github.com/repos/{owner}/{repo}/collaborators/{username}/permission`` (https://docs.github.com/en/rest/reference/repos#get-repository-permissions-for-a-user)
252+
253+
By doing this, we will keep our ``Remote*`` table very small because we will
254+
only track repositories that users gave us permissions. Then, only these
255+
repositories will be shown in the "Import Project" page.
256+
257+
Once they created a new repository under their organization, they will need to
258+
go to the GitHub Application installation configuration and grant access to the
259+
new repository. This will send us a webhook (``installation_repositories``,
260+
https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#installation_repositories)
261+
and we can automatically do a re-sync of ``Remote*`` models.
262+
263+
.. note::
264+
265+
Users installing our GitHub Application and selecting "All repositories" will
266+
always see all the repositories on the "Import Project" page.
267+
268+
269+
Need more research
270+
------------------
271+
272+
* Does django-allauth support GitHub Application for Login?
273+
274+
Using the Client ID for the GitHub Application (instead the OAuth
275+
application), should make it to work:
276+
https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#web-application-flow
277+
278+
* How do we use Client ID for GitHub Application for new users and Client ID for
279+
OAuth application for current/existing users?
280+
281+
* Should we keep using our OAuth Application for login and GitHub Application
282+
for the rest? Is it possible?
283+
284+
Related: https://docs.github.com/en/developers/apps/getting-started-with-apps/migrating-oauth-apps-to-github-apps
285+
Related: https://github.com/readthedocs/readthedocs-ops/issues/532
286+
287+
288+
Questions
289+
---------
290+
291+
* How are we going to handle other VCS providers (GitLab and Bitbucket)?
292+
293+
GitLab and Bitbucket does not offer another option than OAuth Application. We
294+
need to maintain the implementation that we currently have for them.
295+
296+
* Does it worth the effort integrating GitHub Application without being able to
297+
use the same for other services?
298+
299+
GitHub is the most VCS provider used in our platform. Because of this, if the
300+
we can provide a better UX to most of our users I'd call it a win.
301+
302+
* Do we need to support both, GitHub Application and GitHub OAuth, at the same time?
303+
304+
Yes. Current users will keep using GitHub OAuth for a long period of time. We
305+
could notify them encouraging them to migrate to our new GitHub Application.
306+
However, this will take a non-trivial amount of time.

0 commit comments

Comments
 (0)