user.py 10.0 KB
Newer Older
baltery's avatar
baltery 已提交
1 2 3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
baltery's avatar
baltery 已提交
4
import uuid
baltery's avatar
baltery 已提交
5 6
from collections import OrderedDict

7
from django.conf import settings
baltery's avatar
baltery 已提交
8 9 10
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import AbstractUser
from django.core import signing
11
from django.db import models
baltery's avatar
baltery 已提交
12 13 14 15
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.shortcuts import reverse

baltery's avatar
baltery 已提交
16
from common.utils import get_signer, date_expired_default
baltery's avatar
baltery 已提交
17 18 19


__all__ = ['User']
baltery's avatar
baltery 已提交
20
signer = get_signer()
baltery's avatar
baltery 已提交
21 22 23


class User(AbstractUser):
baltery's avatar
baltery 已提交
24 25 26 27
    ROLE_ADMIN = 'Admin'
    ROLE_USER = 'User'
    ROLE_APP = 'App'

baltery's avatar
baltery 已提交
28
    ROLE_CHOICES = (
baltery's avatar
baltery 已提交
29 30 31
        (ROLE_ADMIN, _('Administrator')),
        (ROLE_USER, _('User')),
        (ROLE_APP, _('Application'))
baltery's avatar
baltery 已提交
32
    )
baltery's avatar
baltery 已提交
33 34 35 36 37
    OTP_LEVEL_CHOICES = (
        (0, _('Disable')),
        (1, _('Enable')),
        (2, _("Force enable")),
    )
baltery's avatar
baltery 已提交
38
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
baltery's avatar
baltery 已提交
39 40 41
    username = models.CharField(max_length=128, unique=True, verbose_name=_('Username'))
    name = models.CharField(max_length=128, verbose_name=_('Name'))
    email = models.EmailField(max_length=128, unique=True, verbose_name=_('Email'))
baltery's avatar
baltery 已提交
42
    groups = models.ManyToManyField('users.UserGroup', related_name='users', blank=True, verbose_name=_('User group'))
baltery's avatar
baltery 已提交
43
    role = models.CharField(choices=ROLE_CHOICES, default='User', max_length=10, blank=True, verbose_name=_('Role'))
baltery's avatar
baltery 已提交
44
    avatar = models.ImageField(upload_to="avatar", null=True, verbose_name=_('Avatar'))
baltery's avatar
baltery 已提交
45
    wechat = models.CharField(max_length=128, blank=True, verbose_name=_('Wechat'))
baltery's avatar
baltery 已提交
46
    phone = models.CharField(max_length=20, blank=True, null=True, verbose_name=_('Phone'))
baltery's avatar
baltery 已提交
47
    otp_level = models.SmallIntegerField(default=0, choices=OTP_LEVEL_CHOICES, verbose_name=_('Enable OTP'))
baltery's avatar
baltery 已提交
48
    otp_secret_key = models.CharField(max_length=16, blank=True, null=True)
49 50 51
    # Todo: Auto generate key, let user download
    _private_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Private key'))
    _public_key = models.CharField(max_length=5000, blank=True, verbose_name=_('Public key'))
baltery's avatar
baltery 已提交
52
    comment = models.TextField(max_length=200, blank=True, verbose_name=_('Comment'))
baltery's avatar
baltery 已提交
53
    is_first_login = models.BooleanField(default=True)
baltery's avatar
baltery 已提交
54
    date_expired = models.DateTimeField(default=date_expired_default, blank=True, null=True, verbose_name=_('Date expired'))
baltery's avatar
baltery 已提交
55 56
    created_by = models.CharField(max_length=30, default='', verbose_name=_('Created by'))

baltery's avatar
baltery 已提交
57
    def __str__(self):
58
        return '{0.name}({0.username})'.format(self)
baltery's avatar
baltery 已提交
59

baltery's avatar
baltery 已提交
60 61
    @property
    def password_raw(self):
baltery's avatar
baltery 已提交
62
        raise AttributeError('Password raw is not a readable attribute')
baltery's avatar
baltery 已提交
63 64 65 66 67 68 69 70 71 72 73 74 75

    #: Use this attr to set user object password, example
    #: user = User(username='example', password_raw='password', ...)
    #: It's equal:
    #: user = User(username='example', ...)
    #: user.set_password('password')
    @password_raw.setter
    def password_raw(self, password_raw_):
        self.set_password(password_raw_)

    def get_absolute_url(self):
        return reverse('users:user-detail', args=(self.id,))

76 77 78 79 80 81 82 83 84
    def is_public_key_valid(self):
        """
            Check if the user's ssh public key is valid.
            This function is used in base.html.
        """
        if self._public_key:
            return True
        return False

baltery's avatar
baltery 已提交
85 86
    @property
    def is_expired(self):
baltery's avatar
baltery 已提交
87
        if self.date_expired and self.date_expired < timezone.now():
baltery's avatar
baltery 已提交
88
            return True
baltery's avatar
baltery 已提交
89 90
        else:
            return False
baltery's avatar
baltery 已提交
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113

    @property
    def is_valid(self):
        if self.is_active and not self.is_expired:
            return True
        return False

    @property
    def private_key(self):
        return signer.unsign(self._private_key)

    @private_key.setter
    def private_key(self, private_key_raw):
        self._private_key = signer.sign(private_key_raw)

    @property
    def public_key(self):
        return signer.unsign(self._public_key)

    @public_key.setter
    def public_key(self, public_key_raw):
        self._public_key = signer.sign(public_key_raw)

baltery's avatar
baltery 已提交
114
    @property
baltery's avatar
baltery 已提交
115 116 117 118
    def public_key_obj(self):
        class PubKey(object):
            def __getattr__(self, item):
                return ''
baltery's avatar
baltery 已提交
119 120 121
        if self.public_key:
            import sshpubkeys
            try:
baltery's avatar
baltery 已提交
122
                return sshpubkeys.SSHKey(self.public_key)
123
            except (TabError, TypeError):
baltery's avatar
baltery 已提交
124
                pass
baltery's avatar
baltery 已提交
125
        return PubKey()
baltery's avatar
baltery 已提交
126

baltery's avatar
baltery 已提交
127 128 129 130 131 132 133 134 135 136 137 138 139 140
    @property
    def is_superuser(self):
        if self.role == 'Admin':
            return True
        else:
            return False

    @is_superuser.setter
    def is_superuser(self, value):
        if value is True:
            self.role = 'Admin'
        else:
            self.role = 'User'

141 142 143 144
    @property
    def is_app(self):
        return self.role == 'App'

baltery's avatar
baltery 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157 158
    @property
    def is_staff(self):
        if self.is_authenticated and self.is_valid:
            return True
        else:
            return False

    @is_staff.setter
    def is_staff(self, value):
        pass

    def save(self, *args, **kwargs):
        if not self.name:
            self.name = self.username
baltery's avatar
baltery 已提交
159 160 161 162
        if self.username == 'admin':
            self.role = 'Admin'
            self.is_active = True

baltery's avatar
baltery 已提交
163
        super().save(*args, **kwargs)
baltery's avatar
baltery 已提交
164 165 166

    @property
    def private_token(self):
167
        return self.create_private_token()
baltery's avatar
baltery 已提交
168

169 170
    def create_private_token(self):
        from .authentication import PrivateToken
baltery's avatar
baltery 已提交
171
        try:
172 173 174
            token = PrivateToken.objects.get(user=self)
        except PrivateToken.DoesNotExist:
            token = PrivateToken.objects.create(user=self)
baltery's avatar
baltery 已提交
175 176
        return token.key

baltery's avatar
baltery 已提交
177 178 179 180 181
    def create_access_key(self):
        from . import AccessKey
        access_key = AccessKey.objects.create(user=self)
        return access_key

baltery's avatar
baltery 已提交
182
    def refresh_private_token(self):
183 184 185
        from .authentication import PrivateToken
        PrivateToken.objects.filter(user=self).delete()
        return PrivateToken.objects.create(user=self)
baltery's avatar
baltery 已提交
186 187 188 189 190 191 192

    def is_member_of(self, user_group):
        if user_group in self.groups.all():
            return True
        return False

    def check_public_key(self, public_key):
baltery's avatar
baltery 已提交
193
        if self.ssH_public_key == public_key:
baltery's avatar
baltery 已提交
194 195 196
            return True
        return False

197
    def avatar_url(self):
baltery's avatar
baltery 已提交
198 199
        admin_default = settings.STATIC_URL + "img/avatar/admin.png"
        user_default = settings.STATIC_URL + "img/avatar/user.png"
200 201
        if self.avatar:
            return self.avatar.url
baltery's avatar
baltery 已提交
202 203
        if self.is_superuser:
            return admin_default
204
        else:
baltery's avatar
baltery 已提交
205
            return user_default
206

baltery's avatar
baltery 已提交
207
    def generate_reset_token(self):
baltery's avatar
baltery 已提交
208
        return signer.sign_t({'reset': str(self.id), 'email': self.email}, expires_in=3600)
baltery's avatar
baltery 已提交
209

baltery's avatar
baltery 已提交
210 211 212 213
    @property
    def otp_enabled(self):
        return self.otp_level > 0

baltery's avatar
baltery 已提交
214 215 216 217 218 219
    def enabled_otp(self):
        self.otp_level = 1

    def force_enable_otp(self):
        self.otp_level = 2

baltery's avatar
baltery 已提交
220 221 222 223
    @property
    def otp_force_enabled(self):
        return self.otp_level == 2

baltery's avatar
baltery 已提交
224 225 226 227 228 229 230 231 232 233 234 235 236
    def to_json(self):
        return OrderedDict({
            'id': self.id,
            'username': self.username,
            'name': self.name,
            'email': self.email,
            'is_active': self.is_active,
            'is_superuser': self.is_superuser,
            'role': self.get_role_display(),
            'groups': [group.name for group in self.groups.all()],
            'wechat': self.wechat,
            'phone': self.phone,
            'comment': self.comment,
237
            'date_expired': self.date_expired.strftime('%Y-%m-%d %H:%M:%S') if self.date_expired is not None else None
baltery's avatar
baltery 已提交
238 239
        })

240 241
    @classmethod
    def create_app_user(cls, name, comment):
baltery's avatar
baltery 已提交
242
        app = cls.objects.create(
baltery's avatar
baltery 已提交
243
            username=name, name=name, email='{}@local.domain'.format(name),
baltery's avatar
baltery 已提交
244
            is_active=False, role='App', comment=comment,
baltery's avatar
baltery 已提交
245 246
            is_first_login=False, created_by='System'
        )
baltery's avatar
baltery 已提交
247
        access_key = app.create_access_key()
baltery's avatar
baltery 已提交
248
        return app, access_key
249

baltery's avatar
baltery 已提交
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
    @classmethod
    def validate_reset_token(cls, token):
        try:
            data = signer.unsign_t(token)
            user_id = data.get('reset', None)
            user_email = data.get('email', '')
            user = cls.objects.get(id=user_id, email=user_email)

        except (signing.BadSignature, cls.DoesNotExist):
            user = None
        return user

    def reset_password(self, new_password):
        self.set_password(new_password)
        self.save()

baltery's avatar
baltery 已提交
266
    def delete(self, using=None, keep_parents=False):
baltery's avatar
baltery 已提交
267 268 269 270
        if self.pk == 1 or self.username == 'admin':
            return
        return super(User, self).delete()

baltery's avatar
baltery 已提交
271
    class Meta:
baltery's avatar
baltery 已提交
272
        ordering = ['username']
baltery's avatar
baltery 已提交
273
        verbose_name = _("User")
baltery's avatar
baltery 已提交
274 275 276 277

    #: Use this method initial user
    @classmethod
    def initial(cls):
baltery's avatar
baltery 已提交
278
        from .group import UserGroup
baltery's avatar
baltery 已提交
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
        user = cls(username='admin',
                   email='admin@jumpserver.org',
                   name=_('Administrator'),
                   password_raw='admin',
                   role='Admin',
                   comment=_('Administrator is the super user of system'),
                   created_by=_('System'))
        user.save()
        user.groups.add(UserGroup.initial())

    @classmethod
    def generate_fake(cls, count=100):
        from random import seed, choice
        import forgery_py
        from django.db import IntegrityError
baltery's avatar
baltery 已提交
294
        from .group import UserGroup
baltery's avatar
baltery 已提交
295 296 297 298 299 300 301

        seed()
        for i in range(count):
            user = cls(username=forgery_py.internet.user_name(True),
                       email=forgery_py.internet.email_address(),
                       name=forgery_py.name.full_name(),
                       password=make_password(forgery_py.lorem_ipsum.word()),
baltery's avatar
baltery 已提交
302
                       role=choice(list(dict(User.ROLE_CHOICES).keys())),
baltery's avatar
baltery 已提交
303 304 305 306 307 308 309 310 311 312
                       wechat=forgery_py.internet.user_name(True),
                       comment=forgery_py.lorem_ipsum.sentence(),
                       created_by=choice(cls.objects.all()).username)
            try:
                user.save()
            except IntegrityError:
                print('Duplicate Error, continue ...')
                continue
            user.groups.add(choice(UserGroup.objects.all()))
            user.save()