Esempio n. 1
0
class BaseUser(OrgModelMixin, AuthMixin, ConnectivityMixin):
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
    name = models.CharField(max_length=128, verbose_name=_('Name'))
    username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), validators=[alphanumeric], db_index=True)
    password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
    private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
    public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
    comment = models.TextField(blank=True, verbose_name=_('Comment'))
    date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
    date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
    created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))

    ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
    ASSET_USER_CACHE_TIME = 600

    _prefer = "system_user"

    def get_related_assets(self):
        assets = self.assets.filter(org_id=self.org_id)
        return assets

    def get_username(self):
        return self.username

    @lazyproperty
    def assets_amount(self):
        cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
        cached = cache.get(cache_key)
        if not cached:
            cached = self.get_related_assets().count()
            cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME)
        return cached

    def expire_assets_amount(self):
        cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
        cache.delete(cache_key)

    def _to_secret_json(self):
        """Push system user use it"""
        return {
            'name': self.name,
            'username': self.username,
            'password': self.password,
            'public_key': self.public_key,
            'private_key': self.private_key_file,
        }

    class Meta:
        abstract = True
Esempio n. 2
0
class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
    SOURCE_LOCAL = 'local'
    SOURCE_LDAP = 'ldap'
    SOURCE_OPENID = 'openid'
    SOURCE_RADIUS = 'radius'
    SOURCE_CAS = 'cas'
    SOURCE_CHOICES = (
        (SOURCE_LOCAL, _('Local')),
        (SOURCE_LDAP, 'LDAP/AD'),
        (SOURCE_OPENID, 'OpenID'),
        (SOURCE_RADIUS, 'Radius'),
        (SOURCE_CAS, 'CAS'),
    )

    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
    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'))
    groups = models.ManyToManyField('users.UserGroup',
                                    related_name='users',
                                    blank=True,
                                    verbose_name=_('User group'))
    role = models.CharField(choices=RoleMixin.ROLE_CHOICES,
                            default='User',
                            max_length=10,
                            blank=True,
                            verbose_name=_('Role'))
    avatar = models.ImageField(upload_to="avatar",
                               null=True,
                               verbose_name=_('Avatar'))
    wechat = models.CharField(max_length=128,
                              blank=True,
                              verbose_name=_('Wechat'))
    phone = models.CharField(max_length=20,
                             blank=True,
                             null=True,
                             verbose_name=_('Phone'))
    mfa_level = models.SmallIntegerField(default=0,
                                         choices=MFAMixin.MFA_LEVEL_CHOICES,
                                         verbose_name=_('MFA'))
    otp_secret_key = fields.EncryptCharField(max_length=128,
                                             blank=True,
                                             null=True)
    # Todo: Auto generate key, let user download
    private_key = fields.EncryptTextField(blank=True,
                                          null=True,
                                          verbose_name=_('Private key'))
    public_key = fields.EncryptTextField(blank=True,
                                         null=True,
                                         verbose_name=_('Public key'))
    comment = models.TextField(blank=True,
                               null=True,
                               verbose_name=_('Comment'))
    is_first_login = models.BooleanField(default=True)
    date_expired = models.DateTimeField(default=date_expired_default,
                                        blank=True,
                                        null=True,
                                        db_index=True,
                                        verbose_name=_('Date expired'))
    created_by = models.CharField(max_length=30,
                                  default='',
                                  blank=True,
                                  verbose_name=_('Created by'))
    source = models.CharField(max_length=30,
                              default=SOURCE_LOCAL,
                              choices=SOURCE_CHOICES,
                              verbose_name=_('Source'))
    date_password_last_updated = models.DateTimeField(
        auto_now_add=True,
        blank=True,
        null=True,
        verbose_name=_('Date password last updated'))

    user_cache_key_prefix = '_User_{}'

    def __str__(self):
        return '{0.name}({0.username})'.format(self)

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

    @property
    def groups_display(self):
        return ' '.join([group.name for group in self.groups.all()])

    @property
    def source_display(self):
        return self.get_source_display()

    @property
    def is_expired(self):
        if self.date_expired and self.date_expired < timezone.now():
            return True
        else:
            return False

    @property
    def expired_remain_days(self):
        date_remain = self.date_expired - timezone.now()
        return date_remain.days

    @property
    def will_expired(self):
        if 0 <= self.expired_remain_days < 5:
            return True
        else:
            return False

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

    @property
    def is_local(self):
        return self.source == self.SOURCE_LOCAL

    def set_unprovide_attr_if_need(self):
        if not self.name:
            self.name = self.username
        if not self.email or '@' not in self.email:
            email = '{}@{}'.format(self.username, settings.EMAIL_SUFFIX)
            if '@' in self.username:
                email = self.username
            self.email = email

    def save(self, *args, **kwargs):
        self.set_unprovide_attr_if_need()
        if self.username == 'admin':
            self.role = 'Admin'
            self.is_active = True
        super().save(*args, **kwargs)

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

    def set_avatar(self, f):
        self.avatar.save(self.username, f)

    @classmethod
    def get_avatar_url(cls, username):
        user_default = settings.STATIC_URL + "img/avatar/user.png"
        return user_default

    # def admin_orgs(self):
    #     from orgs.models import Organization
    #     orgs = Organization.get_user_admin_or_audit_orgs(self)
    #     return orgs

    def avatar_url(self):
        admin_default = settings.STATIC_URL + "img/avatar/admin.png"
        user_default = settings.STATIC_URL + "img/avatar/user.png"
        if self.avatar:
            return self.avatar.url
        if self.is_superuser:
            return admin_default
        else:
            return user_default

    def delete(self, using=None, keep_parents=False):
        if self.pk == 1 or self.username == 'admin':
            return
        return super(User, self).delete()

    class Meta:
        ordering = ['username']
        verbose_name = _("User")

    #: Use this method initial user
    @classmethod
    def initial(cls):
        from .group import UserGroup
        user = cls(username='******',
                   email='*****@*****.**',
                   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())

    def can_send_created_mail(self):
        if self.email and self.source == self.SOURCE_LOCAL:
            return True
        return False

    @classmethod
    def generate_fake(cls, count=100):
        from random import seed, choice
        import forgery_py
        from django.db import IntegrityError
        from .group import UserGroup

        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()),
                       role=choice(list(dict(User.ROLE_CHOICES).keys())),
                       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()
Esempio n. 3
0
class AssetUser(OrgModelMixin):
    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
    name = models.CharField(max_length=128, verbose_name=_('Name'))
    username = models.CharField(max_length=32, blank=True, verbose_name=_('Username'), validators=[alphanumeric], db_index=True)
    password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
    private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
    public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
    comment = models.TextField(blank=True, verbose_name=_('Comment'))
    date_created = models.DateTimeField(auto_now_add=True, verbose_name=_("Date created"))
    date_updated = models.DateTimeField(auto_now=True, verbose_name=_("Date updated"))
    created_by = models.CharField(max_length=128, null=True, verbose_name=_('Created by'))

    CONNECTIVITY_ASSET_CACHE_KEY = "ASSET_USER_{}_{}_ASSET_CONNECTIVITY"
    CONNECTIVITY_AMOUNT_CACHE_KEY = "ASSET_USER_{}_{}_CONNECTIVITY_AMOUNT"
    ASSETS_AMOUNT_CACHE_KEY = "ASSET_USER_{}_ASSETS_AMOUNT"
    ASSET_USER_CACHE_TIME = 3600 * 24

    _prefer = "system_user"

    @property
    def private_key_obj(self):
        if self.private_key:
            return ssh_key_string_to_obj(self.private_key, password=self.password)
        else:
            return None

    @property
    def private_key_file(self):
        if not self.private_key_obj:
            return None
        project_dir = settings.PROJECT_DIR
        tmp_dir = os.path.join(project_dir, 'tmp')
        key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest()
        key_path = os.path.join(tmp_dir, key_name)
        if not os.path.exists(key_path):
            self.private_key_obj.write_private_key_file(key_path)
            os.chmod(key_path, 0o400)
        return key_path

    @property
    def public_key_obj(self):
        if self.public_key:
            try:
                return sshpubkeys.SSHKey(self.public_key)
            except TabError:
                pass
        return None

    @property
    def part_id(self):
        i = '-'.join(str(self.id).split('-')[:3])
        return i

    def get_related_assets(self):
        assets = self.assets.all()
        return assets

    def set_auth(self, password=None, private_key=None, public_key=None):
        update_fields = []
        if password:
            self.password = password
            update_fields.append('password')
        if private_key:
            self.private_key = private_key
            update_fields.append('private_key')
        if public_key:
            self.public_key = public_key
            update_fields.append('public_key')

        if update_fields:
            self.save(update_fields=update_fields)

    def set_connectivity(self, summary):
        unreachable = summary.get('dark', {}).keys()
        reachable = summary.get('contacted', {}).keys()

        assets = self.get_related_assets()
        if not isinstance(assets, list):
            assets = assets.only('id', 'hostname', 'admin_user__id')
        for asset in assets:
            if asset.hostname in unreachable:
                self.set_asset_connectivity(asset, Connectivity.unreachable())
            elif asset.hostname in reachable:
                self.set_asset_connectivity(asset, Connectivity.reachable())
            else:
                self.set_asset_connectivity(asset, Connectivity.unknown())
        cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
        cache.delete(cache_key)

    @property
    def connectivity(self):
        assets = self.get_related_assets()
        if not isinstance(assets, list):
            assets = assets.only('id', 'hostname', 'admin_user__id')
        data = {
            'unreachable': [],
            'reachable': [],
            'unknown': [],
        }
        for asset in assets:
            connectivity = self.get_asset_connectivity(asset)
            if connectivity.is_reachable():
                data["reachable"].append(asset.hostname)
            elif connectivity.is_unreachable():
                data["unreachable"].append(asset.hostname)
            else:
                data["unknown"].append(asset.hostname)
        return data

    @property
    def connectivity_amount(self):
        cache_key = self.CONNECTIVITY_AMOUNT_CACHE_KEY.format(self.username, self.part_id)
        amount = cache.get(cache_key)
        if not amount:
            amount = {k: len(v) for k, v in self.connectivity.items()}
            cache.set(cache_key, amount, self.ASSET_USER_CACHE_TIME)
        return amount

    @property
    def assets_amount(self):
        cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
        cached = cache.get(cache_key)
        if not cached:
            cached = self.get_related_assets().count()
            cache.set(cache_key, cached, self.ASSET_USER_CACHE_TIME)
        return cached

    def expire_assets_amount(self):
        cache_key = self.ASSETS_AMOUNT_CACHE_KEY.format(self.id)
        cache.delete(cache_key)

    def get_asset_connectivity(self, asset):
        key = self.get_asset_connectivity_key(asset)
        return Connectivity.get(key)

    def get_asset_connectivity_key(self, asset):
        return self.CONNECTIVITY_ASSET_CACHE_KEY.format(self.username, asset.id)

    def set_asset_connectivity(self, asset, c):
        key = self.get_asset_connectivity_key(asset)
        Connectivity.set(key, c)

    def get_asset_user(self, asset):
        from ..backends import AssetUserManager
        try:
            manager = AssetUserManager().prefer(self._prefer)
            other = manager.get(username=self.username, asset=asset, prefer_id=self.id)
            return other
        except Exception as e:
            logger.error(e, exc_info=True)
            return None

    def load_specific_asset_auth(self, asset):
        instance = self.get_asset_user(asset)
        if instance:
            self._merge_auth(instance)

    def _merge_auth(self, other):
        if other.password:
            self.password = other.password
        if other.public_key:
            self.public_key = other.public_key
        if other.private_key:
            self.private_key = other.private_key

    def clear_auth(self):
        self.password = ''
        self.private_key = ''
        self.public_key = ''
        self.save()

    @staticmethod
    def gen_password():
        return str(uuid.uuid4())

    @staticmethod
    def gen_key(username):
        private_key, public_key = ssh_key_gen(
            username=username
        )
        return private_key, public_key

    def auto_gen_auth(self):
        password = str(uuid.uuid4())
        private_key, public_key = ssh_key_gen(
            username=self.username
        )
        self.set_auth(
            password=password, private_key=private_key,
            public_key=public_key
        )

    def auto_gen_auth_password(self):
        password = str(uuid.uuid4())
        self.set_auth(password=password)

    def _to_secret_json(self):
        """Push system user use it"""
        return {
            'name': self.name,
            'username': self.username,
            'password': self.password,
            'public_key': self.public_key,
            'private_key': self.private_key_file,
        }

    def generate_id_with_asset(self, asset):
        user_id = [self.part_id]
        asset_id = str(asset.id).split('-')[3:]
        ids = user_id + asset_id
        return '-'.join(ids)

    def construct_to_authbook(self, asset):
        from . import AuthBook
        fields = [
            'name', 'username', 'comment', 'org_id',
            'password', 'private_key', 'public_key',
            'date_created', 'date_updated', 'created_by'
        ]
        i = self.generate_id_with_asset(asset)
        obj = AuthBook(id=i, asset=asset, version=0, is_latest=True)
        for field in fields:
            value = getattr(self, field)
            setattr(obj, field, value)
        return obj

    class Meta:
        abstract = True
Esempio n. 4
0
class User(AbstractUser):
    ROLE_ADMIN = 'Admin'
    ROLE_USER = '******'
    ROLE_APP = 'App'
    ROLE_AUDITOR = 'Auditor'

    ROLE_CHOICES = ((ROLE_ADMIN, _('Administrator')), (ROLE_USER, _('User')),
                    (ROLE_APP, _('Application')), (ROLE_AUDITOR, _("Auditor")))
    OTP_LEVEL_CHOICES = (
        (0, _('Disable')),
        (1, _('Enable')),
        (2, _("Force enable")),
    )
    SOURCE_LOCAL = 'local'
    SOURCE_LDAP = 'ldap'
    SOURCE_OPENID = 'openid'
    SOURCE_RADIUS = 'radius'
    SOURCE_CHOICES = (
        (SOURCE_LOCAL, 'Local'),
        (SOURCE_LDAP, 'LDAP/AD'),
        (SOURCE_OPENID, 'OpenID'),
        (SOURCE_RADIUS, 'Radius'),
    )

    CACHE_KEY_USER_RESET_PASSWORD_PREFIX = "_KEY_USER_RESET_PASSWORD_{}"

    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
    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'))
    groups = models.ManyToManyField('users.UserGroup',
                                    related_name='users',
                                    blank=True,
                                    verbose_name=_('User group'))
    role = models.CharField(choices=ROLE_CHOICES,
                            default='User',
                            max_length=10,
                            blank=True,
                            verbose_name=_('Role'))
    avatar = models.ImageField(upload_to="avatar",
                               null=True,
                               verbose_name=_('Avatar'))
    wechat = models.CharField(max_length=128,
                              blank=True,
                              verbose_name=_('Wechat'))
    phone = models.CharField(max_length=20,
                             blank=True,
                             null=True,
                             verbose_name=_('Phone'))
    otp_level = models.SmallIntegerField(default=0,
                                         choices=OTP_LEVEL_CHOICES,
                                         verbose_name=_('MFA'))
    otp_secret_key = fields.EncryptCharField(max_length=128,
                                             blank=True,
                                             null=True)
    # Todo: Auto generate key, let user download
    private_key = fields.EncryptTextField(blank=True,
                                          null=True,
                                          verbose_name=_('Private key'))
    public_key = fields.EncryptTextField(blank=True,
                                         null=True,
                                         verbose_name=_('Public key'))
    comment = models.TextField(blank=True,
                               null=True,
                               verbose_name=_('Comment'))
    is_first_login = models.BooleanField(default=True)
    date_expired = models.DateTimeField(default=date_expired_default,
                                        blank=True,
                                        null=True,
                                        db_index=True,
                                        verbose_name=_('Date expired'))
    created_by = models.CharField(max_length=30,
                                  default='',
                                  verbose_name=_('Created by'))
    source = models.CharField(max_length=30,
                              default=SOURCE_LOCAL,
                              choices=SOURCE_CHOICES,
                              verbose_name=_('Source'))
    date_password_last_updated = models.DateTimeField(
        auto_now_add=True,
        blank=True,
        null=True,
        verbose_name=_('Date password last updated'))

    user_cache_key_prefix = '_User_{}'

    def __str__(self):
        return '{0.name}({0.username})'.format(self)

    @property
    def password_raw(self):
        raise AttributeError('Password raw is not a readable attribute')

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

    def set_password(self, raw_password):
        self._set_password = True
        if self.can_update_password():
            return super().set_password(raw_password)
        else:
            error = _("User auth from {}, go there change password").format(
                self.source)
            raise PermissionError(error)

    def can_update_password(self):
        return self.is_local

    def check_otp(self, code):
        from ..utils import check_otp_code
        return check_otp_code(self.otp_secret_key, code)

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

    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

    @property
    def groups_display(self):
        return ' '.join([group.name for group in self.groups.all()])

    @property
    def role_display(self):
        return self.get_role_display()

    @property
    def source_display(self):
        return self.get_source_display()

    @property
    def is_expired(self):
        if self.date_expired and self.date_expired < timezone.now():
            return True
        else:
            return False

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

    @property
    def public_key_obj(self):
        class PubKey(object):
            def __getattr__(self, item):
                return ''

        if self.public_key:
            import sshpubkeys
            try:
                return sshpubkeys.SSHKey(self.public_key)
            except (TabError, TypeError):
                pass
        return PubKey()

    @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'

    @property
    def admin_orgs(self):
        from orgs.models import Organization
        return Organization.get_user_admin_orgs(self)

    @property
    def is_org_admin(self):
        if self.is_superuser or self.admin_orgs.exists():
            return True
        else:
            return False

    @property
    def is_auditor(self):
        return self.role == 'Auditor'

    @property
    def is_common_user(self):
        if self.is_org_admin:
            return False
        if self.is_auditor:
            return False
        if self.is_app:
            return False
        return True

    @property
    def is_app(self):
        return self.role == 'App'

    @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

    @property
    def is_local(self):
        return self.source == self.SOURCE_LOCAL

    @property
    def date_password_expired(self):
        interval = settings.SECURITY_PASSWORD_EXPIRATION_TIME
        date_expired = self.date_password_last_updated + timezone.timedelta(
            days=int(interval))
        return date_expired

    @property
    def password_expired_remain_days(self):
        date_remain = self.date_password_expired - timezone.now()
        return date_remain.days

    @property
    def password_has_expired(self):
        if self.is_local and self.password_expired_remain_days < 0:
            return True
        return False

    @property
    def password_will_expired(self):
        if self.is_local and self.password_expired_remain_days < 5:
            return True
        return False

    def save(self, *args, **kwargs):
        if not self.name:
            self.name = self.username
        if self.username == 'admin':
            self.role = 'Admin'
            self.is_active = True
        super().save(*args, **kwargs)

    @property
    def private_token(self):
        from authentication.models import PrivateToken
        try:
            token = PrivateToken.objects.get(user=self)
        except PrivateToken.DoesNotExist:
            token = self.create_private_token()
        return token

    def create_private_token(self):
        from authentication.models import PrivateToken
        token = PrivateToken.objects.create(user=self)
        return token

    def refresh_private_token(self):
        self.private_token.delete()
        return self.create_private_token()

    def create_bearer_token(self, request=None):
        expiration = settings.TOKEN_EXPIRATION or 3600
        if request:
            remote_addr = request.META.get('REMOTE_ADDR', '')
        else:
            remote_addr = '0.0.0.0'
        if not isinstance(remote_addr, bytes):
            remote_addr = remote_addr.encode("utf-8")
        remote_addr = base64.b16encode(remote_addr)  # .replace(b'=', '')
        cache_key = '%s_%s' % (self.id, remote_addr)
        token = cache.get(cache_key)
        if not token:
            token = uuid.uuid4().hex
        cache.set(token, self.id, expiration)
        cache.set('%s_%s' % (self.id, remote_addr), token, expiration)
        return token

    def refresh_bearer_token(self, token):
        pass

    def create_access_key(self):
        access_key = self.access_keys.create()
        return access_key

    @property
    def access_key(self):
        return self.access_keys.first()

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

    def avatar_url(self):
        admin_default = settings.STATIC_URL + "img/avatar/admin.png"
        user_default = settings.STATIC_URL + "img/avatar/user.png"
        if self.avatar:
            return self.avatar.url
        if self.is_superuser:
            return admin_default
        else:
            return user_default

    def generate_reset_token(self):
        letter = string.ascii_letters + string.digits
        token = ''.join([random.choice(letter) for _ in range(50)])
        self.set_cache(token)
        return token

    def set_cache(self, token):
        key = self.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
        cache.set(key, {'id': self.id, 'email': self.email}, 3600)

    @classmethod
    def validate_reset_password_token(cls, token):
        try:
            key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
            value = cache.get(key)
            user_id = value.get('id', '')
            email = value.get('email', '')
            user = cls.objects.get(id=user_id, email=email)
        except (AttributeError, cls.DoesNotExist) as e:
            logger.error(e, exc_info=True)
            user = None
        return user

    @classmethod
    def expired_reset_password_token(cls, token):
        key = cls.CACHE_KEY_USER_RESET_PASSWORD_PREFIX.format(token)
        cache.delete(key)

    @property
    def otp_enabled(self):
        return self.otp_force_enabled or self.otp_level > 0

    @property
    def otp_force_enabled(self):
        if settings.SECURITY_MFA_AUTH:
            return True
        return self.otp_level == 2

    def enable_otp(self):
        if not self.otp_level == 2:
            self.otp_level = 1

    def force_enable_otp(self):
        self.otp_level = 2

    def disable_otp(self):
        self.otp_level = 0
        self.otp_secret_key = None

    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()],
            'source': self.get_source_display(),
            'wechat': self.wechat,
            'phone': self.phone,
            'otp_level': self.otp_level,
            'comment': self.comment,
            'date_expired': self.date_expired.strftime('%Y-%m-%d %H:%M:%S') \
                if self.date_expired is not None else None
        })

    @classmethod
    def create_app_user(cls, name, comment):
        app = cls.objects.create(username=name,
                                 name=name,
                                 email='{}@local.domain'.format(name),
                                 is_active=False,
                                 role='App',
                                 comment=comment,
                                 is_first_login=False,
                                 created_by='System')
        access_key = app.create_access_key()
        return app, access_key

    def reset_password(self, new_password):
        self.set_password(new_password)
        self.date_password_last_updated = timezone.now()
        self.save()

    def delete(self, using=None, keep_parents=False):
        if self.pk == 1 or self.username == 'admin':
            return
        return super(User, self).delete()

    class Meta:
        ordering = ['username']
        verbose_name = _("User")

    #: Use this method initial user
    @classmethod
    def initial(cls):
        from .group import UserGroup
        user = cls(username='******',
                   email='*****@*****.**',
                   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
        from .group import UserGroup

        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()),
                       role=choice(list(dict(User.ROLE_CHOICES).keys())),
                       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()
Esempio n. 5
0
class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
    SOURCE_LOCAL = 'local'
    SOURCE_LDAP = 'ldap'
    SOURCE_OPENID = 'openid'
    SOURCE_RADIUS = 'radius'
    SOURCE_CHOICES = (
        (SOURCE_LOCAL, _('Local')),
        (SOURCE_LDAP, 'LDAP/AD'),
        (SOURCE_OPENID, 'OpenID'),
        (SOURCE_RADIUS, 'Radius'),
    )

    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
    account_id = models.BigIntegerField(unique=True,
                                        default=utils.generate_account_id,
                                        verbose_name=_('Account ID'))
    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'))
    groups = models.ManyToManyField('account.UserGroup',
                                    related_name='users',
                                    blank=True,
                                    verbose_name=_('User group'))
    role = models.CharField(choices=RoleMixin.ROLE_CHOICES,
                            default='User',
                            max_length=10,
                            blank=True,
                            verbose_name=_('Role'))
    avatar = models.ImageField(upload_to="avatar",
                               null=True,
                               verbose_name=_('Avatar'))
    wechat = models.CharField(max_length=128,
                              blank=True,
                              verbose_name=_('Wechat'))
    phone = models.CharField(max_length=20,
                             unique=True,
                             blank=True,
                             null=True,
                             verbose_name=_('Phone'))
    mfa_level = models.SmallIntegerField(default=0,
                                         choices=MFAMixin.MFA_LEVEL_CHOICES,
                                         verbose_name=_('MFA'))
    otp_secret_key = fields.EncryptCharField(max_length=128,
                                             blank=True,
                                             null=True)
    # Todo: Auto generate key, let user download
    private_key = fields.EncryptTextField(blank=True,
                                          null=True,
                                          verbose_name=_('Private key'))
    public_key = fields.EncryptTextField(blank=True,
                                         null=True,
                                         verbose_name=_('Public key'))
    comment = models.TextField(blank=True,
                               null=True,
                               verbose_name=_('Comment'))
    is_first_login = models.BooleanField(default=True)
    is_admin_set_password = models.BooleanField(default=False)
    date_expired = models.DateTimeField(default=date_expired_default,
                                        blank=True,
                                        null=True,
                                        db_index=True,
                                        verbose_name=_('Date expired'))
    created_by = models.CharField(max_length=30,
                                  default='',
                                  blank=True,
                                  verbose_name=_('Created by'))
    source = models.CharField(max_length=30,
                              default=SOURCE_LOCAL,
                              choices=SOURCE_CHOICES,
                              verbose_name=_('Source'))
    date_password_last_updated = models.DateTimeField(
        auto_now_add=True,
        blank=True,
        null=True,
        verbose_name=_('Date password last updated'))

    user_cache_key_prefix = '_User_{}'
    users = UsersManager()

    def __str__(self):
        return '{0.name}({0.username})'.format(self)

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

    @property
    def groups_display(self):
        return [{
            "id": group.id,
            "name": group.name
        } for group in self.groups.all()]

    @property
    def source_display(self):
        return self.get_source_display()

    @property
    def is_expired(self):
        if self.date_expired and self.date_expired < timezone.now():
            return True
        else:
            return False

    @property
    def expired_remain_days(self):
        date_remain = self.date_expired - timezone.now()
        return date_remain.days

    @property
    def will_expired(self):
        if 0 <= self.expired_remain_days < 5:
            return True
        else:
            return False

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

    @property
    def is_local(self):
        return self.source == self.SOURCE_LOCAL

    def save(self, *args, **kwargs):
        if not self.name:
            self.name = self.username
        if self.username == 'admin':
            self.role = 'Admin'
            self.is_active = True
        super().save(*args, **kwargs)

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

    def avatar_url(self):
        admin_default = settings.STATIC_URL + "img/avatar/admin.gif"
        user_default = settings.STATIC_URL + "img/avatar/user.gif"
        if self.avatar:
            return self.avatar.url
        if self.is_superuser:
            return admin_default
        else:
            return user_default

    def delete(self, using=None, keep_parents=False):
        if self.username == 'admin':
            return
        return super(User, self).delete()

    class Meta:
        ordering = ['username']
        verbose_name = _("User")
Esempio n. 6
0
class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
    class Source(TextChoices):
        local = 'local', _('Local')
        ldap = 'ldap', 'LDAP/AD'
        openid = 'openid', 'OpenID'
        radius = 'radius', 'Radius'
        cas = 'cas', 'CAS'

    SOURCE_BACKEND_MAPPING = {
        Source.local: [
            settings.AUTH_BACKEND_MODEL,
            settings.AUTH_BACKEND_PUBKEY,
            settings.AUTH_BACKEND_WECOM,
            settings.AUTH_BACKEND_DINGTALK,
        ],
        Source.ldap: [settings.AUTH_BACKEND_LDAP],
        Source.openid:
        [settings.AUTH_BACKEND_OIDC_PASSWORD, settings.AUTH_BACKEND_OIDC_CODE],
        Source.radius: [settings.AUTH_BACKEND_RADIUS],
        Source.cas: [settings.AUTH_BACKEND_CAS],
    }

    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
    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'))
    groups = models.ManyToManyField('users.UserGroup',
                                    related_name='users',
                                    blank=True,
                                    verbose_name=_('User group'))
    role = models.CharField(choices=RoleMixin.ROLE.choices,
                            default='User',
                            max_length=10,
                            blank=True,
                            verbose_name=_('Role'))
    avatar = models.ImageField(upload_to="avatar",
                               null=True,
                               verbose_name=_('Avatar'))
    wechat = models.CharField(max_length=128,
                              blank=True,
                              verbose_name=_('Wechat'))
    phone = models.CharField(max_length=20,
                             blank=True,
                             null=True,
                             verbose_name=_('Phone'))
    mfa_level = models.SmallIntegerField(default=0,
                                         choices=MFAMixin.MFA_LEVEL_CHOICES,
                                         verbose_name=_('MFA'))
    otp_secret_key = fields.EncryptCharField(max_length=128,
                                             blank=True,
                                             null=True)
    # Todo: Auto generate key, let user download
    private_key = fields.EncryptTextField(blank=True,
                                          null=True,
                                          verbose_name=_('Private key'))
    public_key = fields.EncryptTextField(blank=True,
                                         null=True,
                                         verbose_name=_('Public key'))
    comment = models.TextField(blank=True,
                               null=True,
                               verbose_name=_('Comment'))
    is_first_login = models.BooleanField(default=True)
    date_expired = models.DateTimeField(default=date_expired_default,
                                        blank=True,
                                        null=True,
                                        db_index=True,
                                        verbose_name=_('Date expired'))
    created_by = models.CharField(max_length=30,
                                  default='',
                                  blank=True,
                                  verbose_name=_('Created by'))
    source = models.CharField(max_length=30,
                              default=Source.local,
                              choices=Source.choices,
                              verbose_name=_('Source'))
    date_password_last_updated = models.DateTimeField(
        auto_now_add=True,
        blank=True,
        null=True,
        verbose_name=_('Date password last updated'))
    need_update_password = models.BooleanField(
        default=False, verbose_name=_('Need update password'))
    wecom_id = models.CharField(null=True,
                                default=None,
                                unique=True,
                                max_length=128,
                                verbose_name=_('WeCom'))
    dingtalk_id = models.CharField(null=True,
                                   default=None,
                                   unique=True,
                                   max_length=128,
                                   verbose_name=_('DingTalk'))
    feishu_id = models.CharField(null=True,
                                 default=None,
                                 unique=True,
                                 max_length=128,
                                 verbose_name=_('FeiShu'))

    def __str__(self):
        return '{0.name}({0.username})'.format(self)

    @classmethod
    def get_group_ids_by_user_id(cls, user_id):
        group_ids = cls.groups.through.objects.filter(
            user_id=user_id).distinct().values_list('usergroup_id', flat=True)
        group_ids = list(group_ids)
        return group_ids

    @property
    def receive_backends(self):
        return self.user_msg_subscription.receive_backends

    @property
    def is_wecom_bound(self):
        return bool(self.wecom_id)

    @property
    def is_dingtalk_bound(self):
        return bool(self.dingtalk_id)

    @property
    def is_feishu_bound(self):
        return bool(self.feishu_id)

    @property
    def is_otp_secret_key_bound(self):
        return bool(self.otp_secret_key)

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

    @property
    def groups_display(self):
        return ' '.join([group.name for group in self.groups.all()])

    @property
    def source_display(self):
        return self.get_source_display()

    @property
    def is_expired(self):
        if self.date_expired and self.date_expired < timezone.now():
            return True
        else:
            return False

    @property
    def expired_remain_days(self):
        date_remain = self.date_expired - timezone.now()
        return date_remain.days

    @property
    def will_expired(self):
        if 0 <= self.expired_remain_days < 5:
            return True
        else:
            return False

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

    @property
    def is_local(self):
        return self.source == self.Source.local.value

    def set_unprovide_attr_if_need(self):
        if not self.name:
            self.name = self.username
        if not self.email or '@' not in self.email:
            email = '{}@{}'.format(self.username, settings.EMAIL_SUFFIX)
            if '@' in self.username:
                email = self.username
            self.email = email

    def save(self, *args, **kwargs):
        self.set_unprovide_attr_if_need()
        if self.username == 'admin':
            self.role = 'Admin'
            self.is_active = True
        super().save(*args, **kwargs)

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

    def set_avatar(self, f):
        self.avatar.save(self.username, f)

    @classmethod
    def get_avatar_url(cls, username):
        user_default = settings.STATIC_URL + "img/avatar/user.png"
        return user_default

    def avatar_url(self):
        admin_default = settings.STATIC_URL + "img/avatar/admin.png"
        user_default = settings.STATIC_URL + "img/avatar/user.png"
        if self.avatar:
            return self.avatar.url
        if self.is_superuser:
            return admin_default
        else:
            return user_default

    def unblock_login(self):
        from users.utils import LoginBlockUtil, MFABlockUtils
        LoginBlockUtil.unblock_user(self.username)
        MFABlockUtils.unblock_user(self.username)

    @property
    def login_blocked(self):
        from users.utils import LoginBlockUtil, MFABlockUtils
        if LoginBlockUtil.is_user_block(self.username):
            return True
        if MFABlockUtils.is_user_block(self.username):
            return True

        return False

    def delete(self, using=None, keep_parents=False):
        if self.pk == 1 or self.username == 'admin':
            return
        return super(User, self).delete()

    @classmethod
    def get_user_allowed_auth_backends(cls, username):
        if not settings.ONLY_ALLOW_AUTH_FROM_SOURCE or not username:
            # return settings.AUTHENTICATION_BACKENDS
            return None
        user = cls.objects.filter(username=username).first()
        if not user:
            return None
        return user.get_allowed_auth_backends()

    def get_allowed_auth_backends(self):
        if not settings.ONLY_ALLOW_AUTH_FROM_SOURCE:
            return None
        return self.SOURCE_BACKEND_MAPPING.get(self.source, [])

    class Meta:
        ordering = ['username']
        verbose_name = _("User")

    #: Use this method initial user
    @classmethod
    def initial(cls):
        from .group import UserGroup
        user = cls(username='******',
                   email='*****@*****.**',
                   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())

    def can_send_created_mail(self):
        if self.email and self.source == self.Source.local.value:
            return True
        return False
Esempio n. 7
0
class User(AuthMixin, TokenMixin, RoleMixin, MFAMixin, AbstractUser):
    class Source(TextChoices):
        local = 'local', _('Local')
        ldap = 'ldap', 'LDAP/AD'
        openid = 'openid', 'OpenID'
        radius = 'radius', 'Radius'
        cas = 'cas', 'CAS'

    SOURCE_BACKEND_MAPPING = {
        Source.local:
        [settings.AUTH_BACKEND_MODEL, settings.AUTH_BACKEND_PUBKEY],
        Source.ldap: [settings.AUTH_BACKEND_LDAP],
        Source.openid:
        [settings.AUTH_BACKEND_OIDC_PASSWORD, settings.AUTH_BACKEND_OIDC_CODE],
        Source.radius: [settings.AUTH_BACKEND_RADIUS],
        Source.cas: [settings.AUTH_BACKEND_CAS],
    }

    id = models.UUIDField(default=uuid.uuid4, primary_key=True)
    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'))
    groups = models.ManyToManyField('users.UserGroup',
                                    related_name='users',
                                    blank=True,
                                    verbose_name=_('User group'))
    role = models.CharField(choices=RoleMixin.ROLE.choices,
                            default='User',
                            max_length=10,
                            blank=True,
                            verbose_name=_('Role'))
    avatar = models.ImageField(upload_to="avatar",
                               null=True,
                               verbose_name=_('Avatar'))
    wechat = models.CharField(max_length=128,
                              blank=True,
                              verbose_name=_('Wechat'))
    phone = models.CharField(max_length=20,
                             blank=True,
                             null=True,
                             verbose_name=_('Phone'))
    mfa_level = models.SmallIntegerField(default=0,
                                         choices=MFAMixin.MFA_LEVEL_CHOICES,
                                         verbose_name=_('MFA'))
    otp_secret_key = fields.EncryptCharField(max_length=128,
                                             blank=True,
                                             null=True)
    # Todo: Auto generate key, let user download
    private_key = fields.EncryptTextField(blank=True,
                                          null=True,
                                          verbose_name=_('Private key'))
    public_key = fields.EncryptTextField(blank=True,
                                         null=True,
                                         verbose_name=_('Public key'))
    comment = models.TextField(blank=True,
                               null=True,
                               verbose_name=_('Comment'))
    is_first_login = models.BooleanField(default=True)
    date_expired = models.DateTimeField(default=date_expired_default,
                                        blank=True,
                                        null=True,
                                        db_index=True,
                                        verbose_name=_('Date expired'))
    created_by = models.CharField(max_length=30,
                                  default='',
                                  blank=True,
                                  verbose_name=_('Created by'))
    source = models.CharField(max_length=30,
                              default=Source.local,
                              choices=Source.choices,
                              verbose_name=_('Source'))
    date_password_last_updated = models.DateTimeField(
        auto_now_add=True,
        blank=True,
        null=True,
        verbose_name=_('Date password last updated'))

    def __str__(self):
        return '{0.name}({0.username})'.format(self)

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

    @property
    def groups_display(self):
        return ' '.join([group.name for group in self.groups.all()])

    @property
    def source_display(self):
        return self.get_source_display()

    @property
    def is_expired(self):
        if self.date_expired and self.date_expired < timezone.now():
            return True
        else:
            return False

    @property
    def expired_remain_days(self):
        date_remain = self.date_expired - timezone.now()
        return date_remain.days

    @property
    def will_expired(self):
        if 0 <= self.expired_remain_days < 5:
            return True
        else:
            return False

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

    @property
    def is_local(self):
        return self.source == self.Source.local.value

    def set_unprovide_attr_if_need(self):
        if not self.name:
            self.name = self.username
        if not self.email or '@' not in self.email:
            email = '{}@{}'.format(self.username, settings.EMAIL_SUFFIX)
            if '@' in self.username:
                email = self.username
            self.email = email

    def save(self, *args, **kwargs):
        self.set_unprovide_attr_if_need()
        if self.username == 'admin':
            self.role = 'Admin'
            self.is_active = True
        super().save(*args, **kwargs)

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

    def set_avatar(self, f):
        self.avatar.save(self.username, f)

    @classmethod
    def get_avatar_url(cls, username):
        user_default = settings.STATIC_URL + "img/avatar/user.png"
        return user_default

    # def admin_orgs(self):
    #     from orgs.models import Organization
    #     orgs = Organization.get_user_admin_or_audit_orgs(self)
    #     return orgs

    def avatar_url(self):
        admin_default = settings.STATIC_URL + "img/avatar/admin.png"
        user_default = settings.STATIC_URL + "img/avatar/user.png"
        if self.avatar:
            return self.avatar.url
        if self.is_superuser:
            return admin_default
        else:
            return user_default

    @property
    def login_blocked(self):
        key_prefix_block = "_LOGIN_BLOCK_{}"
        key_block = key_prefix_block.format(self.username)
        blocked = bool(cache.get(key_block))
        return blocked

    def delete(self, using=None, keep_parents=False):
        if self.pk == 1 or self.username == 'admin':
            return
        return super(User, self).delete()

    class Meta:
        ordering = ['username']
        verbose_name = _("User")

    #: Use this method initial user
    @classmethod
    def initial(cls):
        from .group import UserGroup
        user = cls(username='******',
                   email='*****@*****.**',
                   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())

    def can_send_created_mail(self):
        if self.email and self.source == self.Source.local.value:
            return True
        return False