Skip to content

Commit

Permalink
combined users into app. updated tests to include permissions check.
Browse files Browse the repository at this point in the history
  • Loading branch information
seanshahkarami committed Oct 3, 2022
1 parent 99db0fd commit 5ace9dd
Show file tree
Hide file tree
Showing 18 changed files with 202 additions and 234 deletions.
15 changes: 14 additions & 1 deletion app/admin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.contrib import admin
from .models import Node, Project, UserMembership, NodeMembership
from django.contrib.auth import admin as auth_admin
from .models import User, Node, Project, UserMembership, NodeMembership


class UserMembershipInline(admin.TabularInline):
Expand All @@ -14,6 +15,18 @@ class NodeMembershipInline(admin.TabularInline):
extra = 0


@admin.register(User)
class UserAdmin(auth_admin.UserAdmin):
fieldsets = (
(None, {"fields": ("username", "password")}),
("Personal info", {"fields": ("name", "email", "organization", "bio", "ssh_public_keys")}),
("Permissions", {"fields": ("is_active", "is_staff", "is_superuser", "groups", "user_permissions")}),
("Important dates", {"fields": ("last_login", "date_joined")}),
)
list_display = ("username", "name", "is_superuser")
search_fields = ("username", "name")


@admin.register(Node)
class NodeAdmin(admin.ModelAdmin):
list_display = ("vsn", "mac")
Expand Down
117 changes: 114 additions & 3 deletions app/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,131 @@
# Generated by Django 4.1 on 2022-10-03 15:38
# Generated by Django 4.1 on 2022-10-03 16:16

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("users", "__first__"),
("auth", "0012_alter_user_first_name_max_length"),
]

operations = [
migrations.CreateModel(
name="User",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
],
verbose_name="username",
),
),
(
"email",
models.EmailField(
blank=True, max_length=254, verbose_name="email address"
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
("name", models.CharField(blank=True, max_length=255)),
("organization", models.CharField(blank=True, max_length=255)),
("bio", models.TextField(blank=True)),
(
"ssh_public_keys",
models.TextField(blank=True, verbose_name="SSH public keys"),
),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.group",
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.permission",
verbose_name="user permissions",
),
),
],
options={
"verbose_name": "user",
"verbose_name_plural": "users",
"abstract": False,
},
managers=[
("objects", django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name="Node",
fields=[
Expand Down
20 changes: 16 additions & 4 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
from django.conf import settings
from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()
from django.contrib.auth.models import AbstractUser
from django.urls import reverse
from django.utils.translation import gettext_lazy as _


class User(AbstractUser):
# Use name instead of assuming cultural convention first and last name.
name = models.CharField(blank=True, max_length=255)
first_name = None
last_name = None
organization = models.CharField(blank=True, max_length=255)
bio = models.TextField(blank=True)
ssh_public_keys = models.TextField("SSH public keys", blank=True)

# def get_absolute_url(self):
# return reverse("app:detail", kwargs={"username": self.username})


class Node(models.Model):
Expand Down
File renamed without changes.
34 changes: 27 additions & 7 deletions app/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from django.test import TestCase
from django.contrib.auth import get_user_model
from rest_framework.authtoken.models import Token
from rest_framework import status
from .models import Project, Node, UserMembership, NodeMembership


Expand All @@ -9,6 +11,12 @@
class TestApp(TestCase):

def testListAccess(self):
user = User.objects.create(username="admin", is_staff=True)
admin_token = Token.objects.create(user=user)

user = User.objects.create(username="user")
user_token = Token.objects.create(user=user)

self.setUpMembershipData(
profile_membership=[
("ada", "sage", {"can_develop": True}),
Expand All @@ -30,33 +38,45 @@ def testListAccess(self):
],
)

# require auth for this endpoint
r = self.client.get("/profiles/ada/access")
self.assertEqual(r.status_code, 200)
self.assertEqual(r.status_code, status.HTTP_401_UNAUTHORIZED)

# require admin permissions for this endpoint
r = self.client.get("/profiles/ada/access", HTTP_AUTHORIZATION=f"Sage {user_token}")
self.assertEqual(r.status_code, status.HTTP_403_FORBIDDEN)

# check responses
r = self.client.get("/profiles/ada/access", HTTP_AUTHORIZATION=f"Sage {admin_token}")
self.assertEqual(r.status_code, status.HTTP_200_OK)
self.assertEqual(r.json(), [
{"vsn": "W001", "access": ["develop", "schedule"]},
{"vsn": "W002", "access": ["develop"]},
{"vsn": "W003", "access": ["develop"]},
])

r = self.client.get("/profiles/jed/access")
self.assertEqual(r.status_code, 200)
r = self.client.get("/profiles/jed/access", HTTP_AUTHORIZATION=f"Sage {admin_token}")
self.assertEqual(r.status_code, status.HTTP_200_OK)
self.assertEqual(r.json(), [
{"vsn": "W001", "access": ["schedule"]},
{"vsn": "W002", "access": ["develop"]},
{"vsn": "W003", "access": ["develop", "schedule"]},
])

r = self.client.get("/profiles/tom/access")
self.assertEqual(r.status_code, 200)
r = self.client.get("/profiles/tom/access", HTTP_AUTHORIZATION=f"Sage {admin_token}")
self.assertEqual(r.status_code, status.HTTP_200_OK)
self.assertEqual(r.json(), [
{"vsn": "W001", "access": ["access_files", "develop", "schedule"]},
{"vsn": "W002", "access": ["develop"]},
{"vsn": "W003", "access": ["develop", "schedule"]},
])

def testAccessNotExist(self):
r = self.client.get("/profiles/nothere/access")
self.assertEqual(r.status_code, 404)
user = User.objects.create(username="admin", is_staff=True)
token = Token.objects.create(user=user)

r = self.client.get("/profiles/nothere/access", HTTP_AUTHORIZATION=f"Sage {token}")
self.assertEqual(r.status_code, status.HTTP_404_NOT_FOUND)

def setUpMembershipData(self, profile_membership, node_membership):
for username, projectname, access in profile_membership:
Expand Down
5 changes: 4 additions & 1 deletion app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@
path("logout/", views.oidc_logout, name="oidc_logout"),
path("globus-auth-redirect/", views.oidc_callback, name="oidc_callback"),
] + format_suffix_patterns([
path("profiles/<str:username>/access", views.UserAccessView.as_view(), name="user_access"),
path("users/", views.UserListView.as_view(), name="user-list"),
path("users/~self", views.UserSelfDetailView.as_view(), name="user-detail-self"),
path("users/<str:username>", views.UserDetailView.as_view(), name="user-detail"),
path("profiles/<str:username>/access", views.UserAccessView.as_view(), name="user-access"),
])
37 changes: 26 additions & 11 deletions app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,40 @@
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.generics import ListAPIView, RetrieveAPIView
from rest_framework.permissions import IsAdminUser, IsAuthenticated
from secrets import compare_digest, token_urlsafe
import requests
from .serializers import UserSerializer

User = get_user_model()


class UserListView(ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAdminUser]


class UserDetailView(RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_field = "username"
permission_classes = [IsAdminUser]


class UserSelfDetailView(RetrieveAPIView):
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]

def get_object(self):
return self.request.user


class UserAccessView(APIView):

permission_classes = [IsAdminUser]

def get(self, request: HttpRequest, username: str, format=None) -> Response:
try:
user = User.objects.get(username=username)
Expand Down Expand Up @@ -75,17 +101,6 @@ def oidc_callback(request: HttpRequest) -> HttpResponse:
if username is None:
return JsonResponse({"error": "missing username from authorization server"}, status=status.HTTP_502_BAD_GATEWAY)

# user, _ = User.objects.update_or_create(
# username=username,
# email=userinfo.get("email", ""),
# )

# Profile.objects.update_or_create(
# user=user,
# name=userinfo.get("name", ""),
# organization=userinfo.get("organization", ""),
# )

user, _ = User.objects.update_or_create(
username=username,
name=userinfo.get("name", ""),
Expand Down
3 changes: 1 addition & 2 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@
"django.contrib.staticfiles",
"rest_framework",
"rest_framework.authtoken",
"users",
"app",
]

AUTH_USER_MODEL = "users.User"
AUTH_USER_MODEL = "app.User"

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
Expand Down
1 change: 0 additions & 1 deletion config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,5 @@
path("admin/", admin.site.urls),
path("api-auth/", include("rest_framework.urls")),
path("", include("app.urls")),
path("", include("users.urls")),
path("", TemplateView.as_view(template_name="index.html")),
]
Empty file removed users/__init__.py
Empty file.
17 changes: 0 additions & 17 deletions users/admin.py

This file was deleted.

6 changes: 0 additions & 6 deletions users/apps.py

This file was deleted.

Loading

0 comments on commit 5ace9dd

Please sign in to comment.