forked from readthedocs/readthedocs.org
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use project-scoped temporal tokens to interact with the API from the …
…builders (readthedocs#10378) This implements the design document from https://dev.readthedocs.io/en/latest/design/secure-api-access-from-builders.html - The api.v2 package was converted into a real django app, so we can add models to it. - A custom API key model was created to hold the relationship of the key with a project - A `/api/v2/revoke/` endpoint was added to revoke an API key after it has been used. - The old super-user permission based still works, this is to avoid breaking the builds while we do the deploy, that code can be removed in the next deploy. - All endpoints use the project attached to the API key to filter the resources - API keys expire after 3 hours Closes readthedocs/meta#21
- Loading branch information
Showing
23 changed files
with
651 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from django.contrib import admin | ||
from rest_framework_api_key.admin import APIKeyModelAdmin | ||
|
||
from readthedocs.api.v2.models import BuildAPIKey | ||
|
||
|
||
@admin.register(BuildAPIKey) | ||
class BuildAPIKeyAdmin(APIKeyModelAdmin): | ||
raw_id_fields = ["project"] | ||
search_fields = [*APIKeyModelAdmin.search_fields, "project__slug"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from django.apps import AppConfig | ||
|
||
|
||
class APIV2Config(AppConfig): | ||
name = "readthedocs.api.v2" | ||
verbose_name = "API V2" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Generated by Django 3.2.18 on 2023-05-31 20:40 | ||
|
||
import django.db.models.deletion | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
initial = True | ||
|
||
dependencies = [ | ||
("projects", "0100_project_readthedocs_yaml_path"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="BuildAPIKey", | ||
fields=[ | ||
( | ||
"id", | ||
models.CharField( | ||
editable=False, | ||
max_length=150, | ||
primary_key=True, | ||
serialize=False, | ||
unique=True, | ||
), | ||
), | ||
("prefix", models.CharField(editable=False, max_length=8, unique=True)), | ||
("hashed_key", models.CharField(editable=False, max_length=150)), | ||
("created", models.DateTimeField(auto_now_add=True, db_index=True)), | ||
( | ||
"name", | ||
models.CharField( | ||
default=None, | ||
help_text="A free-form name for the API key. Need not be unique. 50 characters max.", | ||
max_length=50, | ||
), | ||
), | ||
( | ||
"revoked", | ||
models.BooleanField( | ||
blank=True, | ||
default=False, | ||
help_text="If the API key is revoked, clients cannot use it anymore. (This cannot be undone.)", | ||
), | ||
), | ||
( | ||
"expiry_date", | ||
models.DateTimeField( | ||
blank=True, | ||
help_text="Once API key expires, clients cannot use it anymore.", | ||
null=True, | ||
verbose_name="Expires", | ||
), | ||
), | ||
( | ||
"project", | ||
models.ForeignKey( | ||
help_text="Project that this API key grants access to", | ||
on_delete=django.db.models.deletion.CASCADE, | ||
related_name="build_api_keys", | ||
to="projects.project", | ||
), | ||
), | ||
], | ||
options={ | ||
"verbose_name": "Build API key", | ||
"verbose_name_plural": "Build API keys", | ||
"ordering": ("-created",), | ||
"abstract": False, | ||
}, | ||
), | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
from datetime import timedelta | ||
|
||
from django.db import models | ||
from django.utils import timezone | ||
from django.utils.translation import gettext_lazy as _ | ||
from rest_framework_api_key.models import AbstractAPIKey, BaseAPIKeyManager | ||
|
||
from readthedocs.projects.models import Project | ||
|
||
|
||
class BuildAPIKeyManager(BaseAPIKeyManager): | ||
def create_key(self, project): | ||
""" | ||
Create a new API key for a project. | ||
Build API keys are valid for 3 hours, | ||
and can be revoked at any time by hitting the /api/v2/revoke/ endpoint. | ||
""" | ||
expiry_date = timezone.now() + timedelta(hours=3) | ||
return super().create_key( | ||
# Name is required, so we use the project slug for it. | ||
name=project.slug, | ||
expiry_date=expiry_date, | ||
project=project, | ||
) | ||
|
||
|
||
class BuildAPIKey(AbstractAPIKey): | ||
|
||
""" | ||
API key for securely interacting with the API from the builders. | ||
The key is attached to a single project, | ||
it can be used to have write access to the API V2. | ||
""" | ||
|
||
project = models.ForeignKey( | ||
Project, | ||
on_delete=models.CASCADE, | ||
related_name="build_api_keys", | ||
help_text=_("Project that this API key grants access to"), | ||
) | ||
|
||
objects = BuildAPIKeyManager() | ||
|
||
class Meta(AbstractAPIKey.Meta): | ||
verbose_name = _("Build API key") | ||
verbose_name_plural = _("Build API keys") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.