Exemplo n.º 1
0
    def __setattr__(self, name, value):
        """
        Overrides our core __setattr__ because we have to allow for extra
        authentication attributes that would normally get moved to
        unsupported_attrs.
        """

        if (not self._dynamic and hasattr(self, 'unsupported_attrs')
                and not name in self._fields and not name.startswith('_')
                and not name.startswith('$') and not '.' in name
                and name not in ('backend')):
            if not self.unsupported_attrs:
                self.unsupported_attrs = UnsupportedAttrs()
            self.unsupported_attrs.__setattr__(name, value)
        else:
            super(CriptsDocument, self).__setattr__(name, value)
Exemplo n.º 2
0
Arquivo: user.py Projeto: lakiw/cripts
    def __setattr__(self, name, value):
        """
        Overrides our core __setattr__ because we have to allow for extra
        authentication attributes that would normally get moved to
        unsupported_attrs.
        """

        if (not self._dynamic and hasattr(self, 'unsupported_attrs')
            and not name in self._fields and not name.startswith('_')
            and not name.startswith('$') and not '.' in name
            and name not in ('backend')):
            if not self.unsupported_attrs:
                self.unsupported_attrs = UnsupportedAttrs()
            self.unsupported_attrs.__setattr__(name, value)
        else:
            super(CriptsDocument, self).__setattr__(name, value)
Exemplo n.º 3
0
Arquivo: user.py Projeto: lakiw/cripts
class CRIPTsUser(CriptsDocument, CriptsSchemaDocument, Document):
    """
    CRIPTs User object
    """
    meta = {
        "collection": settings.COL_USERS,
        'indexes': [
            {'fields': ['username'],
             'unique': True,
             'sparse': True,
            },
        ],
        "cripts_type": 'User',
        "latest_schema_version": 3,
        "schema_doc": {
            'username': '******',
            'organization': 'The name of the organization this user is from',
            'role': 'The role this user has been granted from a CRIPTs Admin',
            'sources': ('List [] of source names this user has been granted'
                        ' access to view data from'),
            'subscriptions': { 
                'Dataset': [
                    {
                        'date': 'ISODate subscribed',
                        'id': 'ObjectId of the object subscribed to'
                    }
                ],
                'EmailAddress': [
                    {
                        'date': 'ISODate subscribed',
                        'id': 'ObjectId of the object subscribed to'
                    }
                ],
                'Event': [
                    {
                        'date': 'ISODate subscribed',
                        'id': 'ObjectId of the object subscribed to'
                    }
                ],
                'Hash': [
                    {
                        'date': 'ISODate subscribed',
                        'id': 'ObjectId of the object subscribed to'
                    }
                ],
                'Target': [
                    {
                        'date': 'ISODate subscribed',
                        'id': 'ObjectId of the object subscribed to'
                    }
                ],
                'UserName': [
                    {
                        'date': 'ISODate subscribed',
                        'id': 'ObjectId of the object subscribed to'
                    }
                ],
                'Source': [
                    {
                        'date': 'ISODate subscribed',
                        'name': 'Name of the source subscribed to'
                    }
                ],
            },
            'favorites': {
                'Dataset': [],
                'EmailAddress': [],
                'Event': [],
                'Hash': [],
                'Target': [],
                'UserName': []
            }
        },
    }

    username = StringField(max_length=30, required=True,
                           verbose_name='username',
                           help_text="Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters")

    first_name = StringField(max_length=30,
                             verbose_name='first name')

    last_name = StringField(max_length=30,
                            verbose_name='last name')
    email = EmailField(verbose_name='e-mail address')
    password = StringField(max_length=128,
                           verbose_name='password',
                           help_text="Use '[algo]$[iterations]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>.")
    secret = StringField(verbose_name='TOTP Secret')
    is_staff = BooleanField(default=False,
                            verbose_name='staff status',
                            help_text="Designates whether the user can log into this admin site.")
    is_active = BooleanField(default=True,
                             verbose_name='active',
                             help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.")
    is_superuser = BooleanField(default=False,
                                verbose_name='superuser status',
                                help_text="Designates that this user has all permissions without explicitly assigning them.")
    last_login = DateTimeField(default=datetime.datetime.now,
                               verbose_name='last login')
    date_joined = DateTimeField(default=datetime.datetime.now,
                                verbose_name='date joined')

    invalid_login_attempts = IntField(default=0)
    login_attempts = ListField(EmbeddedDocumentField(EmbeddedLoginAttempt))
    organization = StringField(default=settings.COMPANY_NAME)
    password_reset = EmbeddedDocumentField(EmbeddedPasswordReset, default=EmbeddedPasswordReset())
    role = StringField(default="Analyst")
    sources = ListField(StringField())
    subscriptions = EmbeddedDocumentField(EmbeddedSubscriptions, default=EmbeddedSubscriptions())
    favorites = EmbeddedDocumentField(EmbeddedFavorites, default=EmbeddedFavorites())
    prefs = EmbeddedDocumentField(PreferencesField, default=PreferencesField())
    totp = BooleanField(default=False)
    secret = StringField(default="")
    api_keys = ListField(EmbeddedDocumentField(EmbeddedAPIKey))
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    defaultDashboard = ObjectIdField(required=False, default=None)


    def migrate(self):
        """
        Migrate to latest schema version.
        """

        migrate_user(self)

    def __setattr__(self, name, value):
        """
        Overrides our core __setattr__ because we have to allow for extra
        authentication attributes that would normally get moved to
        unsupported_attrs.
        """

        if (not self._dynamic and hasattr(self, 'unsupported_attrs')
            and not name in self._fields and not name.startswith('_')
            and not name.startswith('$') and not '.' in name
            and name not in ('backend')):
            if not self.unsupported_attrs:
                self.unsupported_attrs = UnsupportedAttrs()
            self.unsupported_attrs.__setattr__(name, value)
        else:
            super(CriptsDocument, self).__setattr__(name, value)

    @property
    def pk(self):
        """
        Return the ObjectId as the primary key.
        """

        return self.id

    def __str__(self):
        """
        This is so request.user returns the username like Django expects,
        not the whole object.
        """

        if self.username:
            return self.username

    # the rest of this taken from the MongoEngine User class.

    def __unicode__(self):
        """
        This is so request.user returns the username like Django expects,
        not the whole object.
        """

        return self.username

    def get_full_name(self):
        """
        Returns the users first and last names, separated by a space.
        """

        full_name = u'%s %s' % (self.first_name or '', self.last_name or '')
        return full_name.strip()

    def is_anonymous(self):
        """
        We do not allow anonymous users.
        """

        return False

    def is_authenticated(self):
        """
        If we know about the user from the request, it means they've
        authenticated.
        """

        return True

    def mark_active(self, analyst=None):
        """
        Mark the user as active.
        """

        self.is_active = True
        self.save(username=analyst)
        return self

    def mark_inactive(self, analyst=None):
        """
        Deactivate the user.
        """

        self.is_active = False
        self.save(username=analyst)
        return self

    def is_password_complex(self, password):
        """
        Based on the CRIPTsConfig, is the password provided complex enough to be
        used?

        :param password: The password to check for complexity.
        :type password: str
        :returns: True, False
        """

        cripts_config = CRIPTsConfig.objects().first()
        if cripts_config:
            pw_regex = cripts_config.password_complexity_regex
        else:
            pw_regex = settings.PASSWORD_COMPLEXITY_REGEX
        complex_regex = re.compile(pw_regex)
        if complex_regex.match(password):
            return True
        return False

    def set_password(self, raw_password, analyst=None):
        """
        Sets the user's password - always use this rather than directly
        assigning to :attr:`~mongoengine.django.auth.User.password` as the
        password is hashed before storage.

        :param raw_password: The password to hash and store.
        :type raw_password: str
        :returns: self, False
        """

        if self.is_password_complex(raw_password):
            self.password = make_password(raw_password)
            self.save(username=analyst)
            return self
        else:
            return False

    def set_reset_code(self, analyst):
        """
        Sets a reset code on the account for password reset validation.

        :returns: str
        """

        e = EmbeddedPasswordReset()
        char_set = string.ascii_uppercase + string.digits
        e.reset_code = ''.join(random.sample(char_set*6,6))
        e.date = datetime.datetime.now()
        self.password_reset = e
        self.save(username=analyst)
        return e.reset_code

    def reset_password(self, rcode, new_p, new_p_c, analyst):
        """
        Reset the user's password. Validate the reset code, ensure the two
        passwords are identical, and then set.

        :param rcode: Reset Code to validate.
        :type rcode: str
        :param new_p: New password.
        :type new_p: str
        :param new_p_c: New password confirmation.
        :type new_p_c: str
        :param analyst: The user.
        :type analyst: str
        :returns: dict with keys "success" (boolean) and "message" (str).
        """

        if self.validate_reset_code(rcode, analyst)['success']:
            if new_p == new_p_c:
                self.password_reset.reset_code = ""
                if self.set_password(new_p):
                    return {'success': True, 'message': 'Password reset.'}
                else:
                    cripts_config = CRIPTsConfig.objects().first()
                    if cripts_config:
                        pw_desc = cripts_config.password_complexity_desc
                    else:
                        pw_desc = settings.PASSWORD_COMPLEXITY_DESC
                    message = 'Password not complex enough: %s' % pw_desc
                    return {'success': False, 'message': message}
            else:
                return {'success': False, 'message': 'Passwords do not match.'}
        else:
            self.password_reset.reset_code = ""
            self.save(username=analyst)
            return {'success': False, 'message': 'Reset Code Expired.'}

    def validate_reset_code(self, reset_code, analyst):
        """
        Validate the reset code. Also ensure that the reset code hasn't expired
        already since it is a limited-time use reset.

        :param reset_code: The reset code.
        :type reset_code: str
        :param analyst: The user.
        :type analyst: str
        :returns: dict with keys "success" (boolean) and "message" (str).
        """

        my_reset = self.password_reset.reset_code
        if len(reset_code) == 6 and len(my_reset) == 6 and my_reset == reset_code:
            date = datetime.datetime.now()
            diff = date - self.password_reset.date
            window = divmod(diff.days * 86400 + diff.seconds, 60)
            if window[0] < 5:
                self.password_reset.attempts = 0
                self.save(username=analyst)
                return {'success': True, 'message': 'Reset Code Validated.'}
            else:
                self.password_reset.attempts += 1
                self.password_reset.reset_code = ""
                self.save(username=analyst)
                return {'success': False, 'message': 'Reset Code Expired.'}
        self.password_reset.attempts += 1
        if self.password_reset.attempts > 2:
            self.password_reset.date = self.password_reset.date + datetime.timedelta(minutes=-5)
            self.save(username=analyst)
            return {'success': False, 'message': 'Reset Code Expired.'}
        self.save(username=analyst)
        return {'success': False, 'message': 'Reset Code Invalid.'}

    def check_password(self, raw_password):
        """
        Checks the user's password against a provided password - always use
        this rather than directly comparing to
        :attr:`~mongoengine.django.auth.User.password` as the password is
        hashed before storage.
        """

        return check_password(raw_password, self.password)

    def create_api_key(self, name, analyst, default=False):
        """
        Generate an API key for the user. It will require a name as we allow for
        unlimited API keys and users need a way to reference them.

        :param name: The name for the API key.
        :type name: str
        :param analyst: The user.
        :type analyst: str
        :param default: Use as default API key.
        :type default: boolean
        :returns: dict with keys "success" (boolean) and "message" (str).
        """

        if not name:
            return {'success': False, 'message': 'Need a name'}
        new_uuid = uuid.uuid4()
        key = hmac.new(new_uuid.bytes, digestmod=sha1).hexdigest()
        ea = EmbeddedAPIKey(name=name, api_key=key, default=default)
        if len(self.api_keys) < 1:
            ea.default = True
        self.api_keys.append(ea)
        self.save(username=analyst)
        return {'success': True, 'message': {'name': name,
                                             'key': key,
                                             'date': str(ea.date)}}

    def default_api_key(self, name, analyst):
        """
        Make an API key the default key for a user. The default key is used for
        situations where the user is not or cannot be asked which API key to
        use.

        :param name: The name of the API key.
        :type name: str
        :param analyst: The user.
        :type analyst: str
        :returns: dict with keys "success" (boolean) and "message" (str).
        """

        c = 0
        for key in self.api_keys:
            if key.name == name:
                self.api_keys[c].default = True
            else:
                self.api_keys[c].default = False
            c += 1
        self.save(username=analyst)
        return {'success': True}

    def revoke_api_key(self, name, analyst):
        """
        Revoke an API key so it can no longer be used.

        :param name: The name of the API key.
        :type name: str
        :param analyst: The user.
        :type analyst: str
        :returns: dict with keys "success" (boolean) and "message" (str).
        """

        keys = self.api_keys
        keyslen = len(keys)
        self.api_keys = [k for k in keys if k.name != name]
        if keyslen > len(self.api_keys):
            self.save(username=analyst)
            return {'success': True}
        else:
            return {'success': False, 'message': 'Key not found.'}

    def get_api_key(self, name):
        """
        Get the API key.

        :param name: The name of the API key.
        :type name: str
        :returns: str, None
        """

        for key in self.api_keys:
            if key.name == name:
                return key.api_key
        return None

    def validate_api_key(self, key):
        """
        Validate that the API key exists for this user.

        :param key: The API key.
        :type key: str
        :returns: True, False
        """

        for keys in self.api_keys:
            if keys.api_key == key:
                return True
        return False

    @classmethod
    def create_user(cls, username, password=None, email=None, analyst=None):
        """
        Create (and save) a new user with the given username, password and
        email address.
        """

        now = datetime.datetime.now()

        # Normalize the address by lowercasing the domain part of the email
        # address.
        if email is not None:
            try:
                email_name, domain_part = email.strip().split('@', 1)
            except ValueError:
                return None
            else:
                email = '@'.join([email_name, domain_part.lower()])

        user = cls(username=username, email=email, date_joined=now)
        if password is not None and user.set_password(password):
            user.save(username=analyst)
            return user
        elif CRIPTsConfig.remote_user:
            user.save(username="******")
            return user
        else:
            return None

    def get_group_permissions(self, obj=None):
        """
        Returns a list of permission strings that this user has through his/her
        groups. This method queries all available auth backends. If an object
        is passed in, only permissions matching this object are returned.
        """
        permissions = set()
        for backend in auth.get_backends():
            if hasattr(backend, "get_group_permissions"):
                permissions.update(backend.get_group_permissions(self, obj))
        return permissions

    def get_all_permissions(self, obj=None):
        return _user_get_all_permissions(self, obj)

    def has_perm(self, perm, obj=None):
        """
        Returns True if the user has the specified permission. This method
        queries all available auth backends, but returns immediately if any
        backend returns True. Thus, a user who has permission from a single
        auth backend is assumed to have permission in general. If an object is
        provided, permissions for this specific object are checked.
        """

        # Active superusers have all permissions.
        if self.is_active and self.is_superuser:
            return True

        # Otherwise we need to check the backends.
        return _user_has_perm(self, perm, obj)

    def has_module_perms(self, app_label):
        """
        Returns True if the user has any permissions in the given app label.
        Uses pretty much the same logic as has_perm, above.
        """
        # Active superusers have all permissions.
        if self.is_active and self.is_superuser:
            return True

        return _user_has_module_perms(self, app_label)

    def email_user(self, subject, message, from_email=None):
        """
        Sends an e-mail to this User.
        """

        from django.core.mail import send_mail
        if not from_email:
            cripts_config = CRIPTsConfig.objects().first()
            if cripts_config:
                from_email = cripts_config.cripts_email
        send_mail(subject, message, from_email, [self.email])

    def get_username(self):
        return self.username

    def get_preference(self, section, setting, default=None):
        """
        Get a user preference setting out of the deep dynamic dictionary
        'section' is the preferences 'section' e.g. 'ui'
        'setting' is the dot separated preference setting  e.g. 'foo.bar.enabled'

        :param section: A specific section of preferences you want.
        :type section: str
        :param setting: The setting you want to get.
        :type setting: str
        :returns: None, str, dict
        """

        if not section in self.prefs:
            return default

        # Split the preference option into subtrees on '.'
        otree = setting.split(".")
        param = otree.pop()
        opt = self.prefs[section]

        if len(otree):
            for subsect in otree:
                if subsect in opt:
                    opt = opt[subsect]
                else:
                    return default

        if not param in opt:
            return default

        return opt[param]

    def update_from_ldap(self, analyst, config=None, passw=''):
        """
        Set First Name, Last Name, and Email from LDAP if we can get the data.
        """

        info = self.info_from_ldap(config, passw)
        if info['result'] == "OK":
            self.first_name = info['first_name']
            self.last_name = info['last_name']
            self.email = info['email']
            self.save(username=analyst)

    def info_from_ldap(self, config=None, password=''):
        """
        Get information about this user from LDAP.
        """

        import ldap, ldapurl
        resp = {"result": "ERROR"}
        if not config:
            config = CRIPTsConfig.objects().first()
        # Make sure we have the rquired settings, else return failure
        if not config.ldap_server or not config.ldap_userdn:
            return resp
        ldap_server = config.ldap_server.split(':')
        scheme = "ldap"
        if config.ldap_tls:
            scheme = "ldaps"
        url = ldapurl.LDAPUrl('%s://%s' % (scheme, ldap_server[0]))
        if len(ldap_server) == 2:
            l = ldap.initialize('%s:%s' % (url.unparse(),
                                           ldap_server[1]))
        else:
            l = ldap.initialize(url.unparse())
        l.protocol_version = 3
        l.set_option(ldap.OPT_REFERRALS, 0)
        l.set_option(ldap.OPT_TIMEOUT, 10)
        # setup auth for custom cn's
        cn = "cn="
        if config.ldap_usercn:
            cn = config.ldap_usercn
        # two-step ldap binding
        if len(config.ldap_bind_dn) > 0:
            try:
            	logger.info("binding with bind_dn: %s" % config.ldap_bind_dn)
            	l.simple_bind_s(config.ldap_bind_dn, config.ldap_bind_password)
            	filter = '(|(cn='+self.username+')(uid='+self.username+')(mail='+self.username+'))'
            	# use the retrieved dn for the second bind
            	un = l.search_s(config.ldap_userdn,ldap.SCOPE_SUBTREE,filter,['dn'])[0][0]
            except Exception as err:
            	#logger.error("Error binding to LDAP for: %s" % config.ldap_bind_dn)
            	logger.error("Error in info_from_ldap: %s" % err)
            l.unbind()
            if len(ldap_server) == 2:
                l = ldap.initialize('%s:%s' % (url.unparse(),
                                               ldap_server[1]))
            else:
                l = ldap.initialize(url.unparse())
            l.protocol_version = 3
            l.set_option(ldap.OPT_REFERRALS, 0)
            l.set_option(ldap.OPT_TIMEOUT, 10)
        else:
            un = self.username
        # setup auth for custom cn's
        if len(config.ldap_usercn) > 0:
            un = "%s%s,%s" % (config.ldap_usercn,
                              self.username,
                              config.ldap_userdn)
        elif "@" in config.ldap_userdn:
            un = "%s%s" % (self.username, config.ldap_userdn)
	try:
            # Try auth bind first
            l.simple_bind_s(un, password)
            logger.info("Bound to LDAP for: %s" % un)
        except Exception as e:
            #logger.error("Error binding to LDAP for: %s" % self.username)
            logger.error("info_from_ldap:ERR: %s" % e)
        try:
            uatr = None
            uatr = l.search_s(config.ldap_userdn,
                              ldap.SCOPE_SUBTREE,
                              '(|(cn='+self.username+')(uid='+self.username+'))'
                              )[0][1]
            resp['first_name'] = uatr['givenName'][0]
            resp['last_name'] = uatr['sn'][0]
            resp['email'] = uatr['mail'][0]
            resp['result'] = "OK"
            logger.info("Retrieved LDAP info for: %s" % self.username)
        except Exception as e:
            #logger.error("Error retrieving LDAP info for: %s" % self.username)
            logger.error("info_from_ldap ERR: %s" % e)
        l.unbind()
        return resp

    def getDashboards(self):
        from cripts.dashboards.handlers import getDashboardsForUser
        return getDashboardsForUser(self)
Exemplo n.º 4
0
class CRIPTsUser(CriptsDocument, CriptsSchemaDocument, Document):
    """
    CRIPTs User object
    """
    meta = {
        "collection": settings.COL_USERS,
        'indexes': [
            {
                'fields': ['username'],
                'unique': True,
                'sparse': True,
            },
        ],
        "cripts_type": 'User',
        "latest_schema_version": 3,
        "schema_doc": {
            'username':
            '******',
            'organization':
            'The name of the organization this user is from',
            'role':
            'The role this user has been granted from a CRIPTs Admin',
            'sources': ('List [] of source names this user has been granted'
                        ' access to view data from'),
            'subscriptions': {
                'Dataset': [{
                    'date': 'ISODate subscribed',
                    'id': 'ObjectId of the object subscribed to'
                }],
                'EmailAddress': [{
                    'date': 'ISODate subscribed',
                    'id': 'ObjectId of the object subscribed to'
                }],
                'Event': [{
                    'date': 'ISODate subscribed',
                    'id': 'ObjectId of the object subscribed to'
                }],
                'Hash': [{
                    'date': 'ISODate subscribed',
                    'id': 'ObjectId of the object subscribed to'
                }],
                'Target': [{
                    'date': 'ISODate subscribed',
                    'id': 'ObjectId of the object subscribed to'
                }],
                'UserName': [{
                    'date': 'ISODate subscribed',
                    'id': 'ObjectId of the object subscribed to'
                }],
                'Source': [{
                    'date': 'ISODate subscribed',
                    'name': 'Name of the source subscribed to'
                }],
            },
            'favorites': {
                'Dataset': [],
                'EmailAddress': [],
                'Event': [],
                'Hash': [],
                'Target': [],
                'UserName': []
            }
        },
    }

    username = StringField(
        max_length=30,
        required=True,
        verbose_name='username',
        help_text=
        "Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"
    )

    first_name = StringField(max_length=30, verbose_name='first name')

    last_name = StringField(max_length=30, verbose_name='last name')
    email = EmailField(verbose_name='e-mail address')
    password = StringField(
        max_length=128,
        verbose_name='password',
        help_text=
        "Use '[algo]$[iterations]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."
    )
    secret = StringField(verbose_name='TOTP Secret')
    is_staff = BooleanField(
        default=False,
        verbose_name='staff status',
        help_text="Designates whether the user can log into this admin site.")
    is_active = BooleanField(
        default=True,
        verbose_name='active',
        help_text=
        "Designates whether this user should be treated as active. Unselect this instead of deleting accounts."
    )
    is_superuser = BooleanField(
        default=False,
        verbose_name='superuser status',
        help_text=
        "Designates that this user has all permissions without explicitly assigning them."
    )
    last_login = DateTimeField(default=datetime.datetime.now,
                               verbose_name='last login')
    date_joined = DateTimeField(default=datetime.datetime.now,
                                verbose_name='date joined')

    invalid_login_attempts = IntField(default=0)
    login_attempts = ListField(EmbeddedDocumentField(EmbeddedLoginAttempt))
    organization = StringField(default=settings.COMPANY_NAME)
    password_reset = EmbeddedDocumentField(EmbeddedPasswordReset,
                                           default=EmbeddedPasswordReset())
    role = StringField(default="Analyst")
    sources = ListField(StringField())
    subscriptions = EmbeddedDocumentField(EmbeddedSubscriptions,
                                          default=EmbeddedSubscriptions())
    favorites = EmbeddedDocumentField(EmbeddedFavorites,
                                      default=EmbeddedFavorites())
    prefs = EmbeddedDocumentField(PreferencesField, default=PreferencesField())
    totp = BooleanField(default=False)
    secret = StringField(default="")
    api_keys = ListField(EmbeddedDocumentField(EmbeddedAPIKey))
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email']

    defaultDashboard = ObjectIdField(required=False, default=None)

    def migrate(self):
        """
        Migrate to latest schema version.
        """

        migrate_user(self)

    def __setattr__(self, name, value):
        """
        Overrides our core __setattr__ because we have to allow for extra
        authentication attributes that would normally get moved to
        unsupported_attrs.
        """

        if (not self._dynamic and hasattr(self, 'unsupported_attrs')
                and not name in self._fields and not name.startswith('_')
                and not name.startswith('$') and not '.' in name
                and name not in ('backend')):
            if not self.unsupported_attrs:
                self.unsupported_attrs = UnsupportedAttrs()
            self.unsupported_attrs.__setattr__(name, value)
        else:
            super(CriptsDocument, self).__setattr__(name, value)

    @property
    def pk(self):
        """
        Return the ObjectId as the primary key.
        """

        return self.id

    def __str__(self):
        """
        This is so request.user returns the username like Django expects,
        not the whole object.
        """

        if self.username:
            return self.username

    # the rest of this taken from the MongoEngine User class.

    def __unicode__(self):
        """
        This is so request.user returns the username like Django expects,
        not the whole object.
        """

        return self.username

    def get_full_name(self):
        """
        Returns the users first and last names, separated by a space.
        """

        full_name = u'%s %s' % (self.first_name or '', self.last_name or '')
        return full_name.strip()

    def is_anonymous(self):
        """
        We do not allow anonymous users.
        """

        return False

    def is_authenticated(self):
        """
        If we know about the user from the request, it means they've
        authenticated.
        """

        return True

    def mark_active(self, analyst=None):
        """
        Mark the user as active.
        """

        self.is_active = True
        self.save(username=analyst)
        return self

    def mark_inactive(self, analyst=None):
        """
        Deactivate the user.
        """

        self.is_active = False
        self.save(username=analyst)
        return self

    def is_password_complex(self, password):
        """
        Based on the CRIPTsConfig, is the password provided complex enough to be
        used?

        :param password: The password to check for complexity.
        :type password: str
        :returns: True, False
        """

        cripts_config = CRIPTsConfig.objects().first()
        if cripts_config:
            pw_regex = cripts_config.password_complexity_regex
        else:
            pw_regex = settings.PASSWORD_COMPLEXITY_REGEX
        complex_regex = re.compile(pw_regex)
        if complex_regex.match(password):
            return True
        return False

    def set_password(self, raw_password, analyst=None):
        """
        Sets the user's password - always use this rather than directly
        assigning to :attr:`~mongoengine.django.auth.User.password` as the
        password is hashed before storage.

        :param raw_password: The password to hash and store.
        :type raw_password: str
        :returns: self, False
        """

        if self.is_password_complex(raw_password):
            self.password = make_password(raw_password)
            self.save(username=analyst)
            return self
        else:
            return False

    def set_reset_code(self, analyst):
        """
        Sets a reset code on the account for password reset validation.

        :returns: str
        """

        e = EmbeddedPasswordReset()
        char_set = string.ascii_uppercase + string.digits
        e.reset_code = ''.join(random.sample(char_set * 6, 6))
        e.date = datetime.datetime.now()
        self.password_reset = e
        self.save(username=analyst)
        return e.reset_code

    def reset_password(self, rcode, new_p, new_p_c, analyst):
        """
        Reset the user's password. Validate the reset code, ensure the two
        passwords are identical, and then set.

        :param rcode: Reset Code to validate.
        :type rcode: str
        :param new_p: New password.
        :type new_p: str
        :param new_p_c: New password confirmation.
        :type new_p_c: str
        :param analyst: The user.
        :type analyst: str
        :returns: dict with keys "success" (boolean) and "message" (str).
        """

        if self.validate_reset_code(rcode, analyst)['success']:
            if new_p == new_p_c:
                self.password_reset.reset_code = ""
                if self.set_password(new_p):
                    return {'success': True, 'message': 'Password reset.'}
                else:
                    cripts_config = CRIPTsConfig.objects().first()
                    if cripts_config:
                        pw_desc = cripts_config.password_complexity_desc
                    else:
                        pw_desc = settings.PASSWORD_COMPLEXITY_DESC
                    message = 'Password not complex enough: %s' % pw_desc
                    return {'success': False, 'message': message}
            else:
                return {'success': False, 'message': 'Passwords do not match.'}
        else:
            self.password_reset.reset_code = ""
            self.save(username=analyst)
            return {'success': False, 'message': 'Reset Code Expired.'}

    def validate_reset_code(self, reset_code, analyst):
        """
        Validate the reset code. Also ensure that the reset code hasn't expired
        already since it is a limited-time use reset.

        :param reset_code: The reset code.
        :type reset_code: str
        :param analyst: The user.
        :type analyst: str
        :returns: dict with keys "success" (boolean) and "message" (str).
        """

        my_reset = self.password_reset.reset_code
        if len(reset_code) == 6 and len(
                my_reset) == 6 and my_reset == reset_code:
            date = datetime.datetime.now()
            diff = date - self.password_reset.date
            window = divmod(diff.days * 86400 + diff.seconds, 60)
            if window[0] < 5:
                self.password_reset.attempts = 0
                self.save(username=analyst)
                return {'success': True, 'message': 'Reset Code Validated.'}
            else:
                self.password_reset.attempts += 1
                self.password_reset.reset_code = ""
                self.save(username=analyst)
                return {'success': False, 'message': 'Reset Code Expired.'}
        self.password_reset.attempts += 1
        if self.password_reset.attempts > 2:
            self.password_reset.date = self.password_reset.date + datetime.timedelta(
                minutes=-5)
            self.save(username=analyst)
            return {'success': False, 'message': 'Reset Code Expired.'}
        self.save(username=analyst)
        return {'success': False, 'message': 'Reset Code Invalid.'}

    def check_password(self, raw_password):
        """
        Checks the user's password against a provided password - always use
        this rather than directly comparing to
        :attr:`~mongoengine.django.auth.User.password` as the password is
        hashed before storage.
        """

        return check_password(raw_password, self.password)

    def create_api_key(self, name, analyst, default=False):
        """
        Generate an API key for the user. It will require a name as we allow for
        unlimited API keys and users need a way to reference them.

        :param name: The name for the API key.
        :type name: str
        :param analyst: The user.
        :type analyst: str
        :param default: Use as default API key.
        :type default: boolean
        :returns: dict with keys "success" (boolean) and "message" (str).
        """

        if not name:
            return {'success': False, 'message': 'Need a name'}
        new_uuid = uuid.uuid4()
        key = hmac.new(new_uuid.bytes, digestmod=sha1).hexdigest()
        ea = EmbeddedAPIKey(name=name, api_key=key, default=default)
        if len(self.api_keys) < 1:
            ea.default = True
        self.api_keys.append(ea)
        self.save(username=analyst)
        return {
            'success': True,
            'message': {
                'name': name,
                'key': key,
                'date': str(ea.date)
            }
        }

    def default_api_key(self, name, analyst):
        """
        Make an API key the default key for a user. The default key is used for
        situations where the user is not or cannot be asked which API key to
        use.

        :param name: The name of the API key.
        :type name: str
        :param analyst: The user.
        :type analyst: str
        :returns: dict with keys "success" (boolean) and "message" (str).
        """

        c = 0
        for key in self.api_keys:
            if key.name == name:
                self.api_keys[c].default = True
            else:
                self.api_keys[c].default = False
            c += 1
        self.save(username=analyst)
        return {'success': True}

    def revoke_api_key(self, name, analyst):
        """
        Revoke an API key so it can no longer be used.

        :param name: The name of the API key.
        :type name: str
        :param analyst: The user.
        :type analyst: str
        :returns: dict with keys "success" (boolean) and "message" (str).
        """

        keys = self.api_keys
        keyslen = len(keys)
        self.api_keys = [k for k in keys if k.name != name]
        if keyslen > len(self.api_keys):
            self.save(username=analyst)
            return {'success': True}
        else:
            return {'success': False, 'message': 'Key not found.'}

    def get_api_key(self, name):
        """
        Get the API key.

        :param name: The name of the API key.
        :type name: str
        :returns: str, None
        """

        for key in self.api_keys:
            if key.name == name:
                return key.api_key
        return None

    def validate_api_key(self, key):
        """
        Validate that the API key exists for this user.

        :param key: The API key.
        :type key: str
        :returns: True, False
        """

        for keys in self.api_keys:
            if keys.api_key == key:
                return True
        return False

    @classmethod
    def create_user(cls, username, password=None, email=None, analyst=None):
        """
        Create (and save) a new user with the given username, password and
        email address.
        """

        now = datetime.datetime.now()

        # Normalize the address by lowercasing the domain part of the email
        # address.
        if email is not None:
            try:
                email_name, domain_part = email.strip().split('@', 1)
            except ValueError:
                return None
            else:
                email = '@'.join([email_name, domain_part.lower()])

        user = cls(username=username, email=email, date_joined=now)
        if password is not None and user.set_password(password):
            user.save(username=analyst)
            return user
        elif CRIPTsConfig.remote_user:
            user.save(username="******")
            return user
        else:
            return None

    def get_group_permissions(self, obj=None):
        """
        Returns a list of permission strings that this user has through his/her
        groups. This method queries all available auth backends. If an object
        is passed in, only permissions matching this object are returned.
        """
        permissions = set()
        for backend in auth.get_backends():
            if hasattr(backend, "get_group_permissions"):
                permissions.update(backend.get_group_permissions(self, obj))
        return permissions

    def get_all_permissions(self, obj=None):
        return _user_get_all_permissions(self, obj)

    def has_perm(self, perm, obj=None):
        """
        Returns True if the user has the specified permission. This method
        queries all available auth backends, but returns immediately if any
        backend returns True. Thus, a user who has permission from a single
        auth backend is assumed to have permission in general. If an object is
        provided, permissions for this specific object are checked.
        """

        # Active superusers have all permissions.
        if self.is_active and self.is_superuser:
            return True

        # Otherwise we need to check the backends.
        return _user_has_perm(self, perm, obj)

    def has_module_perms(self, app_label):
        """
        Returns True if the user has any permissions in the given app label.
        Uses pretty much the same logic as has_perm, above.
        """
        # Active superusers have all permissions.
        if self.is_active and self.is_superuser:
            return True

        return _user_has_module_perms(self, app_label)

    def email_user(self, subject, message, from_email=None):
        """
        Sends an e-mail to this User.
        """

        from django.core.mail import send_mail
        if not from_email:
            cripts_config = CRIPTsConfig.objects().first()
            if cripts_config:
                from_email = cripts_config.cripts_email
        send_mail(subject, message, from_email, [self.email])

    def get_username(self):
        return self.username

    def get_preference(self, section, setting, default=None):
        """
        Get a user preference setting out of the deep dynamic dictionary
        'section' is the preferences 'section' e.g. 'ui'
        'setting' is the dot separated preference setting  e.g. 'foo.bar.enabled'

        :param section: A specific section of preferences you want.
        :type section: str
        :param setting: The setting you want to get.
        :type setting: str
        :returns: None, str, dict
        """

        if not section in self.prefs:
            return default

        # Split the preference option into subtrees on '.'
        otree = setting.split(".")
        param = otree.pop()
        opt = self.prefs[section]

        if len(otree):
            for subsect in otree:
                if subsect in opt:
                    opt = opt[subsect]
                else:
                    return default

        if not param in opt:
            return default

        return opt[param]

    def update_from_ldap(self, analyst, config=None, passw=''):
        """
        Set First Name, Last Name, and Email from LDAP if we can get the data.
        """

        info = self.info_from_ldap(config, passw)
        if info['result'] == "OK":
            self.first_name = info['first_name']
            self.last_name = info['last_name']
            self.email = info['email']
            self.save(username=analyst)

    def info_from_ldap(self, config=None, password=''):
        """
        Get information about this user from LDAP.
        """

        import ldap, ldapurl
        resp = {"result": "ERROR"}
        if not config:
            config = CRIPTsConfig.objects().first()
        # Make sure we have the rquired settings, else return failure
        if not config.ldap_server or not config.ldap_userdn:
            return resp
        ldap_server = config.ldap_server.split(':')
        scheme = "ldap"
        if config.ldap_tls:
            scheme = "ldaps"
        url = ldapurl.LDAPUrl('%s://%s' % (scheme, ldap_server[0]))
        if len(ldap_server) == 2:
            l = ldap.initialize('%s:%s' % (url.unparse(), ldap_server[1]))
        else:
            l = ldap.initialize(url.unparse())
        l.protocol_version = 3
        l.set_option(ldap.OPT_REFERRALS, 0)
        l.set_option(ldap.OPT_TIMEOUT, 10)
        # setup auth for custom cn's
        cn = "cn="
        if config.ldap_usercn:
            cn = config.ldap_usercn
        # two-step ldap binding
        if len(config.ldap_bind_dn) > 0:
            try:
                logger.info("binding with bind_dn: %s" % config.ldap_bind_dn)
                l.simple_bind_s(config.ldap_bind_dn, config.ldap_bind_password)
                filter = '(|(cn=' + self.username + ')(uid=' + self.username + ')(mail=' + self.username + '))'
                # use the retrieved dn for the second bind
                un = l.search_s(config.ldap_userdn, ldap.SCOPE_SUBTREE, filter,
                                ['dn'])[0][0]
            except Exception as err:
                #logger.error("Error binding to LDAP for: %s" % config.ldap_bind_dn)
                logger.error("Error in info_from_ldap: %s" % err)
            l.unbind()
            if len(ldap_server) == 2:
                l = ldap.initialize('%s:%s' % (url.unparse(), ldap_server[1]))
            else:
                l = ldap.initialize(url.unparse())
            l.protocol_version = 3
            l.set_option(ldap.OPT_REFERRALS, 0)
            l.set_option(ldap.OPT_TIMEOUT, 10)
        else:
            un = self.username
        # setup auth for custom cn's
        if len(config.ldap_usercn) > 0:
            un = "%s%s,%s" % (config.ldap_usercn, self.username,
                              config.ldap_userdn)
        elif "@" in config.ldap_userdn:
            un = "%s%s" % (self.username, config.ldap_userdn)
        try:
            # Try auth bind first
            l.simple_bind_s(un, password)
            logger.info("Bound to LDAP for: %s" % un)
        except Exception as e:
            #logger.error("Error binding to LDAP for: %s" % self.username)
            logger.error("info_from_ldap:ERR: %s" % e)
        try:
            uatr = None
            uatr = l.search_s(
                config.ldap_userdn, ldap.SCOPE_SUBTREE, '(|(cn=' +
                self.username + ')(uid=' + self.username + '))')[0][1]
            resp['first_name'] = uatr['givenName'][0]
            resp['last_name'] = uatr['sn'][0]
            resp['email'] = uatr['mail'][0]
            resp['result'] = "OK"
            logger.info("Retrieved LDAP info for: %s" % self.username)
        except Exception as e:
            #logger.error("Error retrieving LDAP info for: %s" % self.username)
            logger.error("info_from_ldap ERR: %s" % e)
        l.unbind()
        return resp

    def getDashboards(self):
        from cripts.dashboards.handlers import getDashboardsForUser
        return getDashboardsForUser(self)