2
0

auth.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. __package__ = 'archivebox.api'
  2. from typing import Optional, cast
  3. from django.http import HttpRequest
  4. from django.contrib.auth import login
  5. from django.contrib.auth import authenticate
  6. from django.contrib.auth.models import AbstractBaseUser
  7. from ninja.security import HttpBearer, APIKeyQuery, APIKeyHeader, HttpBasicAuth, django_auth_superuser
  8. def auth_using_token(token, request: Optional[HttpRequest]=None) -> Optional[AbstractBaseUser]:
  9. """Given an API token string, check if a corresponding non-expired APIToken exists, and return its user"""
  10. from api.models import APIToken # lazy import model to avoid loading it at urls.py import time
  11. user = None
  12. submitted_empty_form = token in ('string', '', None)
  13. if submitted_empty_form:
  14. assert request is not None, 'No request provided for API key authentication'
  15. user = request.user # see if user is authed via django session and use that as the default
  16. else:
  17. try:
  18. token = APIToken.objects.get(token=token)
  19. if token.is_valid():
  20. user = token.created_by
  21. except APIToken.DoesNotExist:
  22. pass
  23. if not user:
  24. print('[❌] Failed to authenticate API user using API Key:', request)
  25. return None
  26. def auth_using_password(username, password, request: Optional[HttpRequest]=None) -> Optional[AbstractBaseUser]:
  27. """Given a username and password, check if they are valid and return the corresponding user"""
  28. user = None
  29. submitted_empty_form = (username, password) in (('string', 'string'), ('', ''), (None, None))
  30. if submitted_empty_form:
  31. assert request is not None, 'No request provided for API key authentication'
  32. user = request.user # see if user is authed via django session and use that as the default
  33. else:
  34. user = authenticate(
  35. username=username,
  36. password=password,
  37. )
  38. if not user:
  39. print('[❌] Failed to authenticate API user using API Key:', request)
  40. user = None
  41. return cast(AbstractBaseUser | None, user)
  42. ### Base Auth Types
  43. class APITokenAuthCheck:
  44. """The base class for authentication methods that use an api.models.APIToken"""
  45. def authenticate(self, request: HttpRequest, key: Optional[str]=None) -> Optional[AbstractBaseUser]:
  46. user = auth_using_token(
  47. token=key,
  48. request=request,
  49. )
  50. if user is not None:
  51. login(request, user, backend='django.contrib.auth.backends.ModelBackend')
  52. return user
  53. class UserPassAuthCheck:
  54. """The base class for authentication methods that use a username & password"""
  55. def authenticate(self, request: HttpRequest, username: Optional[str]=None, password: Optional[str]=None) -> Optional[AbstractBaseUser]:
  56. user = auth_using_password(
  57. username=username,
  58. password=password,
  59. request=request,
  60. )
  61. if user is not None:
  62. login(request, user, backend='django.contrib.auth.backends.ModelBackend')
  63. return user
  64. ### Django-Ninja-Provided Auth Methods
  65. class HeaderTokenAuth(APITokenAuthCheck, APIKeyHeader):
  66. """Allow authenticating by passing X-API-Key=xyz as a request header"""
  67. param_name = "X-ArchiveBox-API-Key"
  68. class BearerTokenAuth(APITokenAuthCheck, HttpBearer):
  69. """Allow authenticating by passing Bearer=xyz as a request header"""
  70. pass
  71. class QueryParamTokenAuth(APITokenAuthCheck, APIKeyQuery):
  72. """Allow authenticating by passing api_key=xyz as a GET/POST query parameter"""
  73. param_name = "api_key"
  74. class UsernameAndPasswordAuth(UserPassAuthCheck, HttpBasicAuth):
  75. """Allow authenticating by passing username & password via HTTP Basic Authentication (not recommended)"""
  76. pass
  77. ### Enabled Auth Methods
  78. API_AUTH_METHODS = [
  79. HeaderTokenAuth(),
  80. BearerTokenAuth(),
  81. QueryParamTokenAuth(),
  82. django_auth_superuser,
  83. UsernameAndPasswordAuth(),
  84. ]