dev: enable instance configuration and instance admin roles

This commit is contained in:
pablohashescobar 2023-11-14 15:56:48 +05:30
parent 1eac8ead86
commit 46f2663854
10 changed files with 244 additions and 39 deletions

View File

@ -73,14 +73,15 @@ def instance_registration():
license_key=data.get("license_key"),
api_key=data.get("api_key"),
version=data.get("version"),
email=data.get("email"),
owner=user,
primary_email=data.get("email"),
primary_owner=user,
last_checked_at=timezone.now(),
)
# Create instance admin
_ = InstanceAdmin.objects.create(
user=user,
instance=instance,
role=20,
)
print(f"Instance succesfully registered with owner: {instance.owner.email}")

View File

@ -0,0 +1 @@
from .instance import InstanceOwnerPermission, InstanceAdminPermission

View File

@ -0,0 +1,23 @@
# Third party imports
from rest_framework.permissions import BasePermission
# Module imports
from plane.license.models import Instance, InstanceAdmin
class InstanceOwnerPermission(BasePermission):
def has_permission(self, request, view):
instance = Instance.objects.first()
return InstanceAdmin.objects.filter(
role=20,
instance=instance,
).exists()
class InstanceAdminPermission(BasePermission):
def has_permission(self, request, view):
instance = Instance.objects.first()
return InstanceAdmin.objects.filter(
role__gte=15,
instance=instance,
).exists()

View File

@ -1 +1 @@
from .instance import InstanceSerializer
from .instance import InstanceSerializer, InstanceAdminSerializer, InstanceConfigurationSerializer

View File

@ -1,18 +1,19 @@
# Module imports
from plane.license.models import Instance
from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
from plane.api.serializers import BaseSerializer
from plane.api.serializers import UserAdminLiteSerializer
class InstanceSerializer(BaseSerializer):
owner_details = UserAdminLiteSerializer(source="owner", read_only=True)
primary_owner_details = UserAdminLiteSerializer(source="primary_owner", read_only=True)
class Meta:
model = Instance
fields = "__all__"
read_only_fields = [
"id",
"owner",
"primary_owner",
"primary_email",
"instance_id",
"license_key",
"api_key",
@ -20,3 +21,21 @@ class InstanceSerializer(BaseSerializer):
"email",
"last_checked_at",
]
class InstanceAdminSerializer(BaseSerializer):
user_detail = UserAdminLiteSerializer(source="user", read_only=True)
class Meta:
model = InstanceAdmin
read_only_fields = [
"id",
"instance",
"user",
]
class InstanceConfigurationSerializer(BaseSerializer):
class Meta:
model = InstanceConfiguration
fields = "__all__"

View File

@ -1 +1,6 @@
from .instance import InstanceEndpoint, TransferOwnerEndpoint
from .instance import (
InstanceEndpoint,
TransferPrimaryOwnerEndpoint,
InstanceAdminEndpoint,
InstanceConfigurationEndpoint,
)

View File

@ -1,33 +1,39 @@
# Python imports
import os
import json
import requests
import uuid
# Django imports
from django.utils import timezone
# Third party imports
from rest_framework import status
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
# Module imports
from plane.api.views import BaseAPIView
from plane.license.models import Instance
from plane.license.api.serializers import InstanceSerializer
from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
from plane.license.api.serializers import InstanceSerializer, InstanceAdminSerializer, InstanceConfigurationSerializer
from plane.license.api.permissions import (
InstanceOwnerPermission,
InstanceAdminPermission,
)
from plane.db.models import User
class InstanceEndpoint(BaseAPIView):
def get_permissions(self):
if self.request.method == "GET":
self.permission_classes = [
AllowAny,
]
else:
self.permission_classes = [
InstanceOwnerPermission,
]
return super(InstanceEndpoint, self).get_permissions()
def get(self, request):
instance = Instance.objects.first()
# get the instance
if instance is None:
return Response({"activated": False}, status=status.HTTP_400_BAD_REQUEST)
# Check the accessing user
if str(request.user.id) != str(instance.owner_id):
return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN)
# Return instance
serializer = InstanceSerializer(instance)
data = {
@ -39,9 +45,6 @@ class InstanceEndpoint(BaseAPIView):
def patch(self, request):
# Get the instance
instance = Instance.objects.first()
# Check the accessing user
if instance is not None and str(request.user.id) != str(instance.owner_id):
return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN)
serializer = InstanceSerializer(instance, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
@ -49,16 +52,15 @@ class InstanceEndpoint(BaseAPIView):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class TransferOwnerEndpoint(BaseAPIView):
class TransferPrimaryOwnerEndpoint(BaseAPIView):
permission_classes = [
InstanceOwnerPermission,
]
# Transfer the owner of the instance
def post(self, request):
instance = Instance.objects.first()
# Check the accessing user
if instance is not None and str(request.user.id) != str(instance.owner_id):
return Response({"error": "Forbidden"}, status=status.HTTP_403_FORBIDDEN)
# Get the email of the new user
email = request.data.get("email", False)
if not email:
@ -68,24 +70,101 @@ class TransferOwnerEndpoint(BaseAPIView):
# Get users
user = User.objects.get(email=email)
user.is_superuser = True
user.save(update_fields=["is_superuser"])
# Save the instance user
instance.owner = user
instance.email = user.email
instance.primary_owner = user
instance.primary_email = user.email
instance.save(update_fields=["owner", "email"])
# Add the user to admin
_ = InstanceAdmin.objects.get_or_create(
instance=instance,
user=user,
role=20,
)
return Response(
{"message": "Owner successfully updated"}, status=status.HTTP_200_OK
)
class InstanceAdminEndpoint(BaseAPIView):
def get_permissions(self):
if self.request.method == "GET":
self.permission_classes = [
AllowAny,
]
elif self.request.method in ["POST", "DELETE"]:
self.permission_classes = [
InstanceOwnerPermission,
]
else:
self.permission_classes = [
InstanceAdminPermission,
]
return super(InstanceAdminEndpoint, self).get_permissions()
# Create an instance admin
def post(self, request):
email = request.data.get("email", False)
role = request.data.get("role", 15)
if not email:
return Response(
{"error": "Email is required"}, status=status.HTTP_400_BAD_REQUEST
)
def get(self, request, pk=None):
instance = Instance.objects.first()
if instance is None:
return Response(
{"error": "Instance is not registered yet"},
status=status.HTTP_403_FORBIDDEN,
)
# Fetch the user
user = User.objects.get(email=email)
instance_admin = InstanceAdmin.objects.create(
instance=instance,
user=user,
role=role,
)
serializer = InstanceAdminSerializer(instance_admin)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def get(self, request):
instance = Instance.objects.first()
if instance is None:
return Response(
{"error": "Instance is not registered yet"},
status=status.HTTP_403_FORBIDDEN,
)
instance_admins = InstanceAdmin.objects.filter(instance=instance)
serializer = InstanceAdminSerializer(instance_admins, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def delete(self, request, pk):
instance = Instance.objects.first()
instance_admin = InstanceAdmin.objects.filter(instance=instance, pk=pk).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class InstanceConfigurationEndpoint(BaseAPIView):
permission_classes = [InstanceAdminEndpoint,]
def get(self, request):
instance_configurations = InstanceConfiguration.objects.all()
serializer = InstanceConfigurationSerializer(instance_configurations, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def patch(self, request):
key = request.data.get("key", False)
if not key:
return Response({"error": "Key is required"}, status=status.HTTP_400_BAD_REQUEST)
configuration = InstanceConfiguration.objects.get(key=key)
configuration.value = request.data.get("value")
configuration.save()
serializer = InstanceConfigurationSerializer(configuration)
return Response(serializer.data, status=status.HTTP_200_OK)

View File

@ -0,0 +1,50 @@
# Generated by Django 4.2.5 on 2023-11-14 10:14
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('license', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='instance',
old_name='email',
new_name='primary_email',
),
migrations.RemoveField(
model_name='instance',
name='owner',
),
migrations.AddField(
model_name='instance',
name='primary_owner',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_primary_owner', to=settings.AUTH_USER_MODEL),
),
migrations.CreateModel(
name='InstanceAdmin',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
('role', models.PositiveIntegerField(choices=[(20, 'Owner'), (15, 'Admin')], default=15)),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
('instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='admins', to='license.instance')),
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_owner', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'Instance Admin',
'verbose_name_plural': 'Instance Admins',
'db_table': 'instance_admins',
'ordering': ('-created_at',),
},
),
]

View File

@ -6,6 +6,12 @@ from django.conf import settings
from plane.db.models import BaseModel
from plane.db.mixins import AuditModel
ROLE_CHOICES = (
(20, "Owner"),
(15, "Admin"),
)
class Instance(BaseModel):
# General informations
instance_name = models.CharField(max_length=255)
@ -15,12 +21,12 @@ class Instance(BaseModel):
api_key = models.CharField(max_length=16)
version = models.CharField(max_length=10)
# User information
email = models.CharField(max_length=256)
owner = models.ForeignKey(
primary_email = models.CharField(max_length=256)
primary_owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name="instance_owner",
related_name="instance_primary_owner",
)
# Instnace specifics
last_checked_at = models.DateTimeField()
@ -43,7 +49,8 @@ class InstanceAdmin(BaseModel):
null=True,
related_name="instance_owner",
)
instance = models.ForeignKey("db.Instance", on_delete=models.CASCADE, related_name="admins")
instance = models.ForeignKey(Instance, on_delete=models.CASCADE, related_name="admins")
role = models.PositiveIntegerField(choices=ROLE_CHOICES, default=15)
class Meta:
verbose_name = "Instance Admin"

View File

@ -1,6 +1,11 @@
from django.urls import path
from plane.license.api.views import InstanceEndpoint, TransferOwnerEndpoint
from plane.license.api.views import (
InstanceEndpoint,
TransferPrimaryOwnerEndpoint,
InstanceAdminEndpoint,
InstanceConfigurationEndpoint,
)
urlpatterns = [
path(
@ -9,8 +14,23 @@ urlpatterns = [
name="instance",
),
path(
"instances/transfer-owner/",
TransferOwnerEndpoint.as_view(),
"instances/transfer-primary-owner/",
TransferPrimaryOwnerEndpoint.as_view(),
name="instance",
),
path(
"instances/admins/",
InstanceAdminEndpoint.as_view(),
name="instance-admins",
),
path(
"instances/admins/<uuid:pk>/",
InstanceAdminEndpoint.as_view(),
name="instance-admins",
),
path(
"instances/configurations/",
InstanceConfigurationEndpoint.as_view(),
name="instance-configuration",
),
]