models.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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.user_id'
  23. abid_rand_src = 'self.id'
  24. id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
  25. uuid = models.UUIDField(blank=True, null=True, editable=True, 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. "uuid": str(self.id),
  42. "abid": str(self.calculate_abid()),
  43. "user_id": str(self.user.id),
  44. "user_username": self.user.username,
  45. "token": self.token,
  46. "created": self.created.isoformat(),
  47. "expires": self.expires_as_iso8601,
  48. }
  49. @property
  50. def expires_as_iso8601(self):
  51. """Returns the expiry date of the token in ISO 8601 format or a date 100 years in the future if none."""
  52. expiry_date = self.expires or (timezone.now() + timedelta(days=365 * 100))
  53. return expiry_date.isoformat()
  54. def is_valid(self, for_date=None):
  55. for_date = for_date or timezone.now()
  56. if self.expires and self.expires < for_date:
  57. return False
  58. return True
  59. # monkey patch django-signals-webhooks to change how it shows up in Admin UI
  60. class OutboundWebhook(ABIDModel, WebhookBase):
  61. """
  62. Model used in place of (extending) signals_webhooks.models.WebhookModel. Swapped using:
  63. settings.SIGNAL_WEBHOOKS_CUSTOM_MODEL = 'api.models.OutboundWebhook'
  64. """
  65. abid_prefix = 'whk_'
  66. abid_ts_src = 'self.created'
  67. abid_uri_src = 'self.endpoint'
  68. abid_subtype_src = 'self.ref'
  69. abid_rand_src = 'self.id'
  70. id = models.UUIDField(blank=True, null=True, unique=True, editable=True)
  71. uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=True)
  72. abid = ABIDField(prefix=abid_prefix)
  73. WebhookBase._meta.get_field('name').help_text = (
  74. 'Give your webhook a descriptive name (e.g. Notify ACME Slack channel of any new ArchiveResults).')
  75. WebhookBase._meta.get_field('signal').help_text = (
  76. 'The type of event the webhook should fire for (e.g. Create, Update, Delete).')
  77. WebhookBase._meta.get_field('ref').help_text = (
  78. 'Dot import notation of the model the webhook should fire for (e.g. core.models.Snapshot or core.models.ArchiveResult).')
  79. WebhookBase._meta.get_field('endpoint').help_text = (
  80. 'External URL to POST the webhook notification to (e.g. https://someapp.example.com/webhook/some-webhook-receiver).')
  81. class Meta(WebhookBase.Meta):
  82. verbose_name = 'API Outbound Webhook'