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"), license_key=data.get("license_key"),
api_key=data.get("api_key"), api_key=data.get("api_key"),
version=data.get("version"), version=data.get("version"),
email=data.get("email"), primary_email=data.get("email"),
owner=user, primary_owner=user,
last_checked_at=timezone.now(), last_checked_at=timezone.now(),
) )
# Create instance admin # Create instance admin
_ = InstanceAdmin.objects.create( _ = InstanceAdmin.objects.create(
user=user, user=user,
instance=instance, instance=instance,
role=20,
) )
print(f"Instance succesfully registered with owner: {instance.owner.email}") 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 # 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 BaseSerializer
from plane.api.serializers import UserAdminLiteSerializer from plane.api.serializers import UserAdminLiteSerializer
class InstanceSerializer(BaseSerializer): class InstanceSerializer(BaseSerializer):
owner_details = UserAdminLiteSerializer(source="owner", read_only=True) primary_owner_details = UserAdminLiteSerializer(source="primary_owner", read_only=True)
class Meta: class Meta:
model = Instance model = Instance
fields = "__all__" fields = "__all__"
read_only_fields = [ read_only_fields = [
"id", "id",
"owner", "primary_owner",
"primary_email",
"instance_id", "instance_id",
"license_key", "license_key",
"api_key", "api_key",
@ -20,3 +21,21 @@ class InstanceSerializer(BaseSerializer):
"email", "email",
"last_checked_at", "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 # Django imports
from django.utils import timezone from django.utils import timezone
# Third party imports # Third party imports
from rest_framework import status from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.permissions import AllowAny
# Module imports # Module imports
from plane.api.views import BaseAPIView from plane.api.views import BaseAPIView
from plane.license.models import Instance from plane.license.models import Instance, InstanceAdmin, InstanceConfiguration
from plane.license.api.serializers import InstanceSerializer from plane.license.api.serializers import InstanceSerializer, InstanceAdminSerializer, InstanceConfigurationSerializer
from plane.license.api.permissions import (
InstanceOwnerPermission,
InstanceAdminPermission,
)
from plane.db.models import User from plane.db.models import User
class InstanceEndpoint(BaseAPIView): 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): def get(self, request):
instance = Instance.objects.first() instance = Instance.objects.first()
# get the instance # get the instance
if instance is None: if instance is None:
return Response({"activated": False}, status=status.HTTP_400_BAD_REQUEST) 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 # Return instance
serializer = InstanceSerializer(instance) serializer = InstanceSerializer(instance)
data = { data = {
@ -39,9 +45,6 @@ class InstanceEndpoint(BaseAPIView):
def patch(self, request): def patch(self, request):
# Get the instance # Get the instance
instance = Instance.objects.first() 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) serializer = InstanceSerializer(instance, data=request.data, partial=True)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
@ -49,16 +52,15 @@ class InstanceEndpoint(BaseAPIView):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 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 # Transfer the owner of the instance
def post(self, request): def post(self, request):
instance = Instance.objects.first() 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 # Get the email of the new user
email = request.data.get("email", False) email = request.data.get("email", False)
if not email: if not email:
@ -68,24 +70,101 @@ class TransferOwnerEndpoint(BaseAPIView):
# Get users # Get users
user = User.objects.get(email=email) user = User.objects.get(email=email)
user.is_superuser = True
user.save(update_fields=["is_superuser"])
# Save the instance user # Save the instance user
instance.owner = user instance.primary_owner = user
instance.email = user.email instance.primary_email = user.email
instance.save(update_fields=["owner", "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( return Response(
{"message": "Owner successfully updated"}, status=status.HTTP_200_OK {"message": "Owner successfully updated"}, status=status.HTTP_200_OK
) )
class InstanceAdminEndpoint(BaseAPIView): 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() instance = Instance.objects.first()
if instance is None: 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.models import BaseModel
from plane.db.mixins import AuditModel from plane.db.mixins import AuditModel
ROLE_CHOICES = (
(20, "Owner"),
(15, "Admin"),
)
class Instance(BaseModel): class Instance(BaseModel):
# General informations # General informations
instance_name = models.CharField(max_length=255) instance_name = models.CharField(max_length=255)
@ -15,12 +21,12 @@ class Instance(BaseModel):
api_key = models.CharField(max_length=16) api_key = models.CharField(max_length=16)
version = models.CharField(max_length=10) version = models.CharField(max_length=10)
# User information # User information
email = models.CharField(max_length=256) primary_email = models.CharField(max_length=256)
owner = models.ForeignKey( primary_owner = models.ForeignKey(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null=True, null=True,
related_name="instance_owner", related_name="instance_primary_owner",
) )
# Instnace specifics # Instnace specifics
last_checked_at = models.DateTimeField() last_checked_at = models.DateTimeField()
@ -43,7 +49,8 @@ class InstanceAdmin(BaseModel):
null=True, null=True,
related_name="instance_owner", 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: class Meta:
verbose_name = "Instance Admin" verbose_name = "Instance Admin"

View File

@ -1,6 +1,11 @@
from django.urls import path from django.urls import path
from plane.license.api.views import InstanceEndpoint, TransferOwnerEndpoint from plane.license.api.views import (
InstanceEndpoint,
TransferPrimaryOwnerEndpoint,
InstanceAdminEndpoint,
InstanceConfigurationEndpoint,
)
urlpatterns = [ urlpatterns = [
path( path(
@ -9,8 +14,23 @@ urlpatterns = [
name="instance", name="instance",
), ),
path( path(
"instances/transfer-owner/", "instances/transfer-primary-owner/",
TransferOwnerEndpoint.as_view(), TransferPrimaryOwnerEndpoint.as_view(),
name="instance", 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",
),
] ]