def add_authorizedkey(self, key, comment=None): """ Add the given key to the user. Adding the key to his `authorized_keys` file if it exists and adding it to database. """ # Parse and validate ssh key assert key key = authorizedkeys.check_publickey(key) if not key: raise ValueError(_("Invalid SSH key.")) # Remove option, replace comments. key = authorizedkeys.AuthorizedKey(options=None, keytype=key.keytype, key=key.key, comment=comment or key.comment) # If a filename exists, use it by default. filename = os.path.join(self.user_root, '.ssh', 'authorized_keys') if os.path.isfile(filename): with open(filename, mode="r+", encoding='utf-8') as fh: if authorizedkeys.exists(fh, key): raise ValueError(_("SSH key already exists")) logger.info("add key [%s] to [%s] authorized_keys", key, self.username) authorizedkeys.add(fh, key) else: # Also look in database. logger.info("add key [%s] to [%s] database", key, self.username) self._db.add_authorizedkey(self._username, fingerprint=key.fingerprint, key=key.getvalue()) self._userdb._notify('user_attr_changed', self, {'authorizedkeys': True})
def _handle_set_password(self, **kwargs): """ Called when changing user password. """ if 'current' not in kwargs or not kwargs['current']: raise RdiffWarning(_("Current password is missing.")) if 'new' not in kwargs or not kwargs['new']: raise RdiffWarning(_("New password is missing.")) if 'confirm' not in kwargs or not kwargs['confirm']: raise RdiffWarning(_("Confirmation password is missing.")) # Check if confirmation is valid. if kwargs['new'] != kwargs['confirm']: return { 'error': _("The new password and its confirmation do not match.") } # Update user password try: self.app.currentuser.set_password(kwargs['new'], old_password=kwargs['current']) return {'success': _("Password updated successfully.")} except ValueError as e: return {'warning': str(e)}
def _handle_set_password(self, **kwargs): """ Called when changing user password. """ if 'current' not in kwargs or not kwargs['current']: raise RdiffWarning(_("Current password is missing.")) if 'new' not in kwargs or not kwargs['new']: raise RdiffWarning(_("New password is missing.")) if 'confirm' not in kwargs or not kwargs['confirm']: raise RdiffWarning(_("Confirmation password is missing.")) # Check if confirmation is valid. if kwargs['new'] != kwargs['confirm']: return { 'error': _("The new password and its confirmation do not match.") } # Update user password user = self.app.currentuser.username _logger.info("updating user [%s] password", user) self.app.userdb.set_password(user, kwargs['new'], old_password=kwargs['current']) return {'success': _("Password updated successfully.")}
def _users_handle_action(self, action, username, email, password, user_root, is_admin): success = "" # We need to change values. Change them, then give back that main # page again, with a message if username == self.app.currentuser.username: # Don't allow the user to changes it's "admin" state. is_admin = self.app.currentuser.is_admin is_admin = str(is_admin).lower() in ['on', 'true', '1'] # Fork the behaviour according to the action. if action == "edit": user = self.app.userdb.get_user(username) logger.info("updating user [%s] info", user) if password: self.app.userdb.set_password(username, password, old_password=None) user.user_root = user_root user.is_admin = is_admin # Avoid updating the email fields is it didn'T changed. see pdsl/minarca#187 if email != user.email: user.email = email success = _("User information modified successfully.") # Check and update user directory if user.user_root: self._check_user_root_dir(user.user_root) rdw_spider_repos.find_repos_for_user(user) elif action == "add": if username == "": raise RdiffWarning(_("The username is invalid.")) logger.info("adding user [%s]", username) user = self.app.userdb.add_user(username, password) if user_root: user.user_root = user_root user.is_admin = is_admin user.email = email # Check and update user directory if user.user_root: self._check_user_root_dir(user.user_root) rdw_spider_repos.find_repos_for_user(user) success = _("User added successfully.") if action == "delete": user = self.app.userdb.get_user(username) if username == self.app.currentuser.username: raise RdiffWarning(_("You cannot remove your own account!.")) logger.info("deleting user [%s]", username) self.app.userdb.delete_user(user) success = _("User account removed.") # Return messages return {'success': success}
def get_hwinfo(): if hasattr(os, 'getloadavg'): yield _('Load Average'), ', '.join( map(str, map(lambda x: round(x, 2), os.getloadavg()))) yield _('CPU Count'), psutil.cpu_count() meminfo = psutil.virtual_memory() yield _('Memory usage'), '%s / %s' % (filesize( meminfo.used), filesize(meminfo.total))
def _set_encoding(self, repo_obj, new_encoding=None, **kwargs): """ Update repository encoding via Ajax. """ validate(new_encoding) try: repo_obj.encoding = new_encoding except ValueError: raise cherrypy.HTTPError(400, _("invalid encoding value")) return _("Updated")
def check_username_and_password(self, username, password): """Validate user credentials.""" logger.debug("check credentials for [%s]", username) try: userobj = cherrypy.request.app.userdb.login( username, password) # @UndefinedVariable except: logger.exception("fail to validate user credential") raise RdiffWarning(_("Fail to validate user credential.")) if not userobj: logger.warning("invalid username [%s] or password", username) raise RdiffWarning(_("Invalid username or password.")) return userobj
def get_osinfo(): def gr_name(gid): try: return grp.getgrgid(gid).gr_name except: return def pw_name(uid): try: return pwd.getpwuid(os.getuid()).pw_name except: return if hasattr(sys, 'getfilesystemencoding'): yield _('File System Encoding'), sys.getfilesystemencoding() if hasattr(os, 'getcwd'): yield _('Current Working Directory'), os.getcwd() if hasattr(os, 'getegid'): yield _('Effective Group'), '%s (%s)' % (os.getegid(), gr_name(os.getegid())) if hasattr(os, 'geteuid'): yield _('Effective User'), '%s (%s)' % (os.geteuid(), pw_name(os.geteuid)) if hasattr(os, 'getgid'): yield _('Group'), '%s (%s)' % (os.getgid(), gr_name(os.getgid())) if hasattr(os, 'getuid'): yield _('User'), '%s (%s)' % (os.getuid(), gr_name(os.getuid())) if hasattr(os, 'getgroups'): yield _('Group Membership'), ', '.join(['%s (%s)' % (gid, gr_name(gid)) for gid in os.getgroups()]) try: if hasattr(os, 'getpid') and hasattr(os, 'getppid'): yield _('Process ID'), ('%s (parent: %s)' % (os.getpid(), os.getppid())) except: pass
def set_password(self, username, password, old_password=None): assert isinstance(username, str) assert old_password is None or isinstance(old_password, str) assert isinstance(password, str) if not password: raise RdiffError(_("Password can't be empty.")) # Check old password value. if old_password and not self.are_valid_credentials( username, old_password): raise RdiffError(_("Wrong password.")) # Update password. self._set_user_field(username, 'Password', self._hash_password(password))
def status(self): """Check if a backup is in progress for the current repo.""" if hasattr(self, '_status'): return self._status # Check if the repository exists. # Make sure repoRoot is a valid rdiff-backup repository if (not os.access(self._data_path, os.F_OK) or not os.path.isdir(self._data_path)): self._status = ( 'failed', _('The repository cannot be found or is badly damaged.')) return self._status pid_re = re.compile(b"^PID\s*([0-9]+)", re.I | re.M) def extract_pid(current_mirror): """Return process ID from a current mirror marker, if any""" entry = IncrementEntry(self, current_mirror) match = pid_re.search(entry.read()) if not match: return None else: return int(match.group(1)) # Read content of the file and check if pid still exists for current_mirror in self._current_mirrors: pid = extract_pid(current_mirror) try: p = psutil.Process(pid) if any('rdiff-backup' in c for c in p.cmdline()): self._status = ( 'in_progress', _('A backup is currently in progress to this repository.' )) return self._status except psutil.NoSuchProcess: logger.debug('pid [%s] does not exists', pid) pass # If multiple current_mirror file exists and none of them are associated to a PID, this mean the last backup was interrupted. # Also, if the last backup date is undefined, this mean the first initial backup was interrupted. if len(self._current_mirrors) > 1 or not self.last_backup_date: self._status = ('interrupted', _('The previous backup seams to have failed.')) return self._status self._status = ('ok', '') return self._status
def _set_maxage(self, repo_obj, maxage=None, **kwargs): """ Update repository maxage via Ajax. """ validate_int(maxage) repo_obj.maxage = maxage return _("Updated")
def add_user(self, user, password=None, attrs=None): """ Used to add a new user with an optional password. """ assert password is None or isinstance(password, str) # Check if user already exists. if self.get_user(user): raise RdiffError(_("User %s already exists." % (user, ))) # Find a database where to add the user logger.debug("adding new user [%s]", user) if password: inserted = self._database.insert('users', username=user, password=_hash_password(password)) else: inserted = self._database.insert('users', username=user, password='') assert inserted record = self._database.findone('users', username=user) userobj = UserObject(self, record) self._notify('user_added', userobj, attrs) # Return user object return userobj
class NotificationPref(Controller): panel_id = 'notification' panel_name = _('Notification') def _handle_set_notification_info(self, **kwargs): # Loop trough user repo and update max age. for repo in self.app.currentuser.repo_objs: # Get value received for the repo. value = kwargs.get(repo.name, None) if value: # Update the maxage repo.maxage = validate_int(value) def render_prefs_panel(self, panelid, action=None, **kwargs): # @UnusedVariable # Process the parameters. if action == "set_notification_info": self._handle_set_notification_info(**kwargs) params = { 'email': self.app.currentuser.email, 'repos': self.app.currentuser.repo_objs, } return "prefs_notification.html", params
def send_notifications(self): """ Loop trough all the user repository and send notifications. """ now = librdiff.RdiffTime() def _user_repos(): """Return a generator trought user repos to be notified.""" for user in self.app.store.users(): # Check if user has email. if not user.email: continue # Identify old repo for current user. old_repos = [] for repo in user.repo_objs: # Check if repo has age configured (in days) maxage = repo.maxage if not maxage or maxage <= 0: continue # Check repo age. if repo.last_backup_date < ( now - datetime.timedelta(days=maxage)): old_repos.append(repo) # Return an item only if user had old repo if old_repos: yield user, old_repos # For each candidate, send mail. for user, repos in _user_repos(): parms = {'user': user, 'repos': repos} self.send_mail(user, _('Notification'), 'email_notification.html', **parms)
def render_prefs_panel(self, panelid, **kwargs): # @UnusedVariable # Handle action params = {} if 'action' in kwargs: try: action = kwargs['action'] if action == 'add': self._handle_add(**kwargs) elif action == 'delete': self._handle_delete(**kwargs) except RdiffWarning as e: params['warning'] = str(e) except RdiffError as e: params['error'] = str(e) # Get SSH keys if file exists. params["sshkeys"] = [] try: params["sshkeys"] = [ {'title': key.comment or (key.keytype + ' ' + key.key[:18]), 'fingerprint': key.fingerprint} for key in self.app.currentuser.authorizedkeys] except IOError: params['error'] = _("error reading SSH keys file") _logger.warning("error reading SSH keys", exc_info=1) return "prefs_sshkeys.html", params
def render_prefs_panel(self, panelid, **kwargs): # @UnusedVariable # Process the parameters. params = dict() action = kwargs.get('action') if action: try: if action == "set_profile_info": params = self._handle_set_profile_info(**kwargs) elif action == "set_password": params = self._handle_set_password(**kwargs) elif action == "update_repos": params = self._handle_update_repos() else: _logger.info("unknown action: %s", action) raise cherrypy.NotFound("Unknown action") except RdiffWarning as e: params['warning'] = str(e) except RdiffError as e: params['error'] = str(e) except Exception as e: _logger.warning("unknown error processing action", exc_info=True) params['error'] = _("Unknown error") params.update({ 'email': self.app.currentuser.email, }) return "prefs_general.html", params
def set_password(self, password, old_password=None): """ Change the user's password. Raise a ValueError if the username or the password are invalid. """ assert isinstance(password, str) assert old_password is None or isinstance(old_password, str) if not password: raise ValueError("password can't be empty") # Try to update the user password in LDAP for store in self._store._password_stores: try: valid = store.are_valid_credentials(self.username, old_password) if valid: store.set_password(self.username, password, old_password) return except: pass # Fallback to database if old_password and self._get_attr('password') != _hash_password( old_password): raise ValueError(_("Wrong password")) self._set_attr('password', 'password', _hash_password(password)) self._store._notify('user_password_changed', self.username, password)
def get_pyinfo(): if platform.dist()[0] != '' and platform.dist()[1] != '': yield _('OS Version'), '%s %s (%s %s)' % (platform.system(), platform.release(), platform.dist()[0].capitalize(), platform.dist()[1]) else: yield _('OS Version'), '%s %s' % (platform.system(), platform.release()) if hasattr(os, 'path'): yield _('OS Path'), os.environ['PATH'] if hasattr(sys, 'version'): yield _('Python Version'), ''.join(sys.version) if hasattr(sys, 'subversion'): yield _('Python Subversion'), ', '.join(sys.subversion) if hasattr(sys, 'prefix'): yield _('Python Prefix'), sys.prefix if hasattr(sys, 'executable'): yield _('Python Executable'), sys.executable if hasattr(sys, 'path'): yield _('Python Path'), ', '.join(sys.path)
def _set_encoding(self, value): """Change the repository encoding""" # Validate if the value is a valid encoding before updating the database. codec = encodings.search_function(value.lower()) if not codec: raise ValueError(_('invalid encoding %s') % value) logger.info("updating repository %s encoding %s", self, codec.name) self._set_attr('encoding', codec.name) self._encoding = codec
def _handle_delete(self, **kwargs): """ Called for delete a key from an authorized_keys file. """ assert kwargs.get('key') , "key is missing" try: self.app.currentuser.remove_authorizedkey(kwargs['key']) except: _logger.warn("error removing ssh key", exc_info=1) raise RdiffWarning(_("Unknown error while removing the SSH Key"))
def check_crendential(l, r): # Check results if len(r) != 1: logger.debug("user [%s] not found in LDAP", username) return None # Bind using the user credentials. Throws an exception in case of # error. l.simple_bind_s(r[0][0], password) try: logger.info("user [%s] found in LDAP", username) # Verify the shadow expire if self.check_shadow_expire: shadow_expire = self._attr_shadow_expire(r) # Convert nb. days into seconds. if shadow_expire and shadow_expire * 24 * 60 * 60 < time.time( ): logger.warn("user account %s expired: %s", username, shadow_expire) raise RdiffError( _('User account %s expired.' % username)) # Get username dn = r[0][0] new_username = self._decode(r[0][1][self.attribute][0]) # Verify if the user is member of the required group if self.require_group: value = dn if self.group_attribute_is_dn else new_username logger.info("check if user [%s] is member of [%s]", value, self.require_group) if not l.compare_s(self.require_group, self.group_attribute, value): raise RdiffError( _('Permissions denied for user account %s.' % username)) finally: l.unbind_s() # Return the username return new_username, r[0][1]
def change_passwd(l, r): if len(r) != 1: raise RdiffError(_("User %s not found." % (username, ))) # Bind using the user credentials. Throws an exception in case of # error. if old_password is not None: l.simple_bind_s(r[0][0], old_password) l.passwd_s(r[0][0], old_password, password) l.unbind_s() logger.info("password for user [%s] is updated in LDAP", username) # User updated, return False return False
def _execute(self, username, function): assert isinstance(username, str) """Reusable method to run LDAP operation.""" assert self.uri, "LdapUri must be define in configuration" assert self.base_dn, "LdapBaseDn must be define in configuration" if self.scope == "base": scope = ldap.SCOPE_BASE elif self.scope == "onelevel": scope = ldap.SCOPE_ONELEVEL else: scope = ldap.SCOPE_SUBTREE # try STARTLS if configured if self.tls: ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) # Check LDAP credential only. l = ldap.initialize(self.uri) # Set v2 or v3 if self.version == 2: l.protocol_version = ldap.VERSION2 else: l.protocol_version = ldap.VERSION3 try: # Bind to the LDAP server logger.debug("binding to ldap server {}".format(self.uri)) l.simple_bind_s(self.bind_dn, self.bind_password) # Search the LDAP server search_filter = "(&{}({}={}))".format(self.filter, self.attribute, username) logger.debug("search ldap server: {}/{}?{}?{}?{}".format( self.uri, self.base_dn, self.attribute, scope, search_filter)) r = l.search_s(self.base_dn, scope, search_filter) # Execute operation return function(l, r) except ldap.LDAPError as e: l.unbind_s() # Handle the LDAP exception and build a nice user message. logger.warning('ldap error', exc_info=1) msg = _("An LDAP error occurred: %s") ldap_msg = str(e) if hasattr(e, 'message') and isinstance(e.message, dict): if 'desc' in e.message: ldap_msg = e.message['desc'] if 'info' in e.message: ldap_msg = e.message['info'] raise RdiffError(msg % ldap_msg)
def _handle_set_profile_info(self, **kwargs): """ Called when changing user profile. """ # Check data. if 'email' not in kwargs: raise RdiffWarning(_("Email is undefined.")) # Parse the email value to extract a valid email. The following method # return an empty string if the email is not valid. This RFC also accept # local email address without '@'. So we add verification for '@' if not PATTERN_EMAIL.match(kwargs['email'].lower()): raise RdiffWarning(_("Invalid email.")) # Update the user's email assert self.app.currentuser username = self.app.currentuser.username email = kwargs['email'] _logger.info("updating user [%s] email [%s]", username, email) self.app.currentuser.email = kwargs['email'] return {'success': _("Profile updated successfully.")}
def _handle_add(self, **kwargs): """ Called to add a new key to an authorized_keys file. """ assert 'key' in kwargs, "key is missing" # Add the key to the current user. try: self.app.currentuser.add_authorizedkey(key=kwargs['key'], comment=kwargs.get('title', None)) except ValueError as e: _logger.warn("error adding ssh key", exc_info=1) raise RdiffWarning(str(e)) except: _logger.error("error adding ssh key", exc_info=1) raise RdiffWarning(_("Unknown error while adding the SSH Key"))
def set_password(self, username, password, old_password=None): """Update the password of the given user.""" assert isinstance(username, str) assert old_password is None or isinstance(old_password, str) assert isinstance(password, str) # Do nothing if password is empty if not password: raise RdiffError(_("Password can't be empty.")) # Check if users are allowed to change their password in LDAP. if not self.allow_password_change: logger.warn( "authentication backend for user [%s] does not support changing the password", username) raise RdiffError( _("LDAP users are not allowed to change their password.")) # Check if old_password id valid if old_password and not self.are_valid_credentials( username, old_password): raise RdiffError(_("Wrong password.")) # Update the username password of the given user. If possible. return self._set_password_in_ldap(username, old_password, password)
def user_password_changed(self, username, password): """ Implementation of IUserChangeListener interface. """ # get User object (to get email) userobj = self.app.store.get_user(username) assert userobj if not userobj.email: logging.info("can't sent mail to user [%s] without an email", userobj.username) return # If the email attributes was changed, send a mail notification. self.send_mail(userobj, _("Password changed"), "password_changed.html")
def add_user(self, user, password=None, attrs=None): """ Used to add a new user with an optional password. """ assert password is None or isinstance(password, str) # Check if user already exists. if self._database.exists(user): raise RdiffError(_("User %s already exists." % (user, ))) # Find a database where to add the user logger.debug("adding new user [%s]", user) self._database.add_user(user, password) userobj = UserObject(self, self._database, user) self._notify('user_added', userobj, attrs) # Return user object return userobj
def add_authorizedkey(self, username, fingerprint, key): assert isinstance(username, str) assert fingerprint assert key # Query user user_id = self._get_user_id(username) assert user_id, "user [%s] doesn't exists" % username try: self._execute_query( "INSERT INTO sshkeys (UserID, Fingerprint, Key) values (?, ?, ?)", (user_id, fingerprint, key)) except sqlite3.IntegrityError: # @UndefinedVariable raise ValueError( _("Duplicate key. This key already exists or is associated to another user." ))
def user_attr_changed(self, userobj, attrs={}): """ Implementation of IUserChangeListener interface. """ if not self._send_change_notification: return # Leave if the mail was not changed. if 'email' not in attrs: return if not userobj.email: logging.info("can't sent mail to user [%s] without an email", userobj.username) return # If the email attributes was changed, send a mail notification. self.send_mail(userobj, _("Email address changed"), "email_changed.html")