Remote DRF Auth and handle payload with JWT - django

I have auth_service, tpm_api and frontend enviroments. All services use the same secret_key.
My users and permissions are on the auth_service.
I am using jwt_simple for Authentication on auth_service.
On the frontend service, I get token from auth_service with username and password.
I am sending requests to endpoints in my tpm_api service with this token.
I'm parsing the response and displaying it in my frontend service.
So far, no problem.
However, I am not getting the token.payload data within the tpm_api service.
I added
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework_simplejwt.authentication.JWTAuthentication",
),
}
in settings.py.
When I send request to tpm_api service, under self.request.authenticators there is <rest_framework.authentication.BasicAuthentication object 0x000001668986C430> and <rest_framework.authentication.SessionAuthentication object at 0x000001668986C6A0>.
I need <rest_framework_simplejwt.authentication.JWTAuthentication object at 0x000001FFDCD23790>.
I don't have user model anywhere except auth_service.
##### auth_service model.py #####
from django.db import models
from django.contrib.auth.models import AbstractUser
perm_parent_choices = [
("app", "app"),
("factory", "factory"),
("department", "department"),
("role", "role"),
]
class User(AbstractUser):
perms = models.ManyToManyField("login.Perm", related_name="user_perms", blank=True)
gsm = models.CharField(max_length=15, null=True)
class Perm(models.Model):
parent = models.CharField(max_length=50, choices=perm_parent_choices, null=True)
name = models.CharField(max_length=50)
short_code = models.CharField(max_length=5)
def __str__(self):
return self.short_code
##### auth_service views.py #####
class UserViewSet(viewsets.ModelViewSet):
serializer_class = serializers.UserSerializer
queryset = models.User.objects.all()
def list(self, request, *args, **kwargs):
##############################
from rest_framework_simplejwt.authentication import JWTAuthentication
JWT_authenticator = JWTAuthentication()
response = JWT_authenticator.authenticate(request)
if response is not None:
# unpacking
user , token = response
request.session["perms"] = token.payload["perms"]
print(request.session["perms"])
# print("this is decoded token claims", token.payload)
else:
print("no token is provided in the header or the header is missing")
##############################
return super().list(request, *args, **kwargs)
##### tpm_api_service views.py #####
class MachineGroupViewSet(viewsets.ModelViewSet):
queryset = models.MachineGroup.objects.all()
serializer_class = serializers.MachineGroupSerializer
# authentication_classes = []
def get_queryset(self):
r = self.request.authenticators
print(r)
from rest_framework_simplejwt.authentication import JWTAuthentication
JWT_authenticator = JWTAuthentication()
response = JWT_authenticator.authenticate(self.request)
return super().get_queryset()
Here I am not seeing the simple_jwt object.

Related

How Django rest IsAdminUser does not pass even the user is admin?

class Admin(models.Model):
username = models.CharField(primary_key=True, max_length=30)
password = models.CharField(max_length=255)
email = models.EmailField(unique=True)
created_on = models.DateTimeField(auto_now=True)
django_user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='admin')
class AdminAPIViewSet(viewsets.ModelViewSet):
queryset = Admin.objects.all()
serializer_class = AdminSerializer
permission_classes = [permissions.IsAdminUser]
def get_queryset(self):
if self.request.user.is_authenticated:
return Admin.objects.filter(username=self.request.user.admin.username)
else:
return []
def create(self, request, *args, **kwargs):
serializer = AdminSerializer(data=request.data)
if serializer.is_valid():
email = serializer.data['email']
username = serializer.data['email']
password = serializer.data['password']
with transaction.atomic():
django_user = User.objects.create_user(username, email, password)
admin = Admin.objects.create(**serializer.data, django_user=django_user)
#User.objects.filter(pk=1001).update(is_superuser=True, is_staff=True)
return Response(admin.pk)
return Response('/error')
class ClientFullAccessAPIViewSet(viewsets.ModelViewSet):
queryset = Client.objects.all()
serializer_class = ClientSerializer
permission_classes = [permissions.IsAdminUser]
def create(self, request, *args, **kwargs):
serializer = ClientSerializer(data=request.data)
if serializer.is_valid():
email = serializer.data['email']
username = serializer.data['email']
password = serializer.data['password']
with transaction.atomic():
django_user = User.objects.create_user(username, email, password)
client = Client.objects.create(**serializer.data, django_user=django_user)
return Response(client.username)
return Response('/error')
`Here am trying to make the admin see the all the clients and the client see his data only ,... but I couldn't find why the i cant see the all the list clients as an admin, I am keep getting not authorized to access this endpoint..
`
urls.py
from django.contrib import admin
from django.urls import path, include
from rest_framework import routers
import user_management.views
router = routers.DefaultRouter()
router.register(r'clients', user_management.views.ClientReadOnlyAPIViewSet)
router.register(r'clientslist', user_management.views.ClientFullAccessAPIViewSet)
router.register(r'admin', user_management.views.AdminAPIViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
path('api/v1/', include(router.urls)),
#path('clients/', user_management.views.ClientAPIViewSet.as_view(), name="clients"),
]
`Here am trying to make the admin see the all the clients and the client see his data only ,... but I couldn't find why the i cant see the all the list clients as an admin, I am keep getting not authorized to access this endpoint..any help please?
The problem is that you defined your Admin model from a regular Django model so Django cannot find the user permissions associated to it.
You should inherit from a Django authentication user model (AbstractUser or AbstractBaseUser) as indicated in the documentation.
For instance, you can do:
from django.contrib.auth.models import AbstractUser
class CustomAdminUser(AbstractUser):
# Here you normally only want to
# define the fields not defined in
# the base model AbstractUser
pass
Then, to create your admin:
CustomAdminUser.objects.create_superuser(...)
Last but not least, two important things (mentioned in the above Django documentation's link):
Don’t forget to point the AUTH_USER_MODEL to your custom user model. Do this before creating any migrations or running manage.py migrate for the first time.
Register the model in the app’s admin.py.

Pytest-django - testing creation and passing a required User object

Apologies if this has already been answered elsewhere. I cannot find an answer which I can retrofit into my situation.
I'm new to django so I feel the problem is me not getting a fundamental grasp of a presumably basic concept here...
Using DRF and pytest-django, i'm trying to be diligent and write tests along the way before it becomes too time consuming to manually test. I can see it snowballing pretty quickly.
The issue I face is when I try to test the creation of a Catalogue, I can't get it to pass an User instance to the mandatory field 'created_by'. The logic works fine when I test manually, but writing the test itself is causing me headaches.
Many thanks in advance!
The error is:
TypeError: Cannot encode None for key 'created_by' as POST data. Did you mean to pass an empty string or omit the value?
Code provided.
# core/models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
email = models.EmailField(unique=True)
# workshop/models.py
from django.conf import settings
from django.db import models
class Catalogue(models.Model):
STATE_DRAFT = 'DRAFT'
STATE_PUBLISHED_PRIVATE = 'PUBPRIV'
STATE_PUBLISHED_PUBLIC = 'PUBPUB'
STATE_CHOICES = [
(STATE_DRAFT, 'Draft'),
(STATE_PUBLISHED_PRIVATE, 'Published (Private)'),
(STATE_PUBLISHED_PUBLIC, 'Published (Public)')
]
company = models.ForeignKey(Company, on_delete=models.PROTECT)
title = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
state = models.CharField(
max_length=10, choices=STATE_CHOICES, default=STATE_DRAFT)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
created_on = models.DateTimeField(auto_now_add=True)
def __str__(self) -> str:
return self.title
class CatalogueItem(models.Model):
TYPE_GOODS = 'GOODS'
TYPE_SERVICES = 'SERVICES'
TYPE_GOODS_AND_SERVICES = 'GOODS_AND_SERVICES'
TYPE_CHOICES = [
(TYPE_GOODS, 'Goods'),
(TYPE_SERVICES, 'Services'),
(TYPE_GOODS_AND_SERVICES, 'Goods & Services')
]
catalogue = models.ForeignKey(
Catalogue, on_delete=models.CASCADE, related_name='catalogueitems')
type = models.CharField(
max_length=50, choices=TYPE_CHOICES, default=TYPE_GOODS)
name = models.CharField(max_length=255)
description = models.TextField()
unit_price = models.DecimalField(max_digits=9, decimal_places=2)
can_be_discounted = models.BooleanField(default=True)
def __str__(self) -> str:
return self.name
#property
def item_type(self):
return self.get_type_display()
# workshop/serializers.py
class CatalogueSerializer(serializers.ModelSerializer):
catalogueitems = SimpleCatalogueItemSerializer(
many=True, read_only=True)
created_on = serializers.DateTimeField(read_only=True)
created_by = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
# depth = 1
model = Catalogue
fields = ['id', 'title', 'description',
'state', 'catalogueitems', 'created_by', 'created_on']
def create(self, validated_data):
company_id = self.context['company_id']
user = self.context['user']
return Catalogue.objects.create(company_id=company_id, created_by=user, **validated_data)
# workshop/views.py
class CatalogueViewSet(ModelViewSet):
serializer_class = CatalogueSerializer
def get_permissions(self):
if self.request.method in ['PATCH', 'PUT', 'DELETE', 'POST']:
return [IsAdminUser()]
return [IsAuthenticated()]
def get_queryset(self):
user = self.request.user
if user.is_staff:
return Catalogue.objects.prefetch_related('catalogueitems__catalogue').filter(company_id=self.kwargs['company_pk'])
elif user.is_authenticated:
return Catalogue.objects.filter(company_id=self.kwargs['company_pk'], state='PUBPUB')
def get_serializer_context(self):
company_id = self.kwargs['company_pk']
return {'company_id': company_id, 'user': self.request.user}
# workshop/tests/conftest.py
from core.models import User
from rest_framework.test import APIClient
import pytest
#pytest.fixture
def api_client():
return APIClient()
#pytest.fixture
def authenticate(api_client):
def do_authenticate(is_staff=False):
return api_client.force_authenticate(user=User(is_staff=is_staff))
return do_authenticate
# workshop/tests/test_catalogues.py
from core.models import User
from workshop.models import Catalogue
from rest_framework import status
import pytest
#pytest.fixture
def create_catalogue(api_client):
def do_create_catalogue(catalogue):
return api_client.post('/companies/1/catalogues/', catalogue)
return do_create_catalogue
class TestCreateCatalogue:
def test_if_admin_can_create_catalogue_returns_201(self, authenticate, create_catalogue):
user = authenticate(is_staff=True)
response = create_catalogue(
{'title': 'a', 'description': 'a', 'state': 'DRAFT','created_by':user})
assert response.status_code == status.HTTP_201_CREATED
I think you may have a problem with the user that you are using to do the test,
when you call authenticate it returns a client which is not the same as a user.
then you run the authenticate and log in as a generic user. Try making another fixture that creates a user first, authenticate with that user to return the client and then post that user you created to create_catalogue
from django.conf import settings
#pytest.fixture
def create_user() -> User:
return settings.AUTH_USER_MODEL.objects.create(
username="Test User", password="Test Password", email="testuser#example.com"
)
#pytest.fixture
def authenticate(api_client):
def do_authenticate(create_user):
return api_client.force_authenticate(create_user)
return do_authenticate
class TestCreateCatalogue:
def test_if_admin_can_create_catalogue_returns_201(self, authenticate, create_user create_catalogue):
user = authenticate(create_user)
response = create_catalogue(
{'title': 'a', 'description': 'a', 'state': 'DRAFT','created_by':create_user})
assert response.status_code == status.HTTP_201_CREATED

Django Admin Inline Form Fails in Test but Works in Admin Interface

So I am at a loss as to why the following example fails in a test case, but works perfectly fine in the admin interface.
I have a very basic User and Login models. The Login simply records user logins into the system via Django signal.
class User(AbstractUser):
company = models.CharField(
max_length=100, blank=True, null=True
)
class Login(models.Model):
"""Represents a record of a user login event."""
user = models.ForeignKey(
get_user_model(), on_delete=models.CASCADE, related_name="logins"
)
ip = models.GenericIPAddressField()
user_agent = models.TextField()
date = models.DateTimeField(auto_now_add=True, db_index=True)
domain = models.CharField(max_length=255)
http_host = models.CharField(null=True, max_length=255)
remote_host = models.CharField(null=True, max_length=255)
server_name = models.CharField(null=True, max_length=255)
In the admin.py I define a UserAdmin with inlines = [LoginInline]. Basically when viewing the user I can see the login history.
from django.contrib import admin
from django.contrib.auth import admin as auth_admin
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.utils.html import format_html
from users.models import Login
User = get_user_model()
class ReadOnlyModelMixin:
def has_add_permission(self, request, obj=None):
return False
def has_change_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
class LoginAdmin(ReadOnlyModelMixin, admin.ModelAdmin):
list_display = (
'id',
'user_link_with_name',
'user_email',
'user_company',
'domain',
'date',
'ip',
'remote_host',
'http_host',
'server_name',
'user_agent',
)
#admin.display(description='Name', ordering='user__first_name')
def user_link_with_name(self, obj):
url = reverse("admin:users_user_change", args=[obj.user.id])
return format_html(f'{obj.user}')
#admin.display(description='Email', ordering='user__email')
def user_email(self, obj):
return format_html(
f'{obj.user.email}'
)
#admin.display(description='Company', ordering="user__company")
def user_company(self, obj):
return obj.user.company
class LoginInline(ReadOnlyModelMixin, admin.TabularInline):
model = Login
class UserAdmin(auth_admin.UserAdmin):
inlines = [LoginInline]
admin.site.register(User, UserAdmin)
admin.site.register(Login, LoginAdmin)
It works perfectly fine in the Admin interface, I can view and ADD users no problem.
I then introduced test as follows:
class TestUserAdmin:
def test_add(self, admin_client):
url = reverse("admin:users_user_add")
response = admin_client.get(url)
assert response.status_code == 200
response = admin_client.post(
url,
data={
"username": "test",
"password1": "My_R#ndom-P#ssw0rd",
"password2": "My_R#ndom-P#ssw0rd",
},
)
# below FAILS when UserAdmin includes inlines = [LoginInline]
assert User.objects.filter(username="test").exists()
assert response.status_code == 302
the line assert User.objects.filter(username="test").exists() will FAIL as long as I have inlines = [LoginInline] included in the UserAdmin. If I comment out the inline then the test passes.
I really want to understand what I am missing, and why this test is failing, given that it works perfectly fine in the Admin Interface.
A repo that reproduces this problem: https://github.com/oizik/django_admin_inline_test_problem
Thank you.

Django Rest Framework, request POST, update if exists, create if not exists from mass data of POST request

I am building an API for users info data
I want to make that when the POST request, execute function "create", "update"
if from POST request user exists:
update (full_name, function, department, logon, system, lic_type )
if from POST request user doesn't exist:
create (user, full_name, function, department, logon, system, lic_type )
models.py
from django.db import models
class Users(models.Model):
user = models.CharField(max_length=50,blank=True, null=True)
full_name = models.CharField(max_length=200, blank=True, null=True)
function = models.CharField(max_length=300,blank=True, null=True)
department = models.CharField(max_length=300,blank=True, null=True)
logon = models.DateTimeField(blank=True, null=True)
system = models.CharField(max_length=300,blank=True, null=True)
lic_type = models.CharField(max_length=300,blank=True, null=True)
serizlizers.py
from rest_framework import serializers
from .models import Users
class UsersSerializer(serializers.ModelSerializer):
logon = serializers.DateTimeField(input_formats=settings.DATE_INPUT_FORMATS)
class Meta:
model = Users
# fields = '__all__'
fields = ['user', 'full_name', 'function', 'department', 'logon', 'system', 'lic_type']
views.py
from django.http.response import JsonResponse
from rest_framework.parsers import JSONParser
from rest_framework import status
from .models import Users
from .serializers import UsersSerializer
from rest_framework.decorators import api_view, authentication_classes
from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt
from rest_framework.authentication import BasicAuthentication
#csrf_exempt
#api_view(['GET', 'POST'])
#authentication_classes([BasicAuthentication])
def users_list(request):
if request.method == 'GET':
users = Users.objects.all()
user = request.GET.get('user', None)
if user is not None:
users = users.filter(user__icontains=user)
users_serializer = UsersSerializer(users, many=True)
return JsonResponse(users_serializer.data, safe=False)
elif request.method == 'POST':
users_data = JSONParser().parse(request)
users_serializer = UsersSerializer(data=users_data, many=True)
if users_serializer.is_valid():
users_serializer.save()
return Response(users_serializer.data, status=status.HTTP_201_CREATED)
return Response(users_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('users/', views.users_list),
]
I could do like this, when POST request delete all from database and create data from POST request
elif request.method == 'POST':
users = Users.objects.all()
users.delete()
users_data = JSONParser().parse(request)
users_serializer = UsersSerializer(data=users_data, many=True)
if users_serializer.is_valid():
users_serializer.save()
return Response(users_serializer.data, status=status.HTTP_201_CREATED)
return Response(users_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
but instead I want to make update_or_create()
I tried like so, but this only creates empty row in database
user_name = request.POST.get('user')
user, created = Users.objects.update_or_create(user = user_name)
user.full_name = request.POST.get('full_name')
user.function = request.POST.get('function')
user.department = request.POST.get('department')
user.logon = request.POST.get('logon')
user.system = request.POST.get('system')
user.lic_type = request.POST.get('lic_type')
user.save()
return Response(user, status=status.HTTP_201_CREATED)
thanks for any help
The best way is when a POST is there, you first retrieve the item from the database (using django's utility get_object_or_404) and have restframework update all fields you set as non-readonly in the serializer.
Example:
# on top import
from django.shortcuts import get_object_or_404
....
elif request.method == 'POST':
# add here the query you determine is user already exists, usually a unique ID or UUID
user_object = get_object_or_404(Users, id=request.data.get('id'))
users_data = JSONParser().parse(request)
users_serializer = UsersSerializer(user_object, data=users_data, many=True)
if users_serializer.is_valid():
users_serializer.save()
return Response(users_serializer.data, status=status.HTTP_201_CREATED)
return Response(users_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Something like this ought to work. You still need to tweak it a little the get object

How to write api test case for generic class based views in django rest framework?

Here i am writing some api test case for some create,update views and i tried like this.But this is giving me error.What i might be doing wrong?Is there any solution for this?
self.assertEqual(response.status_code,status.HTTP_200_OK)
AssertionError: 403 != 200
----------------------------------------------------------------------
Ran 2 tests in 0.031s
FAILED (failures=2)
Destroying test database for alias 'default'...
urls.py
app_name = 'product'
urlpatterns = [
path('create/', ProductCreateAPIView.as_view(), name='create-product'),
path('list/', ProductListAPIView.as_view(), name='list-product'),
path('detail/<int:pk>/', ProductDetailAPIView.as_view(), name='detail-product'),
]
views.py
class ProductCreateAPIView(generics.CreateAPIView):
serializer_class = ProductSerializer
permission_classes = [IsAdminUser]
class ProductListAPIView(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
permission_classes = [IsAdminUser]
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name', 'description', 'category']
class ProductDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
permission_classes = [IsAdminUser]
tests.py
CREATE_PRODUCT_URL = reverse('product:create-product')
LIST_PRODUCT_URL = reverse('product:list-product')
class CreateProductTest(APITestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.post(CREATE_PRODUCT_URL,format='json')
print(response.status_code)
self.assertEqual(response.status_code,status.HTTP_201_CREATED)
class ListProductTest(APITestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get(LIST_PRODUCT_URL,format='json')
print(response.status_code)
self.assertEqual(response.status_code,status.HTTP_200_OK)
You are getting 403 code because all of your views are permitted only to superusers because of the IsAdminUser permission.
You need to make your tests by logging in as admin user. You can use login method of Client() to do that.
And for your Create test method you need to provide a valid Product in your input as you want test a view that is supposed to create a product.
Here is a sample test case for your CreateProductTest and ListProductTest:
from django.contrib.auth import get_user_model
from django.test import Client, TestCase
from django.urls import reverse
from rest_framework import status
CREATE_PRODUCT_URL = reverse('product:create-product')
LIST_PRODUCT_URL = reverse('product:list-product')
class CreateProductTest(TestCase):
def setUp(self):
self.client = Client()
self.user = get_user_model().objects.create_superuser(
"admintest",
"admintest#admintest.com",
"admintest"
)
self.client.login(username='admintest', password='admintest')
def test_details(self):
payload = {
'product_key1', 'product_value1',
'product_key2', 'product_value2',
# ...
}
response = self.client.post(CREATE_PRODUCT_URL, payload)
print(response.status_code)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
class ListProductTest(APITestCase):
def setUp(self):
self.client = Client()
self.user = get_user_model().objects.create_superuser(
"admintest",
"admintest#admintest.com",
"admintest"
)
self.client.login(username='admintest', password='admintest')
def test_details(self):
response = self.client.get(LIST_PRODUCT_URL,format='json')
print(response.status_code)
self.assertEqual(response.status_code,status.HTTP_200_OK)
Steps to follow
create user in setUp method
authenticate user in test_<method>
send request to url with data
CREATE_PRODUCT_URL = reverse('product:create-product')
class CreateProductTest(APITestCase):
def setUp(self):
self.client = Client()
self.user = User(username="testuser", email="testemail#test.com")
self.user.is_staff = True
self.user.set_password('secret')
self.user.save()
def test_details(self):
self.assertTrue(self.client.login(username="testuser", password="secret"))
post_data = {} # your data here in dict
response = self.client.post(CREATE_PRODUCT_URL, post_data, format='json')
print(response.status_code)
self.assertEqual(response.status_code,status.HTTP_201_CREATED)