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

Fix deletion of ssh keys

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