models.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. __package__ = 'archivebox.api'
  2. import uuid
  3. import secrets
  4. from datetime import timedelta
  5. from django.conf import settings
  6. from django.db import models
  7. from django.utils import timezone
  8. from signal_webhooks.models import WebhookBase
  9. from django_stubs_ext.db.models import TypedModelMeta
  10. from abid_utils.models import ABIDModel, ABIDField
  11. def generate_secret_token() -> str:
  12. # returns cryptographically secure string with len() == 32
  13. return secrets.token_hex(16)
  14. class APIToken(ABIDModel):
  15. """
  16. A secret key generated by a User that's used to authenticate REST API requests to ArchiveBox.
  17. """
  18. # ABID: apt_<created_ts>_<token_hash>_<user_id_hash>_<uuid_rand>
  19. abid_prefix = 'apt_'
  20. abid_ts_src = 'self.created'
  21. abid_uri_src = 'self.token'
  22. abid_subtype_src = 'self.created_by_id'
  23. abid_rand_src = 'self.id'
  24. id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
  25. uuid = models.UUIDField(blank=True, null=True, editable=False, unique=True)
  26. abid = ABIDField(prefix=abid_prefix)
  27. created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
  28. token = models.CharField(max_length=32, default=generate_secret_token, unique=True)
  29. created = models.DateTimeField(auto_now_add=True)
  30. expires = models.DateTimeField(null=True, blank=True)
  31. class Meta(TypedModelMeta):
  32. verbose_name = "API Key"
  33. verbose_name_plural = "API Keys"
  34. def __str__(self) -> str:
  35. return self.token
  36. def __repr__(self) -> str:
  37. return f'<APIToken user={self.user.username} token=************{self.token[-4:]}>'
  38. def __json__(self) -> dict:
  39. return {
  40. "TYPE": "APIToken",
  41. "id": str(self.pk),
  42. "abid": str(self.ABID),
  43. "created_by_id": str(self.created_by_id),
  44. "token": self.token,
  45. "created": self.created.isoformat(),
  46. "expires": self.expires_as_iso8601,
  47. }
  48. @property
  49. def ulid(self):
  50. return self.get_abid().ulid
  51. @property
  52. def expires_as_iso8601(self):
  53. """Returns the expiry date of the token in ISO 8601 format or a date 100 years in the future if none."""
  54. expiry_date = self.expires or (timezone.now() + timedelta(days=365 * 100))
  55. return expiry_date.isoformat()
  56. def is_valid(self, for_date=None):
  57. for_date = for_date or timezone.now()
  58. if self.expires and self.expires < for_date:
  59. return False
  60. return True
  61. # monkey patch django-signals-webhooks to change how it shows up in Admin UI
  62. class OutboundWebhook(ABIDModel, WebhookBase):
  63. """
  64. Model used in place of (extending) signals_webhooks.models.WebhookModel. Swapped using:
  65. settings.SIGNAL_WEBHOOKS_CUSTOM_MODEL = 'api.models.OutboundWebhook'
  66. """
  67. abid_prefix = 'whk_'
  68. abid_ts_src = 'self.created'
  69. abid_uri_src = 'self.endpoint'
  70. abid_subtype_src = 'self.ref'
  71. abid_rand_src = 'self.id'
  72. id = models.UUIDField(blank=True, null=True, unique=True, editable=True)
  73. uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
  74. abid = ABIDField(prefix=abid_prefix)
  75. WebhookBase._meta.get_field('name').help_text = (
  76. 'Give your webhook a descriptive name (e.g. Notify ACME Slack channel of any new ArchiveResults).')
  77. WebhookBase._meta.get_field('signal').help_text = (
  78. 'The type of event the webhook should fire for (e.g. Create, Update, Delete).')
  79. WebhookBase._meta.get_field('ref').help_text = (
  80. 'Dot import notation of the model the webhook should fire for (e.g. core.models.Snapshot or core.models.ArchiveResult).')
  81. WebhookBase._meta.get_field('endpoint').help_text = (
  82. 'External URL to POST the webhook notification to (e.g. https://someapp.example.com/webhook/some-webhook-receiver).')
  83. class Meta(WebhookBase.Meta):
  84. verbose_name = 'API Outbound Webhook'