Commit 9835afe5 authored by David Haynes's avatar David Haynes 🙆

Add /auth/token API endpoint

- Returns the current user's API token if there is a authenticated
session already established
parent d907af3c
Pipeline #3031 failed with stage
in 35 seconds
......@@ -7,6 +7,7 @@ name = "pypi"
pylint = "*"
pylint-django = "*"
coverage = "*"
black = "*"
[packages]
django = "<2.1,>=2.0"
......
......@@ -15,8 +15,6 @@ from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
# Other Imports
from hashids import Hashids
......@@ -27,62 +25,39 @@ from rest_framework.authtoken.models import Token
# Note: the Hashids library already implements several restrictions oncharacter
# placement, including repeating or incrementing numbers, or placing curse word
# characters adjacent to one another.
SIMILAR_CHARS = set(['b', 'G', '6', 'g', 'q', 'l',
'1', 'I', 'S', '5', 'O', '0'])
SIMILAR_CHARS = set(["b", "G", "6", "g", "q", "l", "1", "I", "S", "5", "O", "0"])
ALPHANUMERICS = set(string.ascii_letters + string.digits)
LINK_CHARS = ''.join(ALPHANUMERICS - SIMILAR_CHARS)
LINK_CHARS = "".join(ALPHANUMERICS - SIMILAR_CHARS)
HASHIDS = Hashids(salt="srct.gmu.edu", alphabet=(LINK_CHARS))
HASHIDS = Hashids(
salt="srct.gmu.edu", alphabet=(LINK_CHARS)
)
class RegisteredUser(models.Model):
"""
Wrapper model for the built in User model which stores data pertaining to
the registration / approval / blocked status of a django user.
"""
user = models.OneToOneField(
User,
on_delete="cascade",
verbose_name="Django User Object"
User, on_delete="cascade", verbose_name="Django User Object"
)
full_name = models.CharField(
"Full Name",
max_length=100,
default="",
)
full_name = models.CharField("Full Name", max_length=100, default="")
organization = models.CharField(
"Organization",
max_length=100,
default="",
)
organization = models.CharField("Organization", max_length=100, default="")
description = models.TextField(
"Signup Description",
blank=True,
default="",
)
description = models.TextField("Signup Description", blank=True, default="")
registered = models.BooleanField(
"Registration Status",
default=False,
)
registered = models.BooleanField("Registration Status", default=False)
approved = models.BooleanField(
"Approval Status",
default=False,
)
approved = models.BooleanField("Approval Status", default=False)
blocked = models.BooleanField(
"Blocked Status",
default=False,
)
blocked = models.BooleanField("Blocked Status", default=False)
def __str__(self):
return f"<RegisteredUser: {self.user} - Approval Status: {self.approved}>"
@receiver(post_save, sender=User)
def handle_reguser_creation(sender, instance, created, **kwargs):
"""
......@@ -92,38 +67,29 @@ def handle_reguser_creation(sender, instance, created, **kwargs):
if created:
RegisteredUser.objects.create(user=instance, full_name=instance.get_full_name())
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
token = Token.objects.create(user=instance)
print(token.key)
Token.objects.create(user=instance)
class URL(models.Model):
"""
The representation of a stored URL redirection rule. Each URL has
attributes that are used for analytic purposes.
"""
owner = models.ForeignKey(
RegisteredUser,
on_delete="cascade",
verbose_name="RegisteredUser Owner"
RegisteredUser, on_delete="cascade", verbose_name="RegisteredUser Owner"
)
date_created = models.DateTimeField(
"Go Link Creation Date",
default=timezone.now,
)
date_created = models.DateTimeField("Go Link Creation Date", default=timezone.now)
date_expires = models.DateTimeField(
"Go Link Expiry Date",
blank=True,
null=True,
)
date_expires = models.DateTimeField("Go Link Expiry Date", blank=True, null=True)
destination = models.URLField(
"Go Link Destination URL",
max_length=1000,
default="https://go.gmu.edu",
"Go Link Destination URL", max_length=1000, default="https://go.gmu.edu"
)
# Note: min_length cannot exist on a model so it is enforced in forms.py
......@@ -143,7 +109,7 @@ class URL(models.Model):
return f"<Owner: {self.owner.user} - Destination URL: {self.destination}>"
class Meta:
ordering = ['short']
ordering = ["short"]
@staticmethod
def generate_valid_short():
......
......@@ -8,9 +8,12 @@ from .models import URL, RegisteredUser
# Third Party Imports
from rest_framework import serializers
from rest_framework.authtoken.models import Token
class URLSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = URL
lookup_field = 'short'
fields = ('destination', 'short', 'date_expires')
lookup_field = "short"
fields = ("destination", "short", "date_expires")
......@@ -17,7 +17,7 @@ from cas import views as cas_views
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'golinks', views.URLViewSet, base_name="golinks")
router.register(r"golinks", views.URLViewSet, base_name="golinks")
# This function attempts to import an admin module in each installed
# application. Such modules are expected to register models with the admin.
......@@ -26,25 +26,21 @@ admin.autodiscover()
urlpatterns = [
# Root API URL
path("api/", include(router.urls)),
# Authentication URLs
path('auth/login/', cas_views.login, name='cas_login'),
path('auth/logout/', cas_views.logout, name='cas_logout'),
path("auth/login/", cas_views.login, name="cas_login"),
path("auth/logout/", cas_views.logout, name="cas_logout"),
# /admin - Administrator interface.
path('admin/', admin.site.urls, name='go_admin'),
path('auth/', include('rest_framework.urls'))
# # /view/<short> - View URL data. Cached for 15 minutes
# re_path(r'^view/(?P<short>([\U00010000-\U0010ffff][\U0000200D]?)+)$',
# cache_page(60 * 15)(go.views.view), name='view'),
# re_path(r'^view/(?P<short>[-\w]+)$',
# cache_page(60 * 15)(go.views.view), name='view'),
# # Redirection regex.
# re_path(r'^(?P<short>([\U00010000-\U0010ffff][\U0000200D]?)+)$',
# go.views.redirection, name='redirection'),
# re_path(r'^(?P<short>[-\w]+)$',
# go.views.redirection, name='redirection'),
path("admin/", admin.site.urls, name="go_admin"),
path("auth/", include("rest_framework.urls")),
path("auth/token/", views.CustomAuthToken.as_view())
# # /view/<short> - View URL data. Cached for 15 minutes
# re_path(r'^view/(?P<short>([\U00010000-\U0010ffff][\U0000200D]?)+)$',
# cache_page(60 * 15)(go.views.view), name='view'),
# re_path(r'^view/(?P<short>[-\w]+)$',
# cache_page(60 * 15)(go.views.view), name='view'),
# # Redirection regex.
# re_path(r'^(?P<short>([\U00010000-\U0010ffff][\U0000200D]?)+)$',
# go.views.redirection, name='redirection'),
# re_path(r'^(?P<short>[-\w]+)$',
# go.views.redirection, name='redirection'),
]
......@@ -4,55 +4,58 @@ go/views.py
The functions that handle a request to a given URL. Get some data, manipulate
it, and return a rendered template.
"""
# Python stdlib imports
from datetime import timedelta
# Django Imports
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import login_required, user_passes_test
from django.core.exceptions import PermissionDenied # ValidationError
from django.core.mail import EmailMessage, send_mail
from django.http import HttpResponseServerError # Http404
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
# Other imports
from ratelimit.decorators import ratelimit
# App Imports
from .forms import EditForm, SignupForm, URLForm
from .models import URL, RegisteredUser
from django.contrib.auth.models import User, Group
from rest_framework import viewsets
from rest_framework import permissions
from rest_framework.authentication import TokenAuthentication
from rest_framework.authentication import TokenAuthentication, SessionAuthentication
from .serializers import URLSerializer
from .models import URL
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
from rest_framework.permissions import IsAuthenticated
class URLPermission(permissions.BasePermission):
"""Custom permission check on URL model operations."""
message = "You do not have the necessary approvals to perform that action."
def has_permission(self, request, view):
return request.user.registereduser.approved or request.user.is_staff
def has_object_permission(self, request, view, obj):
return obj.owner == request.user.registereduser or request.user.is_staff
class URLViewSet(viewsets.ModelViewSet):
"""
API endpoint that handles creation/read/update/deletion of URL objects.
"""
authentication_classes = (TokenAuthentication, )
authentication_classes = (TokenAuthentication,)
serializer_class = URLSerializer
permission_classes = (URLPermission,)
lookup_field = 'short'
permission_classes = (URLPermission, IsAuthenticated)
lookup_field = "short"
def get_queryset(self):
if not self.request.user.is_staff:
return URL.objects.filter(owner=self.request.user.registereduser)
else:
return URL.objects.all()
return URL.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user.registereduser)
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
class CustomAuthToken(ObtainAuthToken):
authentication_classes = (SessionAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request, *args, **kwargs):
token, created = Token.objects.get_or_create(user=request.user)
return Response({"token": token.key})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment