2
0

auth.py 3.7 KB

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