Commit 8aa8b7e6 authored by Lukas Burgey's avatar Lukas Burgey

Fix deletion of ssh keys

parent 7fd2ac35
......@@ -21,8 +21,14 @@ class TypeFilter(admin.SimpleListFilter):
class ClientAdmin(UserAdmin):
list_filter = (TypeFilter,)
admin.site.register(OIDCConfig)
admin.site.register(models.RabbitMQInstance)
admin.site.register(models.User, ClientAdmin)
admin.site.register(models.Site)
admin.site.register(models.Service)
admin.site.register(models.RabbitMQInstance)
admin.site.register(OIDCConfig)
admin.site.register(models.SSHPublicKey)
admin.site.register(models.Deployment)
admin.site.register(models.DeploymentState)
admin.site.register(models.DeploymentStateItem)
......@@ -48,7 +48,7 @@ class AckView(views.APIView):
for item in request.user.site.state_items.all():
if item.parent.id == int(state_id):
if item.parent.state_target == 'deployed':
item.client_deployed()
item.client_deployed({})
else:
item.client_removed()
return Response({})
......@@ -79,7 +79,7 @@ class ResponseView(views.APIView):
if state_item is not None:
if status == 'deployed':
state_item.client_deployed(
credentials=output.get('credentials', None),
credentials=output.get('credentials', {}),
)
return Response({})
......
......@@ -7,7 +7,7 @@ from rest_framework.permissions import AllowAny
from rest_framework.response import Response
from rest_framework import status
from . import serializers
from .. import models
from .. import models, serializers as model_serializers
LOGGER = logging.getLogger(__name__)
......@@ -88,7 +88,9 @@ class SSHPublicKeyView(views.APIView):
# we do not delete ssh keys directly, as we need to keep track
# of them until all clients have also deleted them
key.delete_key()
return _api_state_response(request)
return Response({
'deleted': True,
})
elif request.data['type'] == 'add':
if 'key' in request.data:
key = models.SSHPublicKey(
......@@ -97,7 +99,11 @@ class SSHPublicKeyView(views.APIView):
key=request.data['key']['key'],
)
key.save()
return _api_state_response(request)
return Response(
model_serializers.SSHPublicKeySerializer(
key,
).data
)
LOGGER.error('SSHPublicKeyView: malformed request %s', request)
return _api_error_response("malformed request")
......
......@@ -256,6 +256,23 @@ class User(AbstractUser):
editable=False,
)
@property
def deployment_states(self):
states = []
for deployment in self.deployments.all():
for state in deployment.states.all():
states.append(state)
return states
@property
def deployment_state_items(self):
items = []
for state in self.deployment_states:
for item in state.state_items.all():
items.append(item)
return items
# returns the user as identified by userinfo and idp
# if the user does not exists
@classmethod
......@@ -476,17 +493,20 @@ class SSHPublicKey(models.Model):
editable=False,
)
def states(self):
states = []
@property
def deployed_anywhere(self):
for state in self.states.all():
states.append(state.states)
for item in state.state_items.all():
if item.state == 'deployed' or item.state == 'removal_pending':
return True
return False
# does not directly delete the key if the key is deployed or withdrawn
# somewhere
# the receiver 'delete_withdrawn_ssh_key' does the actual deletion
def delete_key(self):
# if this key is not deployed anywhere we delete it now
if analyze_states(self.states.all()) == 'not_deployed':
if not self.deployed_anywhere:
LOGGER.info(self.msg('Direct deletion of key'))
self.delete()
return
......@@ -501,10 +521,19 @@ class SSHPublicKey(models.Model):
# when a key is withdrawn by a client we try to finally delete it
def try_final_deletion(self):
if (self.deleted and not self.states.exists()):
LOGGER.info(self.msg( 'All clients have withdrawn this key. Final deletion'))
self.delete()
return
if self.deleted:
if not self.deployed_anywhere:
LOGGER.info(self.msg('All clients have withdrawn this key. Final deletion'))
self._final_deletion()
def _final_deletion(self):
_self = self
for state in self.states.all():
#for item in state.state_items.all():
# item.delete()
state.delete()
_self.delete()
def __str__(self):
if self.deleted:
......@@ -526,7 +555,8 @@ class Deployment(models.Model):
user = models.ForeignKey(
User,
related_name='deployments',
on_delete=models.CASCADE,
on_delete=models.SET_NULL,
null=True,
)
service = models.ForeignKey(
Service,
......@@ -572,7 +602,6 @@ class Deployment(models.Model):
LOGGER.error(self.msg('already active'))
return
LOGGER.debug(self.msg(str(self.ssh_keys.all())))
for key in self.ssh_keys.all():
self._deploy_key(key)
......@@ -661,15 +690,7 @@ class DeploymentState(models.Model):
related_name='states',
on_delete=models.CASCADE,
)
# TODO is this relation needed?
# State does relate to Deployment which relates to User
user = models.ForeignKey(
User,
related_name='deployment_states',
# TODO deleting the user leaves us without references about its deployments
# we _HAVE_ to remove all deployments prior to deleting site user
on_delete=models.CASCADE,
)
# which state do we currently want to reach?
state_target = models.CharField(
max_length=50,
......@@ -677,6 +698,10 @@ class DeploymentState(models.Model):
default='deployed',
)
@property
def user(self):
return self.deployment.user
@property
def states(self):
return [item.state for item in self.state_items.all()]
......@@ -708,7 +733,6 @@ class DeploymentState(models.Model):
# create new state if not
state = cls(
deployment=deployment,
user=deployment.user,
key=key,
)
state.save()
......@@ -718,7 +742,6 @@ class DeploymentState(models.Model):
for site in deployment.service.site.all():
deploy = DeploymentStateItem(
parent=state,
user=deployment.user,
site=site,
)
deploy.save()
......@@ -786,15 +809,6 @@ class DeploymentStateItem(models.Model):
site = models.ForeignKey(
Site,
related_name='state_items',
# TODO deleting the site leaves us without references about its deployments
# we _HAVE_ to remove all deployments prior to deleting a site
on_delete=models.CASCADE,
)
user = models.ForeignKey(
User,
related_name='deployment_state_items',
# TODO deleting the user leaves us without references about its deployments
# we _HAVE_ to remove all deployments prior to deleting site user
on_delete=models.CASCADE,
)
state = models.CharField(
......@@ -806,17 +820,17 @@ class DeploymentStateItem(models.Model):
# questions for the user (needed for deployment
questionnaire = JSONField(
default=questionnaire_default,
null=True,
blank=True,
)
# credentials for the service
# only valid when state == deployed
credentials = JSONField(
default=credential_default,
null=True,
blank=True,
)
@property
def user(self):
return self.parent.user
@property
def service(self):
return self.parent.service
......@@ -865,8 +879,15 @@ class DeploymentStateItem(models.Model):
# client: removed
def client_removed(self):
# TODO check if all values are reset correctly
self.credentials = credential_default()
self.questionnaire = questionnaire_default()
self._set_state('not_deployed')
# this removal maybe was the last of out ssh key
self.key.try_final_deletion()
# client: questionnaire
def client_questionnaire(self, questionnaire=None):
self.questionnaire = questionnaire
......
......@@ -9,22 +9,35 @@ from .models import SSHPublicKey, AuthGroup
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ['id', 'name']
fields = [
'id',
'name',
]
class AuthGroupSerializer(serializers.ModelSerializer):
class Meta:
model = AuthGroup
fields = ['id', 'name']
fields = [
'id',
'name',
]
class SSHPublicKeySerializer(serializers.ModelSerializer):
class Meta:
model = SSHPublicKey
fields = ['id', 'name', 'key']
fields = [
'id',
'name',
'key',
]
class SSHPublicKeyRefSerializer(serializers.ModelSerializer):
class Meta:
model = SSHPublicKey
fields = ['id', 'name']
fields = [
'id',
'name',
]
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 to comment