implement SshPublicKey model, manager and tests

- implement osusers.models.SshPublicKey and osusers.models.SshPublicKeyManager
- fix broken osusers.models.tests.test_models
- add new test classes SshPublicKeyManagerTest and SshPublicKeyTest
- add migration for SshPublicKey model
This commit is contained in:
Jan Dittberner 2015-01-31 21:48:07 +01:00
parent 9fa351f801
commit 20359681db
3 changed files with 393 additions and 140 deletions

View file

@ -4,9 +4,11 @@ This module defines the database models of operating system users.
"""
from __future__ import unicode_literals
import base64
from datetime import date
import logging
import os
import six
from django.db import models, transaction
from django.conf import settings
@ -477,3 +479,103 @@ class AdditionalGroup(TimeStampedModel, models.Model):
'remove_ldap_user_from_group'
)
super(AdditionalGroup, self).delete(*args, **kwargs)
class SshPublicKeyManager(models.Manager):
"""
Default manager for :py:class:`SSH public key
<osusers.models.SshPublicKey>` instances.
"""
def parse_keytext(self, keytext):
"""
Parse a SSH public key in OpenSSH or :rfc:`4716` format into its
components algorithm, key data and comment.
:param str keytext: key text
:return: triple of algorithm name, key data and comment
:rtype: triple of str
"""
if keytext.startswith('---- BEGIN SSH2 PUBLIC KEY ----'):
comment = ''
data = ''
continued = ''
headers = {}
for line in keytext.splitlines():
if line == '---- BEGIN SSH2 PUBLIC KEY ----':
continue
elif ':' in line: # a header line
header_tag, header_value = [
item.strip() for item in line.split(':', 1)]
if header_value.endswith('\\'):
continued = header_value[:-1]
else:
headers[header_tag.lower()] = header_value
elif continued:
if line.endswith('\\'):
continued += line[:-1]
continue
header_value = continued + line
headers[header_tag.lower()] = header_value
continued = ''
elif line == '---- END SSH2 PUBLIC KEY ----':
break
elif line: # ignore empty lines
data += line
if 'comment' in headers:
comment = headers['comment']
else:
parts = keytext.split()
if len(parts) > 3:
raise ValueError("unsupported key format")
data = parts[1]
comment = len(parts) == 3 and parts[2] or ""
keybytes = base64.b64decode(data)
parts = keybytes.split(b'\x00' * 3)
alglength = six.byte2int(parts[1])
algname = parts[1][1:1+alglength]
return algname, data, comment
def create_ssh_public_key(self, user, keytext):
"""
Create a new :py:class:`SSH public key <osusers.models.SshPublicKey>`
for a user from the given key text representation. The text can be
either in openssh format or :rfc:`4716` format.
:param user: :py:class:`operating system user <osusers.models.User>`
:param str keytext: key text
:return: public key
:retype: :py:class:`osusers.models.SshPublicKey`
"""
algorithm, data, comment = self.parse_keytext(keytext)
return self.create(
user=user, algorithm=algorithm, data=data, comment=comment)
@python_2_unicode_compatible
class SshPublicKey(TimeStampedModel):
"""
This entity class represents single SSH keys for an :py:class:`operating
system user <osusers.models.User>`.
"""
user = models.ForeignKey(User, verbose_name=_('User'))
algorithm = models.CharField(_('Algorithm'), max_length=20)
data = models.TextField(_('Key bytes'),
help_text=_('Base64 encoded key bytes'))
comment = models.TextField(_('Comment'), blank=True)
objects = SshPublicKeyManager()
class Meta:
verbose_name = _('SSH public key')
verbose_name_plural = _('SSH public keys')
unique_together = [('user', 'algorithm', 'data')]
def __str__(self):
return "{algorithm} {data} {comment}".format(
algorithm=self.algorithm, data=self.data, comment=self.comment
).strip()