Commit 226df6a3 authored by Lukas Burgey's avatar Lukas Burgey
Browse files

Implement new credential tracking

A large step towards solving #7
parent 6f1b9c1c
......@@ -290,19 +290,17 @@ class NewDeployment(models.Model):
self.publish_to_client()
# each state item publishes its state to the user
# some parts of the user object / the credentials have changed
# we have to update them at the clients
def user_credentials_changed(self):
LOGGER.debug('User object of %s changed', self.user)
# TODO implement
def user_credential_added(self, key):
for item in self.state_items.all():
item.user_credential_added(key)
self.publish()
# Currently out of scope
# def user_credential_added(self, key):
# raise Exception('user_credentialis currently out of scope and therefore not implemented')
def user_credential_removed(self, key):
for item in self.state_items.all():
item.user_credential_removed(key)
# Currently out of scope
# def user_credential_removed(self, key):
# raise Exception('user_credential_removed is currently out of scope and therefore not implemented')
self.publish()
def service_added(self, service):
LOGGER.debug(self.msg('Adding service {}'.format(service)))
......@@ -338,7 +336,7 @@ class NewDeployment(models.Model):
LOGGER.error('Deployment has neither a group or a service')
raise ValueError('Deployment has neither a group or a service')
# update the state of the remote webpage
# sends a state update via RabbitMQ / STOMP to the users webpage instance
def publish_to_user(self):
if self.user is not None:
# mitigating circular dependencies here
......@@ -351,12 +349,16 @@ class NewDeployment(models.Model):
msg,
)
def publish(self):
self.publish_to_user()
self.publish_to_client()
def msg(self, msg):
return '[{}] {}'.format(self, msg)
return '{} - {}'.format(self, msg)
def _set_target(self, target):
self.state_target = target
LOGGER.debug(self.msg('target is now: '+target))
LOGGER.debug(self.msg('Target changed to '+target))
self.save()
def __str__(self):
......@@ -427,23 +429,6 @@ class NewDeploymentStateItem(models.Model):
def group(self):
return self.parent.group
# starts tracking this the credential for this item
def add_credential(self, credential):
if settings.DEBUG_CREDENTIALS:
LOGGER.debug('add_credential: %s %s', self, credential)
CredentialState.get_credential_state(
credential,
self,
)
def remove_credential(self, credential):
if settings.DEBUG_CREDENTIALS:
LOGGER.debug('remove_credential: %s %s', self, credential)
# TODO implement
# what is needs to be done here?
pass
@classmethod
def get_state_item(cls, parent=None, site=None, service=None):
try:
......@@ -463,13 +448,31 @@ class NewDeploymentStateItem(models.Model):
)
item.save()
# start tracking the states of the users ssh keys
for key in parent.user.ssh_keys.all():
item.add_credential(key)
LOGGER.debug('get_state_item: created %s', item)
return item
# starts tracking this the credential for this item
def user_credential_added(self, credential):
if settings.DEBUG_CREDENTIALS:
LOGGER.debug('user_credential_added: %s %s', self, credential)
credential_state = CredentialState.get_credential_state(
credential,
self,
)
def user_credential_removed(self, credential):
if settings.DEBUG_CREDENTIALS:
LOGGER.debug('user_credential_removed: %s %s', self, credential)
try:
credential_state = self.credential_states.get(credential=credential)
credential_state.credential_deleted()
self.parent.publish()
except CredentialState.DoesNotExist:
LOGGER.error(self.msg('Credential {} has no CredentialState'.format(credential)))
# STATE TRANSITIONS
# user: provisioning requested
......@@ -482,6 +485,9 @@ class NewDeploymentStateItem(models.Model):
LOGGER.info(self.msg('ignoring invalid state transition user_deploy'))
return
for credential_state in self.credential_states.all():
credential_state.set_target(DEPLOYED)
self._set_state(
DEPLOYMENT_PENDING,
publish=False, # the post response already contains the update
......@@ -509,6 +515,9 @@ class NewDeploymentStateItem(models.Model):
self._set_state(NOT_DEPLOYED)
return
for credential_state in self.credential_states.all():
credential_state.set_target(DEPLOYED)
self._set_state(
REMOVAL_PENDING,
publish=False, # the post response already contains the update
......@@ -521,19 +530,19 @@ class NewDeploymentStateItem(models.Model):
self.parent.publish_to_client()
def client_credential_states(self, credential_states):
LOGGER.debug('client_credential_states: %s', credential_states)
# maps ssh key names to their state
ssh_key_states = credential_states.get('ssh_key', None)
if ssh_key_states is not None:
for key_name in ssh_key_states:
try:
credential_state = self.credential_states.get(credential__name=key_name)
LOGGER.debug('RECEIVED update to credential state %s', credential_state)
credential_state.set(ssh_key_states[key_name])
except CredentialState.DoesNotExist:
LOGGER.error('Credential state for key %s and item %s does not exist', key_name, self)
LOGGER.error('selfs states: %s', self.credential_states.all())
# ASSUMPTION:
# if we hear nothing about a credential we assume it got deprovisioned!
for credential_state in self.credential_states.all():
if (
ssh_key_states is not None
and credential_state.credential.name in ssh_key_states
):
credential_state.set(ssh_key_states[credential_state.credential.name])
else:
credential_state.set(NOT_DEPLOYED)
# returns None on success, or a string describing an error
def client_response(self, output):
......@@ -580,35 +589,34 @@ class NewDeploymentStateItem(models.Model):
self._set_state(state)
return None
# the credentials which are currently being deployed / removed
def get_onroute_credential_states(self):
return self.credential_states.filter(
state=self.state,
)
def set_onroute_credential_states(self, state):
for credential_state in self.get_onroute_credential_states().all():
credential_state.set(state)
# resets all client sent values
def _reset(self):
self.credentials = credential_default()
self.questionnaire = questionnaire_default()
self.message = ''
def msg(self, msg):
return '[{}] {}'.format(self, msg)
def _set_state(self, state, publish=True):
self.set_onroute_credential_states(state)
# assure all user credentials have a state
for key in self.parent.user.ssh_keys.all():
try:
CredentialState.get_credential_state(
credential=key,
target=self,
)
except CredentialState.DoesNotExist:
LOGGER.error('CredentialState.DoesNotExist in _set_state')
self.state = state
self.save()
LOGGER.debug(self.msg('State changed to '+self.state))
if publish:
self.parent.publish_to_user()
def msg(self, msg):
return '{} - {}'.format(self, msg)
def __str__(self):
return 'Item: ({}:{}:{})#{}'.format(
return 'DSItem: ({}:{}:{})#{}'.format(
self.parent.id,
self.service,
self.site,
......@@ -658,44 +666,59 @@ class CredentialState(models.Model):
credential=credential,
target=target,
state=NOT_DEPLOYED,
state_target=target.parent.state_target,
)
new_state.save()
if settings.DEBUG_CREDENTIALS:
LOGGER.debug(new_state.msg('State created'))
LOGGER.debug(new_state.msg('Created'))
return new_state
def set_target(self, target):
# state_target is locked, since we are marked for deletion
if self._credential_deleted:
LOGGER.debug(self.msg('Unable to change state_target of credential marked for deletion'))
return
self.state_target = target
self.save()
if settings.DEBUG_CREDENTIALS:
LOGGER.debug(self.msg('State target changed to {}'.format(target)))
LOGGER.debug(self.msg('Target changed to {}'.format(target)))
def set(self, state):
if state == NOT_DEPLOYED and self._credential_deleted:
self._delete_state()
if state == self.state:
return
self.state = state
self.save()
if settings.DEBUG_CREDENTIALS:
LOGGER.debug(self.msg('State changed to {}'.format(state)))
def credential_deleted(self):
if self.state == NOT_DEPLOYED:
self._delete_state()
self.state_target = NOT_DEPLOYED
self._credential_deleted = True
self.save()
if settings.DEBUG_CREDENTIALS:
LOGGER.debug(self.msg('Credential marked as deleted'))
LOGGER.debug(self.msg('Marked as deleted'))
def _delete_state(self):
LOGGER.debug(self.msg('Deleted'))
self.delete()
def msg(self, message):
return self.target.msg('Credential {}: {}'.format(self.credential, message))
return '{} - {}'.format(self, message)
def __str__(self):
return '{}@{}'.format(
return 'CState: ({}:{})#{}'.format(
self.target.id,
self.credential,
self.target,
self.id,
)
......@@ -300,14 +300,15 @@ class User(AbstractUser):
try:
key = self._ssh_keys.get(name=unity_key_name)
# is the idp key still present?
if idp_key_name not in userinfo:
self.user_changed_key_changed(key)
key.delete_key()
self.user_changed_key_removed(key)
return True
# is the idp key changed?
if key.key != unity_key_value:
key.delete_key()
key = SSHPublicKey(
name=unity_key_name,
......@@ -366,27 +367,6 @@ class User(AbstractUser):
LOGGER.info('user_changed')
pass
def sanity_check(self):
for dep in self.deployments.all():
LOGGER.debug('User has a deployment %s with target %s', dep, dep.state_target)
for state_item in dep.state_items.all():
LOGGER.debug('User has a state item for %s', state_item.service)
for credential_state in state_item.credential_states.all():
if credential_state.credential is None:
LOGGER.debug('Credential state exists for null key -> we have to update the deployment so the according key is deleted')
else:
LOGGER.debug('User has a credential state for %s', credential_state.credential)
for ssh_key in self.ssh_keys.all():
try:
state_item.credential_states.get(
key=ssh_key,
)
except:
LOGGER.debug('ssh key %s has no credential_state! -> we possibly need to deploy this key', ssh_key)
# TODO implement all user_changed_ triggers
# The user_changed methods are triggerd in user_changed
# They serve to assure the correct state at the backend and clients
......@@ -395,7 +375,7 @@ class User(AbstractUser):
LOGGER.debug('user_changed_key_added: %s %s', self, key)
for dep in self.deployments.all():
LOGGER.debug('user_changed_key_added: need to add key to deployment %s', dep)
dep.user_credential_added(key)
def user_changed_key_changed(self, key):
LOGGER.debug('user_changed_key_changed: %s %s', self, key)
......@@ -406,9 +386,8 @@ class User(AbstractUser):
def user_changed_key_removed(self, key):
LOGGER.debug('user_changed_key_removed: %s %s', self, key)
for credential_state in key.credential_states.all():
LOGGER.debug('user_changed_key_removed: need to handle credential state %s', credential_state)
# delete accordingly
for dep in self.deployments.all():
dep.user_credential_removed(key)
def user_changed_group_added(self, group):
LOGGER.debug('user_changed_group_added: %s %s', self, group)
......
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