Commit 3ec83939 by Belákovics Ádám

Merge branch 'instance_auth' into 'DEV'

Instance auth

See merge request !17
parents c0d7f992 befe5fa1
Pipeline #862 passed with stage
in 1 minute 22 seconds
......@@ -17,6 +17,7 @@ django-cors-headers = "*"
openstacksdk = "*"
python-novaclient = "*"
keystoneauth1 = "*"
django-guardian = "*"
djoser = "*"
[requires]
......
{
"_meta": {
"hash": {
"sha256": "8cb2efb1cfa79de96297c90953faf757011ca1f1ff4784ac05226a249001e1f5"
"sha256": "dd7bfbb33d07cbcc96d1f3b1f838538dba41f3eb0bf5279381714b4b9abf90d7"
},
"pipfile-spec": 6,
"requires": {
......@@ -135,6 +135,14 @@
"index": "pypi",
"version": "==3.0.2"
},
"django-guardian": {
"hashes": [
"sha256:965d3a1e20fb3639e0ab16b0e768611f694c02d762916f80d9f3f7520c16aa7b",
"sha256:e8c4556c4e145028a5dcfd7b2611d52e1ac104af562017ce17c3f67e47a62693"
],
"index": "pypi",
"version": "==2.0.0"
},
"django-templated-mail": {
"hashes": [
"sha256:8db807effebb42a532622e2d142dfd453dafcd0d7794c4c3332acb90656315f9",
......
from django.contrib.auth.models import Permission
from django.contrib import admin
admin.site.register(Permission)
from django.apps import AppConfig
class AuthorizationConfig(AppConfig):
name = 'authorization'
from guardian.shortcuts import get_objects_for_user
import logging
logger = logging.getLogger(__name__)
class AuthorizationMixin():
authorization = {}
def get_objects_with_perms(self, user, method, instance):
auth_params = self.authorization[method]
if auth_params:
return get_objects_for_user(user, auth_params["filter"], instance)
else:
logger.error(f"Invalid method for authorization: {method}")
return False
def has_perms_for_object(self, user, method, instance):
auth_params = self.authorization[method]
if auth_params:
for perm in auth_params["object"]:
if not user.has_perm(perm, instance):
return False
return True
else:
logger.error(f"Invalid method for authorization: {method}")
return False
def has_perms_for_model(self, user, method):
auth_params = self.authorization[method]
if auth_params:
for perm in auth_params["model"]:
if not user.has_perm(perm):
return False
return True
else:
logger.error(f"Invalid method for authorization: {method}")
return False
......@@ -2,6 +2,13 @@ from django.contrib import admin
from instance.models import Instance, Flavor, Lease
admin.site.register(Instance)
from guardian.admin import GuardedModelAdmin
class InstanceAdmin(GuardedModelAdmin):
pass
admin.site.register(Instance, InstanceAdmin)
admin.site.register(Flavor)
admin.site.register(Lease)
# Generated by Django 2.2.4 on 2019-08-08 11:37
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('instance', '0010_instance_template'),
]
operations = [
migrations.AlterModelOptions(
name='instance',
options={'permissions': (('create_instance', 'Can create a new VM.'), ('use_instance', 'Can access the VM connection info.'), ('operate_instance', 'Can use basic lifecycle methods of the VM.'), ('administer_instance', 'Can delete VM.'), ('access_console', 'Can access the graphical console of a VM.'), ('change_resources', 'Can change resources of a VM.'), ('manage_access', 'Can manage access rights for the VM.'), ('config_ports', 'Can configure port forwards.'))},
),
]
# Generated by Django 2.2.4 on 2019-08-29 07:54
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('instance', '0011_auto_20190808_1137'),
]
operations = [
migrations.AlterModelOptions(
name='instance',
options={'default_permissions': (), 'permissions': (('create_instance', 'Can create a new VM.'), ('use_instance', 'Can access the VM connection info.'), ('operate_instance', 'Can use basic lifecycle methods of the VM.'), ('administer_instance', 'Can delete VM.'), ('access_console', 'Can access the graphical console of a VM.'), ('change_resources', 'Can change resources of a VM.'), ('manage_access', 'Can manage access rights for the VM.'), ('config_ports', 'Can configure port forwards.'))},
),
]
# Generated by Django 2.2.4 on 2019-08-30 12:27
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('instance', '0012_auto_20190829_0754'),
]
operations = [
migrations.AlterModelOptions(
name='instance',
options={'default_permissions': (), 'permissions': (('create_instance', 'Can create a new VM.'), ('create_template_from_instance', 'Can create template from instance.'), ('use_instance', 'Can access the VM connection info.'), ('operate_instance', 'Can use basic lifecycle methods of the VM.'), ('administer_instance', 'Can delete VM.'), ('access_console', 'Can access the graphical console of a VM.'), ('change_resources', 'Can change resources of a VM.'), ('manage_access', 'Can manage access rights for the VM.'), ('config_ports', 'Can configure port forwards.'))},
),
]
......@@ -5,8 +5,8 @@ from django.utils import timezone
from datetime import timedelta
from image.models import Disk
from interface_openstack.implementation.vm.instance import (
OSVirtualMachineManager
)
OSVirtualMachineManager
)
import logging
logger = logging.getLogger(__name__)
......@@ -45,8 +45,8 @@ class Flavor(models.Model):
name = models.CharField(blank=True, max_length=100)
description = models.CharField(blank=True, max_length=200)
remote_id = models.CharField(
max_length=100, help_text="ID of the instance on the backend"
)
max_length=100, help_text="ID of the instance on the backend"
)
ram = models.IntegerField(blank=True, null=True)
vcpu = models.IntegerField(blank=True, null=True)
initial_disk = models.IntegerField(blank=True, null=True)
......@@ -82,6 +82,20 @@ class Instance(models.Model):
"""
from template.models import ImageTemplate
class Meta:
default_permissions = ()
permissions = (
('create_instance', 'Can create a new VM.'),
('create_template_from_instance', 'Can create template from instance.'),
('use_instance', 'Can access the VM connection info.'),
('operate_instance', 'Can use basic lifecycle methods of the VM.'),
('administer_instance', 'Can delete VM.'),
('access_console', 'Can access the graphical console of a VM.'),
('change_resources', 'Can change resources of a VM.'),
('manage_access', 'Can manage access rights for the VM.'),
('config_ports', 'Can configure port forwards.'),
)
name = models.CharField(max_length=100,
help_text="Human readable name of instance")
......@@ -167,10 +181,20 @@ class Instance(models.Model):
lease = self.lease
return (
timezone.now() + timedelta(
seconds=lease.suspend_interval_in_sec),
seconds=lease.suspend_interval_in_sec),
timezone.now() + timedelta(
seconds=lease.delete_interval_in_sec)
)
seconds=lease.delete_interval_in_sec)
)
def renew(self, lease=None):
"""Renew virtual machine, if a new lease is provided it changes it as well.
"""
if lease is None:
lease = self.lease
else:
self.lease = lease
self.time_of_suspend, self.time_of_delete = self.get_renew_times(lease)
self.save()
def delete(self):
try:
......@@ -192,5 +216,13 @@ class Instance(models.Model):
@classmethod
def generate_password(self):
return User.objects.make_random_password(
allowed_chars='abcdefghijklmnopqrstuvwx'
'ABCDEFGHIJKLMNOPQRSTUVWX123456789')
allowed_chars='abcdefghijklmnopqrstuvwx'
'ABCDEFGHIJKLMNOPQRSTUVWX123456789')
def change_name(self, new_name):
self.name = new_name
self.save()
def change_description(self, new_description):
self.description = new_description
self.save()
......@@ -9,9 +9,46 @@ from template.serializers import InstanceFromTemplateSerializer
from instance.models import Instance, Flavor, Lease
from template.models import ImageTemplate
from template.serializers import ImageTemplateModelSerializer
class InstanceViewSet(ViewSet):
from authorization.mixins import AuthorizationMixin
authorization = {
"list": {"filter": ["use_instance"]},
"create": {"model": ["instance.create_instance"]},
"retrieve": {"object": ["use_instance"]},
"update": {"object": ["use_instance"]},
"destroy": {"object": ["administer_instance"]},
"template": {"model": ["create_template_from_instance"],
"object": ["use_instance"]},
"start": {"object": ["operate_instance"]},
"stop": {"object": ["operate_instance"]},
"suspend": {"object": ["operate_instance"]},
"wake_up": {"object": ["operate_instance"]},
"reset": {"object": ["operate_instance"]},
"reboot": {"object": ["operate_instance"]},
}
update_actions = [
"change_name",
"change_description",
"renew",
"change_lease",
"change_flavor",
"attach_disk",
"resize_disk",
"add_permission",
"remove_permission",
"open_port",
"close_port",
"add_network",
"remove_network",
"new_password",
]
class InstanceViewSet(AuthorizationMixin, ViewSet):
authorization = authorization
def get_object(self, pk):
try:
......@@ -20,10 +57,14 @@ class InstanceViewSet(ViewSet):
raise Http404
def list(self, request):
instances = Instance.objects.all()
instances = self.get_objects_with_perms(request.user, "list", Instance)
return Response(InstanceSerializer(instances, many=True).data)
def create(self, request):
if not self.has_perms_for_model(request.user, 'create'):
return Response({"error": "No permission to create Virtual Machine."},
status=status.HTTP_401_UNAUTHORIZED)
data = request.data
template = ImageTemplate.objects.get(pk=data["template"])
......@@ -50,6 +91,10 @@ class InstanceViewSet(ViewSet):
def retrieve(self, request, pk):
instance = self.get_object(pk)
if not self.has_perms_for_object(request.user, 'retrieve', instance):
return Response({"error": "No permission to access the Virtual Machine."},
status=status.HTTP_401_UNAUTHORIZED)
instanceDict = InstanceSerializer(instance).data
remoteInstance = instance.get_remote_instance()
remoteInstanceDict = remoteInstance.__dict__
......@@ -59,21 +104,67 @@ class InstanceViewSet(ViewSet):
return Response(merged_dict)
def update(self, request, pk, format=None):
instance = self.get_object(pk)
serializer = InstanceSerializer(instance, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
if request.data["action"] in update_actions:
instance = self.get_object(pk)
if not self.has_perms_for_object(request.user, 'update', instance):
return Response({"error": "No permission to access the Virtual Machine."},
status=status.HTTP_401_UNAUTHORIZED)
action = request.data["action"]
if action == "change_name":
instance.change_name(request.data["name"])
elif action == "change_description":
instance.change_description(request.data["description"])
elif action == "renew":
instance.renew()
elif action == "change_lease":
lease = Lease.objects.get(pk=request.data["lease"])
instance.renew(lease)
elif action == "change_flavor":
pass
elif action == "attach_disk":
pass
elif action == "resize_disk":
pass
elif action == "add_permission":
pass
elif action == "remove_permission":
pass
elif action == "open_port":
pass
elif action == "close_port":
pass
elif action == "add_network":
pass
elif action == "remove_network":
pass
elif action == "new_password":
pass
instanceDict = InstanceSerializer(instance).data
remoteInstance = instance.get_remote_instance()
remoteInstanceDict = remoteInstance.__dict__
merged_dict = {"db": instanceDict, "openstack": remoteInstanceDict}
return Response(merged_dict)
else:
return Response({"error": "Unknown update action."}, status=status.HTTP_400_BAD_REQUEST)
def destroy(self, request, pk, format=None):
instance = self.get_object(pk)
if not self.has_perms_for_object(request.user, 'destroy', instance):
return Response({"error": "No permission to destroy the Virtual Machine."},
status=status.HTTP_401_UNAUTHORIZED)
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["post"])
def template(self, request, pk):
instance = self.get_object(pk)
if not self.has_perms_for_model(request.user, 'template'):
return Response({"error": "No permission to create template from instance."},
status=status.HTTP_401_UNAUTHORIZED)
if not self.has_perms_for_object(request.user, 'template', instance):
return Response({"error": "No permission to access the Virtual Machine."},
status=status.HTTP_401_UNAUTHORIZED)
serializer = InstanceFromTemplateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
......@@ -87,8 +178,10 @@ class InstanceViewSet(ViewSet):
@action(detail=True, methods=["POST"])
def actions(self, request, pk):
instance = self.get_object(pk)
if not self.has_perms_for_object(request.user, request.data["action"], instance):
return Response({"error": "No permission to use this action on the VM."},
status=status.HTTP_401_UNAUTHORIZED)
success = instance.execute_common_action(action=request.data["action"])
return Response(success)
......
......@@ -41,6 +41,7 @@ INSTALLED_APPS = [
"djoser",
"rest_framework_swagger",
"corsheaders",
"guardian",
"django_nose",
]
......@@ -49,6 +50,7 @@ LOCAL_APPS = [
"instance",
"storage",
"template",
"authorization",
]
INSTALLED_APPS += LOCAL_APPS
......@@ -112,6 +114,7 @@ REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' # needed for swagger
}
# Internationalization
......@@ -230,3 +233,10 @@ LOGGING = {
for i in LOCAL_APPS:
LOGGING['loggers'][i] = {'handlers': ['console'], 'level': 'DEBUG'}
# Configure django-guardian for the authorization
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # this is default
'guardian.backends.ObjectPermissionBackend',
)
......@@ -12,5 +12,8 @@ ADMIN_ENABLED = True
ALLOWED_HOSTS = ['*']
AUTH_PASSWORD_VALIDATORS = []
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = LOCAL_APPS
\ No newline at end of file
NOSE_ARGS = LOCAL_APPS
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 sign in to comment