models.py 5.25 KB
Newer Older
1 2
# Copyright (c) 2014, Clemson University
# All rights reserved.
Scott Duckworth committed
3
#
4 5
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
Scott Duckworth committed
6
#
7 8
# * Redistributions of source code must retain the above copyright notice, this
#   list of conditions and the following disclaimer.
Scott Duckworth committed
9
#
10 11 12
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
Scott Duckworth committed
13
#
14 15 16 17 18 19 20 21 22 23 24 25 26 27
# * Neither the name of the {organization} nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Scott Duckworth committed
28

29 30
from django.db import models
from django.contrib.auth.models import User
31
from django.core.exceptions import ValidationError
32 33
from django.db.models.signals import pre_save
from django.dispatch import receiver
34 35 36 37 38
try:
  from django.utils.timezone import now
except ImportError:
  import datetime
  now = datetime.datetime.now
39
from django_sshkey.util import PublicKeyParseError, pubkey_parse
40
from django_sshkey import settings
41

42 43
class UserKey(models.Model):
  user = models.ForeignKey(User, db_index=True)
44
  name = models.CharField(max_length=50, blank=True)
45
  key = models.TextField(max_length=2000)
46
  fingerprint = models.CharField(max_length=47, blank=True, db_index=True)
47
  created = models.DateTimeField(auto_now_add=True, null=True)
48
  last_modified = models.DateTimeField(null=True)
49
  last_used = models.DateTimeField(null=True)
50 51

  class Meta:
52
    db_table = 'sshkey_userkey'
53 54 55 56 57 58 59
    unique_together = [
      ('user', 'name'),
    ]

  def __unicode__(self):
    return unicode(self.user) + u': ' + self.name

60 61 62 63
  def clean_fields(self, exclude=None):
    if not exclude or 'key' not in exclude:
      self.key = self.key.strip()

64 65
  def clean(self):
    try:
66
      pubkey = pubkey_parse(self.key)
67
    except PublicKeyParseError as e:
68
      raise ValidationError(str(e))
69 70
    self.key = pubkey.format_openssh()
    self.fingerprint = pubkey.fingerprint()
71
    if not self.name:
72
      if not pubkey.comment:
73
        raise ValidationError('Name or key comment required')
74
      self.name = pubkey.comment
75 76 77 78 79 80 81 82

  def validate_unique(self, exclude=None):
    if self.pk is None:
      objects = type(self).objects
    else:
      objects = type(self).objects.exclude(pk=self.pk)
    if exclude is None or 'name' not in exclude:
      if objects.filter(user=self.user, name=self.name).count():
83 84
        message = 'You already have a key with that name'
        raise ValidationError({'name': [message]})
85 86 87 88 89 90 91 92 93 94
    if exclude is None or 'key' not in exclude:
      try:
        other = objects.get(fingerprint=self.fingerprint, key=self.key)
        if self.user == other.user:
          message = 'You already have that key on file (%s)' % other.name
        else:
          message = 'Somebody else already has that key on file'
        raise ValidationError({'key': [message]})
      except type(self).DoesNotExist:
        pass
95

96
  def export(self, format='RFC4716'):
97
    pubkey = pubkey_parse(self.key)
98 99 100 101 102 103
    f = format.upper()
    if f == 'RFC4716':
      return pubkey.format_rfc4716()
    if f == 'PEM':
      return pubkey.format_pem()
    raise ValueError("Invalid format")
104

105 106
  def save(self, *args, **kwargs):
    if kwargs.pop('update_last_modified', True):
107
      self.last_modified = now()
108 109
    super(UserKey, self).save(*args, **kwargs)

110
  def touch(self):
111
    self.last_used = now()
112
    self.save(update_last_modified=False)
113 114 115 116 117

@receiver(pre_save, sender=UserKey)
def send_email_add_key(sender, instance, **kwargs):
  if not settings.SSHKEY_EMAIL_ADD_KEY or instance.pk:
    return
118 119 120 121 122 123 124 125 126 127 128 129 130
  from django.template.loader import render_to_string
  from django.core.mail import EmailMultiAlternatives
  from django.core.urlresolvers import reverse
  context_dict = {
    'key': instance,
    'subject': settings.SSHKEY_EMAIL_ADD_KEY_SUBJECT,
  }
  request = getattr(instance, 'request', None)
  if request:
    context_dict['request'] = request
    context_dict['userkey_list_uri'] = request.build_absolute_uri(reverse('django_sshkey.views.userkey_list'))
  text_content = render_to_string('sshkey/add_key.txt', context_dict)
  msg = EmailMultiAlternatives(
131
    settings.SSHKEY_EMAIL_ADD_KEY_SUBJECT,
132
    text_content,
133
    settings.SSHKEY_FROM_EMAIL,
134
    [instance.user.email],
135
  )
136 137 138 139
  if settings.SSHKEY_SEND_HTML_EMAIL:
    html_content = render_to_string('sshkey/add_key.html', context_dict)
    msg.attach_alternative(html_content, 'text/html')
  msg.send()