Skip to content

Commit

Permalink
Add token authentication for django views (#762)
Browse files Browse the repository at this point in the history
* add custom backend with token auth

* fix return

* add as a separate backend instead of subclassing the default

* api endpoint logs user in

* add to client test

* lint

* add backend field to HAWCUser model

* change filename and subclass

* add anon check to test

* fix backend issue without a migration

* make backend change as attr of user model

* only query db if a token value is provided

* no need to reauthenticate

* lint

Co-authored-by: Andy Shapiro <[email protected]>
  • Loading branch information
munnsmunns and shapiromatron authored Jan 4, 2023
1 parent f0c4317 commit ff70df3
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 0 deletions.
28 changes: 28 additions & 0 deletions hawc/apps/common/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.contrib.auth.backends import ModelBackend
from rest_framework.authentication import get_authorization_header
from rest_framework.authtoken.models import Token


class TokenBackend(ModelBackend):
keyword = "Token"

def get_token_from_header(self, request) -> str:
auth = get_authorization_header(request).split()
token = ""
if auth and auth[0].lower() == self.keyword.lower().encode() and len(auth) == 2:
try:
token = auth[1].decode()
except UnicodeError:
pass
return token

def authenticate(self, request, token=None, **kwargs):
if request is not None and token is None:
token = self.get_token_from_header(request)
if not token:
return None
try:
token_obj = Token.objects.select_related("user").get(key=token)
except Token.DoesNotExist:
return
return token_obj.user if self.user_can_authenticate(token_obj.user) else None
3 changes: 3 additions & 0 deletions hawc/apps/myuser/api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.contrib.auth import login
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
Expand Down Expand Up @@ -25,4 +26,6 @@ class HawcValidateAuthToken(APIView):
permission_classes = [IsAuthenticated]

def get(self, request, format=None):
# create a django session for django view
login(request, request.user)
return Response({"valid": True})
1 change: 1 addition & 0 deletions hawc/apps/myuser/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class HAWCUser(AbstractBaseUser, PermissionsMixin):

USERNAME_FIELD = "email"
CAN_CREATE_ASSESSMENTS = "can-create-assessments"
backend = "django.contrib.auth.backends.ModelBackend"

class Meta:
ordering = ("last_name",)
Expand Down
4 changes: 4 additions & 0 deletions hawc/main/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@
# Session and authentication
AUTH_USER_MODEL = "myuser.HAWCUser"
AUTH_PROVIDERS = {AuthProvider(p) for p in os.getenv("HAWC_AUTH_PROVIDERS", "django").split("|")}
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"hawc.apps.common.auth.TokenBackend",
]
PASSWORD_RESET_TIMEOUT = 259200 # 3 days, in seconds
SESSION_COOKIE_AGE = int(os.getenv("HAWC_SESSION_DURATION", "604800")) # 1 week
SESSION_COOKIE_DOMAIN = os.getenv("HAWC_SESSION_COOKIE_DOMAIN", None)
Expand Down
8 changes: 8 additions & 0 deletions tests/client/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,19 @@ def test_set_authentication_token(self):
client.set_authentication_token("123")
assert err.value.status_code == 403

with pytest.raises(HawcClientException) as err:
client.session.get(f"{self.live_server_url}/assessment/1/")
assert err.value.status_code == 403

user = HAWCUser.objects.get(email="[email protected]")
token = user.get_api_token().key
resp = client.set_authentication_token(token)
assert resp == {"valid": True}

# can also access regular Django views that are private
resp = client.session.get(f"{self.live_server_url}/assessment/1/")
assert resp.status_code == 200

####################
# BaseClient tests #
####################
Expand Down

0 comments on commit ff70df3

Please sign in to comment.