Source code for maasserver.models.sshkey
# Copyright 2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
""":class:`SSHKey` and friends."""
from __future__ import (
absolute_import,
print_function,
unicode_literals,
)
str = None
__metaclass__ = type
__all__ = [
'SSHKey',
]
from cgi import escape
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.db.models import (
ForeignKey,
Manager,
TextField,
)
from django.utils.safestring import mark_safe
from maasserver import (
DefaultMeta,
logger,
)
from maasserver.models.cleansave import CleanSave
from maasserver.models.timestampedmodel import TimestampedModel
from twisted.conch.ssh.keys import Key
class SSHKeyManager(Manager):
"""A utility to manage the colletion of `SSHKey`s."""
def get_keys_for_user(self, user):
"""Return the text of the ssh keys associated with a user."""
return SSHKey.objects.filter(user=user).values_list('key', flat=True)
def validate_ssh_public_key(value):
"""Validate that the given value contains a valid SSH public key."""
try:
key = Key.fromString(value)
except Exception:
# twisted.conch.ssh.keys.Key.fromString raises all sorts of exceptions.
# Here, we catch them all and return a ValidationError since this
# method only aims at validating keys and not return the exact cause of
# the failure.
logger.exception("Invalid SSH public key")
raise ValidationError("Invalid SSH public key.")
else:
if not key.isPublic():
raise ValidationError(
"Invalid SSH public key (this key is a private key).")
HELLIPSIS = '…'
def get_html_display_for_key(key, size):
"""Return a compact HTML representation of this key with a boundary on
the size of the resulting string.
A key typically looks like this: 'key_type key_string comment'.
What we want here is display the key_type and, if possible (i.e. if it
fits in the boundary that `size` gives us), the comment. If possible we
also want to display a truncated key_string. If the comment is too big
to fit in, we simply display a cropped version of the whole string.
:param key: The key for which we want an HTML representation.
:type name: unicode
:param size: The maximum size of the representation. This may not be
met exactly.
:type size: int
:return: The HTML representation of this key.
:rtype: unicode
"""
key = key.strip()
key_parts = key.split(' ', 2)
if len(key_parts) == 3:
key_type = key_parts[0]
key_string = key_parts[1]
comment = key_parts[2]
room_for_key = (
size - (len(key_type) + len(comment) + len(HELLIPSIS) + 2))
if room_for_key > 0:
return '%s %.*s%s %s' % (
escape(key_type, quote=True),
room_for_key,
escape(key_string, quote=True),
HELLIPSIS,
escape(comment, quote=True))
if len(key) > size:
return '%.*s%s' % (
size - len(HELLIPSIS),
escape(key, quote=True),
HELLIPSIS)
else:
return escape(key, quote=True)
MAX_KEY_DISPLAY = 50
[docs]class SSHKey(CleanSave, TimestampedModel):
"""An `SSHKey` represents a user public SSH key.
Users will be able to access allocated nodes using any of their
registered keys.
:ivar user: The user which owns the key.
:ivar key: The SSH public key.
"""
objects = SSHKeyManager()
user = ForeignKey(User, null=False, editable=False)
key = TextField(
null=False, editable=True, validators=[validate_ssh_public_key])
class Meta(DefaultMeta):
verbose_name = "SSH key"
unique_together = ('user', 'key')
def unique_error_message(self, model_class, unique_check):
if unique_check == ('user', 'key'):
return "This key has already been added for this user."
return super(
SSHKey, self).unique_error_message(model_class, unique_check)
def __unicode__(self):
return self.key
[docs] def display_html(self):
"""Return a compact HTML representation of this key.
:return: The HTML representation of this key.
:rtype: unicode
"""
return mark_safe(get_html_display_for_key(self.key, MAX_KEY_DISPLAY))