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)
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)
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)