def get_profile_actions(self, req, user): """ Return list of actions """ actions = [] homeperm = self._get_home_perm(req) uresource = Resource('user', user.id) # Project settings for own account if user.username == req.authname: actions.append((-40, tag.a(_('View your projects'), href=homeperm.env.href('myprojects')))) actions.append((0, tag.a(_('Edit your settings'), href=homeperm.env.href('prefs')))) # View other user profile else: actions.append((-50, tag.a(_('View service profile'), href=homeperm.env.href('user', user.username)))) # If user can fully manage account if homeperm.has_permission('USER_EDIT', uresource): label = 'Manage your account' if user.username == req.authname else 'Manage account' actions.append((-2, tag.a(_(label), href=homeperm.env.href('admin/users/manage', username=user.username)))) # Tickets assigned to or created by user (if component is enabled and user has permissions to view them) # Note: href.kwargs cannot be used because of reserved word 'or' and un-ordered nature of dict if (self.env.is_component_enabled('trac.ticket.query.QueryModule') or self.env.is_component_enabled('multiproject.project.tickets.viewtickets.QueryModuleInterceptor')) \ and 'TICKET_VIEW' in req.perm: qstring = 'owner={0}&or&reporter={0}&group=status'.format(user.username) query = Query.from_string(self.env, qstring) actions.append((5, (tag.a(_('View tickets'), href=query.get_href(req.href))))) return actions
def _do_save(self, req, user): """ Update user information into database """ userstore = get_userstore() if not req.args.get('mail'): add_warning(req, _('User should have an e-mail address')) return user user.mail = req.args.get('mail') if not req.args.get('lastName'): add_warning(req, _('Last name required')) return user # NOTE: Values are escaped in userstore update user.lastName = req.args.get('lastName') user.givenName = req.args.get('givenName') user.mobile = req.args.get('mobile') if userstore.updateUser(user): user = userstore.getUser(user.username) add_notice(req, _('Updated user settings')) if req.args.get( 'approve') == 'on' and user.status == user.STATUS_INACTIVE: user.activate() add_notice(req, _("Your account is now activated.")) return user add_warning(req, _('Failed to update user')) return user
def _do_save(self, req, user): """ Save ssh key into database """ if not req.args.get('ssh_key'): add_warning(req, _('Failed to add SSH key: Key is required')) return user ssh_key = req.args.get('ssh_key') ssh_key = SshKey.remove_comment_from_key_string(ssh_key) if not SshKey.validate_key_string(ssh_key): add_warning( req, _('Failed to add SSH key: invalid SSH key, key must be in open ssh format' )) return user description = req.args.get('description') if len(description) > 200: add_warning(req, _('Failed to add SSH key: Too long description')) return user key_store = CQDESshKeyStore.instance() user_id = user.id if key_store.add_ssh_key(user_id, ssh_key, description): add_notice( req, _('New SSH key added (please allow 5 minutes for replication)') ) return user add_warning(req, _('Failed to add SSH key: Server error')) return user
def changePassword(self, req): userstore = get_userstore() user = userstore.getUser(req.authname) oldpw = req.args.get('oldpassword') newpw = req.args.get('newpassword') if not oldpw or not userstore.userExists(req.authname, oldpw): add_warning(req, _('Old password is invalid')) return user if not newpw or len(newpw) < 7: add_warning(req, _('New password should be at least 7 characters long')) return user if newpw != req.args.get('confirmpw'): add_warning(req, _('Passwords do not match')) return user if not userstore.updatePassword(user, newpw): add_warning(req, _('Failed to change the password')) return user add_notice(req, _('Password changed')) return user
def expand_macro(self, formatter, name, content, args=None): """ Returns the outcome from macro. Supported arguments: - project: Name of the project to show status / provide follow buttons. Defaults to current project """ req = formatter.req # Parse optional arguments if args is None: args = parse_args(content) if len(args) > 1: args = args[1] # Read optional project name - fallback to current project_name = self.env.project_identifier if args and 'project' in args: project_name = args.get('project', '').strip() # Load project id from db project_id = Project.get(env_name=project_name).id watchers, is_watching = self._get_status(req, project_id) # If user is already watching, do not show the block if is_watching: return tag.div('') # Show macro only when user has permission to view timeline if name not in self.macros or 'TIMELINE_VIEW' not in req.perm or not project_id: # Return default content / instructions return tag.div( tag.h2(_('Follow project'), **{'class': 'title'}), tag.p(_('Project cannot be found or no permission to follow it')), **{'class': 'watch'} ) # For anonymous users, advice login/registering if req.authname == 'anonymous': return tag.div( tag.h2(_('Follow project'), **{'class': 'title'}), tag.p(_('Only registered users can follow the project activity. ')), tag.p(tag.a('Please login or register to service first', href=req.href('../home/user'))), **{'class': 'watch'} ) # Return rendered HTML with JS attached to it data = { 'project_id': project_id, 'env_name': self.env.project_identifier, 'project_name': project_name } chrome = Chrome(self.env) stream = chrome.render_template(req, 'multiproject_watch.html', data, fragment=True) if req.form_token: stream |= chrome._add_form_token(req.form_token) return stream
def get_profile_actions(self, req, account): """ Returns """ actions = [] # Check if user has MESSAGE_CREATE in home env home_env = HomeProject().get_env() home_perm = PermissionCache(home_env, req.authname) if 'MESSAGE_CREATE' not in home_perm: return [] # Own account if req.authname == account.username: actions.append((200, tag.a( _('Send message to...'), **{'class': 'messages-dialog', 'href': '#'} ))) else: actions.append((200, tag.a( _('Send message to %s' % account.username), **{'class': 'messages-dialog', 'href': '#user_id=%d' % account.id} ))) return actions
def _do_save(self, req, user): """ Update user information into database """ userstore = get_userstore() if not req.args.get('mail'): add_warning(req, _('User should have an e-mail address')) return user user.mail = req.args.get('mail') if not req.args.get('lastName'): add_warning(req, _('Last name required')) return user # NOTE: Values are escaped in userstore update user.lastName = req.args.get('lastName') user.givenName = req.args.get('givenName') user.mobile = req.args.get('mobile') if userstore.updateUser(user): user = userstore.getUser(user.username) add_notice(req, _('Updated user settings')) if req.args.get('approve') == 'on' and user.status == user.STATUS_INACTIVE: user.activate() add_notice(req, _("Your account is now activated.")) return user add_warning(req, _('Failed to update user')) return user
def _do_save(self, req, user): """ Save ssh key into database """ if not req.args.get('ssh_key'): add_warning(req, _('Failed to add SSH key: Key is required')) return user ssh_key = req.args.get('ssh_key') ssh_key = SshKey.remove_comment_from_key_string(ssh_key) if not SshKey.validate_key_string(ssh_key): add_warning(req, _('Failed to add SSH key: invalid SSH key, key must be in open ssh format')) return user description = req.args.get('description') if len(description) > 200: add_warning(req, _('Failed to add SSH key: Too long description')) return user key_store = CQDESshKeyStore.instance() user_id = user.id if key_store.add_ssh_key(user_id, ssh_key, description): add_notice(req, _('New SSH key added (please allow 5 minutes for replication)')) return user add_warning(req, _('Failed to add SSH key: Server error')) return user
def expand_macro(self, formatter, name, content, args=None): """ Returns the outcome from macro. """ req = formatter.context.req # Check permissions if 'TIMELINE_VIEW' not in req.perm: # Return default content / instructions return tag.div( tag.h2(_('Project team'), **{'class': 'title'}), tag.p(_('Project team cannot be found or no permission to follow it')), **{'class': 'watch'} ) # Load project info from optional project argument. Defaults to current env project = Project.get(self.env) team, members = self._get_team_info(project) # Return rendered HTML with JS attached to it data = { 'project_id': project.id, 'env_name': self.env.project_identifier, 'project_name': self.env.project_identifier, # TODO: redundant 'team': team, 'members': members } # NOTE: Use fragment to not to recreate chrome (add_script does not work) and run post processing manually chrome = Chrome(self.env) stream = chrome.render_template(req, 'multiproject_team.html', data, fragment=True) if req.form_token: stream |= chrome._add_form_token(req.form_token) return stream
def render_preference_panel(self, req, panel): """ Renders preference panel and handles information change on POST """ if req.authname == 'anonymous': raise TracError("User is not authenticated", "No access") data = {} key_store = CQDESshKeyStore.instance() user = get_userstore().getUser(req.authname) if req.method == 'POST': ssh_key = req.args.get('ssh_key') delete_key = req.args.get('deletelist') if ssh_key: user = self._do_save(req, user) elif delete_key: user = self._do_deletes(req, user) else: add_warning(req, _('Please provide data')) keys = key_store.get_ssh_keys_by_user_id(user.id) data['keys'] = keys if keys else None # This is to prevent adding of more than one ssh password. # Remove this if we support more in the future. if keys: data['hide_add_dialog'] = False data['domain'] = conf.domain_name data['user'] = user return 'multiproject_user_prefs_ssh_keys.html', data
def _do_deletes(self, req, user): """ Delete ssh key from database """ key_store = CQDESshKeyStore.instance() user_id = user.id key_ids = req.args.get('deletelist') key_ids = isinstance(key_ids, list) and key_ids or [key_ids] for key_id in key_ids: if key_id: if key_store.remove_ssh_key(user_id, key_id): add_notice(req, _('SSH key deleted')) else: add_warning(req, _('Failed to delete SSH key: Server error.')) return user
def get_profile_actions(self, req, user): """ Return list of actions """ actions = [] homeperm = self._get_home_perm(req) uresource = Resource('user', user.id) # Project settings for own account if user.username == req.authname: actions.append((-40, tag.a(_('View your projects'), href=homeperm.env.href('myprojects')))) actions.append((0, tag.a(_('Edit your settings'), href=homeperm.env.href('prefs')))) # View other user profile else: actions.append( (-50, tag.a(_('View service profile'), href=homeperm.env.href('user', user.username)))) # If user can fully manage account if homeperm.has_permission('USER_EDIT', uresource): label = 'Manage your account' if user.username == req.authname else 'Manage account' actions.append( (-2, tag.a(_(label), href=homeperm.env.href('admin/users/manage', username=user.username)))) # Tickets assigned to or created by user (if component is enabled and user has permissions to view them) # Note: href.kwargs cannot be used because of reserved word 'or' and un-ordered nature of dict if (self.env.is_component_enabled('trac.ticket.query.QueryModule') or self.env.is_component_enabled('multiproject.project.tickets.viewtickets.QueryModuleInterceptor')) \ and 'TICKET_VIEW' in req.perm: qstring = 'owner={0}&or&reporter={0}&group=status'.format( user.username) query = Query.from_string(self.env, qstring) actions.append((5, (tag.a(_('View tickets'), href=query.get_href(req.href))))) return actions
def expand_macro(self, formatter, name, content, args=None): """ Returns the outcome from macro. """ req = formatter.context.req # Check permissions if 'TIMELINE_VIEW' not in req.perm: # Return default content / instructions return tag.div( tag.h2(_('Project team'), **{'class': 'title'}), tag.p( _('Project team cannot be found or no permission to follow it' )), **{'class': 'watch'}) # Load project info from optional project argument. Defaults to current env project = Project.get(self.env) team, members = self._get_team_info(project) # Return rendered HTML with JS attached to it data = { 'project_id': project.id, 'env_name': self.env.project_identifier, 'project_name': self.env.project_identifier, # TODO: redundant 'team': team, 'members': members } # NOTE: Use fragment to not to recreate chrome (add_script does not work) and run post processing manually chrome = Chrome(self.env) stream = chrome.render_template(req, 'multiproject_team.html', data, fragment=True) if req.form_token: stream |= chrome._add_form_token(req.form_token) return stream
def __require_permissions_for_cloning(self, authname, parent_project): """ Checks that user have permissions to clone project :param authname: Current user name :param parent_project: Project class instance :raises: PermissionError if no permission """ clone_env = parent_project.get_env() perms = clone_env[PermissionSystem].get_user_permissions(authname) if not perms.get('VERSION_CONTROL_VIEW', False): self.log.error("Intrusive version control clone from " + parent_project.env_name + " requested by " + authname + " !") raise PermissionError(msg=_("You need to have permissions to read repository to clone it."))
def remove_user(self, req): """ Show removal form and handle POST as remove action """ username = req.args.get('username') # Check method and permissions if not req.method.upper() == 'POST' or not username: raise PermissionError() # Load user userstore = get_userstore() user = userstore.getUser(req.authname) account = userstore.getUser(username) if not account: add_warning(req, "Could not find user '{0}' from service".format(account.username)) return req.redirect(req.href('admin/users/manage')) # Check permissions req.perm.require('USER_AUTHOR', Resource('user', id=account.id)) # If removable user is project author, change the ownership to person who deletes the user papi = projects.Projects() for project in papi.get_authored_projects(account): project.author = user project.save() # Check if user has TRAC_ADMIN rights for the new project, if not, try setting if not req.perm.has_permission('TRAC_ADMIN', Resource('project', id=project.id)): groupstore = CQDEUserGroupStore(project.trac_environment_key) # Iterate existing project groups and put user into group with TRAC_ADMIN rights for gname, pname in groupstore.get_all_group_permissions(): if pname == 'TRAC_ADMIN': groupstore.add_user_to_group(project.author.username, gname) self.log.info('Added TRAC_ADMIN permissions to {0} at {0}'.format(project.author, project)) self.log.info('Changed ownership of project {0} from {0} to {0}'.format(project, project.author, user)) add_notice(req, tag(_("Changed ownership of the project to you: "), tag.a(project.project_name, href=req.href('..', project.env_name)))) if userstore.deleteUser(account): add_notice(req, "Removed user '{0}' successfully from local store".format(account.username)) else: add_warning(req, "Failed to remove user '{0}' from local store".format(account.username)) # Redirect to user listing return req.redirect(req.href('admin/users/manage'))
def __require_permissions_for_cloning(self, authname, parent_project): """ Checks that user have permissions to clone project :param authname: Current user name :param parent_project: Project class instance :raises: PermissionError if no permission """ clone_env = parent_project.get_env() perms = clone_env[PermissionSystem].get_user_permissions(authname) if not perms.get('VERSION_CONTROL_VIEW', False): self.log.error("Intrusive version control clone from " + parent_project.env_name + " requested by " + authname + " !") raise PermissionError(msg=_( "You need to have permissions to read repository to clone it.") )
def edit_user(self, req): """ Handle user edit: view & save """ changes = {} username = req.args.get('username') if not username: add_warning(req, _('Invalid username')) return self.list_users(req) # Load user being edited userstore = get_userstore() user = userstore.getUser(username) if not user: add_warning(req, _('Invalid username (or connection error)')) return self.list_users(req) # Load user who's doing the edit changed_by = userstore.getUser(req.authname) papi = Projects() # Check permissions and redirect to user listing (handy after editing the user) #req.perm.require('USER_AUTHOR', Resource('user', id=user.id)) if self.check_author_and_deputies(changed_by.id, user.author_id, userstore.get_deputies(user.id), req, user.id) == False: add_warning(req, _("You don't have rights to edit user")) req.redirect(req.href("admin")) data = req.args data['user'] = user data['author'] = userstore.getUserWhereId(user.author_id) if user.author_id else None data['deputies'] = userstore.get_deputies(user.id) data['base_path'] = req.base_path data['dateformats'] = DATEFORMATS data['is_local'] = userstore.is_local(user) data['now'] = datetime.utcnow() data['expired'] = user.expires and ((user.expires - datetime.utcnow()).days < 0) data['states'] = userstore.USER_STATUS_LABELS data['projects'] = papi.get_authored_projects(user) # Add javascript libraries for datepicker and autocomplete add_script(req, 'multiproject/js/jquery-ui.js') add_stylesheet(req, 'multiproject/css/jquery-ui.css') add_script(req, 'multiproject/js/multiproject.js') add_script(req, 'multiproject/js/admin_user_edit.js') # If get request show edit if req.method.upper() == 'GET' and req.args.get('remove_deputy'): deputy = userstore.getUser(req.args.get('remove_deputy').strip()) remove_res = userstore.remove_deputy(user.id, deputy.id) return req.send(remove_res, content_type='text/plain', status=200) elif req.method.upper() == 'GET': return 'admin_user_edit.html', data # Close pressed: get back to user listing if req.args.get('close'): return req.redirect(req.href('admin/users/manage')) if req.args.get('deputy_name'): deputy = userstore.getUser(req.args.get('deputy_name').strip()) resource = Resource('user', id=deputy.id) perm = PermissionCache(self.env, username=deputy.username) if perm.has_permission('USER_AUTHOR', resource): if(userstore.add_deputy(user.id, deputy.username)): add_notice(req, _("Deputy "+deputy.username+" added.")) return_url = 'home/admin/users/manage?username='******'home/admin/users/manage?username='******'t have enough rights")) return_url = 'home/admin/users/manage?username='******'limitexceeded' in req.args: add_warning(req, _('Picture you tried to upload was too big. Try a smaller one')) # Update author if changed author_id = req.args.get('author_id', None) # If id field is empty but name is not: manual input if not author_id and req.args.get('author_text'): add_warning(req, _('Author cannot be found')) return 'admin_user_edit.html', data # Check set reset the author if author_id: author = userstore.getUserWhereId(int(author_id)) if not author: add_warning(req, _('Author cannot be found')) return 'admin_user_edit.html', data # Check if author is valid: has permission to author? perm = PermissionCache(self.env, author.username) if 'USER_AUTHOR' not in perm: add_warning(req, _('User %s cannot work as an author (does not have USER_AUTHOR permissions)' % author)) return self.back(req) user.author_id = author.id changes['author'] = author else: user.author_id = None user.lastName = req.args.get('last') if not user.lastName: add_warning(req, _('Last name required')) return self.back(req) old_mail = user.mail user.mail = req.args.get('email') if not user.mail: add_warning(req, _('Email address required')) return self.back(req) if old_mail != user.mail: changes['email'] = user.mail org_store = CQDEOrganizationStore.instance() # TODO: is this correct? # When changing email, reset organizations to which the user belongs in user.organization_keys = org_store.get_organization_keys(user) or None # Update password if changed password = req.args.get('password') if password: if not userstore.is_local(user): add_warning(req, _("Can't change password for user that uses external authentication method")) return self.back(req) if len(password) < 7: add_warning(req, _("Password must be at least 7 characters long - please provide longer password")) return self.back(req) if password != req.args.get('confirmpw'): add_warning(req, _("Password do not match - please check")) return self.back(req) user.givenName = req.args.get('first') user.mobile = req.args.get('mobile') # Set or reset account expiration date expiration_str = req.args.get('expires', '') if expiration_str: try: # Parse date and set expiration time in the end of the day expires = datetime.strptime(expiration_str, DATEFORMATS['py']) expires += timedelta(hours=23, minutes=59, seconds=59) # If changed if expires != user.expires: user.expires = expires changes['expiration_date'] = user.expires.strftime(DATEFORMATS['py']) except Exception: self.log.exception('Date formatting failed') add_warning(req, _('Non-recognized expiration format')) pass # Remove expiration date elif user.expires: changes['expiration_date'] = 'Never expires' user.expires = None # Update status if set status = int(req.args.get('status', 0)) if status and status in userstore.USER_STATUS_LABELS.keys() and user.status != status: changes['status'] = userstore.USER_STATUS_LABELS[status] user.status = status if req.args.get('removeicon'): user.icon = None else: icon = req.args.get('icon') if not isinstance(icon, unicode) and icon.filename: user.createIcon(req.args.get('icon')) self.log.info('Saving changes to user: %s' % user) ok = userstore.updateUser(user) if ok and password: changes['password'] = password ok = userstore.updatePassword(user, password) if not ok: add_warning(req, _("Could not save changes")) add_notice(req, _("User %s updated" % username)) # Notify user about changes via email? if req.args.get('notify'): data = { 'user':user, 'changed_by':changed_by, 'changes':changes } try: enotify = EmailNotifier(self.env, "Account updated", data) enotify.template_name = 'account_edited.txt' enotify.notify(user.mail) add_notice(req, _("Notified user about the changes")) except TracError: add_warning(req, _("Failed to send email notification - user changed anyway")) # Check if user has still (after modification) permission to modify user # NOTE: req.perm cannot be used here because it is not updated yet #resource = Resource('user', id=user.id) #perm = PermissionCache(self.env, username=req.authname) #if perm.has_permission('USER_AUTHOR', resource): # return self.back(req) if self.check_author_and_deputies(changed_by.id, user.author_id, userstore.get_deputies(user.id), req, user.id) == True: return_url = 'home/admin/users/manage?username='******'You have no longer permission to modify the account: %s' % user.username)) return req.redirect(req.href('admin/users/manage'))
def get_navigation_items(self, req): if 'MESSAGE_VIEW' in req.perm: yield ('metanav', 'messages', tag.a(_('Messages'), **{'class': 'messages', 'href': '#'}))
def create_project(self, req): """ Handler for creating project request """ req.perm.require("PROJECT_CREATE") if req.method != 'POST': return self.create_failure(req, 'POST request needed when creating a new project') author = get_context(req)['author'] # If agreement needed but not getting it, show failure if conf.project_requires_agreed_terms and not self._is_active_user(req): return self.create_failure(req, 'You need to approve legal text to create a project!') # Read and transform some variables vcs_type = req.args.get('vcstype') vcs_name = req.args.get('vcs_name') if not self.validate_repository_name(vcs_name): return self.create_failure(req, 'Check repository name.') parent_project = None if "_project_" in req.args: parent_project = Project.get(env_name=req.args.get('_project_')) self.__require_permissions_for_cloning(req.authname, parent_project) vcs_type = conf.getVersionControlType(parent_project.env_name) # TODO: expensive call, probably needed # Read settings settings = {} if vcs_type: settings['vcs_type'] = vcs_type if vcs_name: settings['vcs_name'] = vcs_name identifier = req.args.get('prj_short_name') name = req.args.get('prj_long_name') project_visibility = 'prj_is_public' in req.args public = False published = None if project_visibility: public = True published = datetime.now() # Create project object project = Project( id = None, env_name = identifier, project_name = name, description = req.args.get('prj_description'), author_id = author.id, created = None, # Use default which is now() public = public, published = published ) # Create project environment projects = Projects() try: projects.create_project(project, settings) except ProjectValidationException as exc: self.log.warning('Project creation failed due the validation: {0}'.format(exc.value)) return self.create_failure(req, exc.value) except: self.log.exception('Project creation failed') return self.create_failure(req, _("Creating project failed. Try again later.")) if public: projects.add_public_project_visibility(project.id) #Add author to follow project watch_store = CQDEWatchlistStore() watch_store.watch_project(author.id, project.id) #Change project trac.ini to support multiple repositories project_env_path = conf.getEnvironmentSysPath(project.env_name) repo_env_path = conf.getEnvironmentVcsPath(project.env_name, vcs_type, vcs_name) os.rename(project_env_path + '/conf/trac.ini', project_env_path + '/conf/trac.ini.bak') oldfile = open(project_env_path + '/conf/trac.ini.bak', 'r') newfile = open(project_env_path + '/conf/trac.ini', 'w') lines = oldfile.readlines() for line in lines: newfile.write(line) if line.startswith('database ='): break newfile.write('repository_dir =\nrepository_type = svn\n\n[repositories]\n') newfile.write('%s.dir = %s\n' % (vcs_name, repo_env_path)) newfile.write('%s.type = %s\n' % (vcs_name, vcs_type)) newfile.close() oldfile.close() os.remove(project_env_path + '/conf/trac.ini.bak') # Notify listeners. The project object still exists, but database does not for listener in self.project_change_listeners: try: listener.project_created(project) listener.project_watchers(project) if public: listener.project_set_public(project) except: pass return self.create_success(req, project)
def process_request(self, req): """ Handles the profile box request, which is expected to be in format:: /user/<username>/profilebox :returns: Pre-rendered user profile box HTML using templates: - multiproject_user_profilebox.html: To show the content - multiproject_user_profilebox_default.html: In a case of failures, missing data etc """ # Read and validate arguments #account_id = req.args.get('user_id', None) #account_username = req.args.get('username', None) match = MATCHREGX.match(req.path_info) if not match: msg = _('Account cannot be found') return 'multiproject_user_profilebox_default.html', { 'msg': msg }, False # Load specified account userstore = get_userstore() account = userstore.getUser(match.group('username')) if not account: msg = _('Account cannot be found') return 'multiproject_user_profilebox_default.html', { 'msg': msg }, False # Check if user has USER_VIEW permission to view other users in home project homeperm = self._get_home_perm(req) if req.authname != account.username and 'USER_VIEW' not in homeperm: msg = _('Access denied') return 'multiproject_user_profilebox_default.html', { 'msg': msg }, False # Load registered actions actions = [] for aprovider in self.profile_action_providers: # Iterate actions and validate them for new_action in aprovider.get_profile_actions(req, account): # Ensure each action is in tuple format: (order, fragment) if not isinstance(new_action, tuple): new_action = (0, new_action) # Add to list actions.append(new_action) # Sort the actions by hints given in actions: # the smaller the value, the higher the priority actions.sort(key=lambda tup: tup[0]) # Construct list items: put first and class values litems = [] llen = len(actions) for index, action in enumerate(actions): classes = [] # If last if index == 0: classes.append('first') if llen == index + 1: classes.append('last') litems.append(tag.li(action[1], **{'class': ' '.join(classes)})) # If empty, put empty list element in place for easier styling if not litems: litems.append(tag.li('li', **{'class': 'first last'})) # Pass data to template. Generate ul/li list from registered actions data = {'account': account, 'actionlist': tag.ul(*litems)} return 'multiproject_user_profilebox.html', data, False
def create_project(self, req): """ Handler for creating project request """ req.perm.require("PROJECT_CREATE") if req.method != 'POST': return self.create_failure( req, 'POST request needed when creating a new project') author = get_context(req)['author'] # If agreement needed but not getting it, show failure if conf.project_requires_agreed_terms and not self._is_active_user( req): return self.create_failure( req, 'You need to approve legal text to create a project!') # Read and transform some variables vcs_type = req.args.get('vcstype') vcs_name = req.args.get('vcs_name') if not self.validate_repository_name(vcs_name): return self.create_failure(req, 'Check repository name.') parent_project = None if "_project_" in req.args: parent_project = Project.get(env_name=req.args.get('_project_')) self.__require_permissions_for_cloning(req.authname, parent_project) vcs_type = conf.getVersionControlType( parent_project.env_name ) # TODO: expensive call, probably needed # Read settings settings = {} if vcs_type: settings['vcs_type'] = vcs_type if vcs_name: settings['vcs_name'] = vcs_name identifier = req.args.get('prj_short_name') name = req.args.get('prj_long_name') project_visibility = 'prj_is_public' in req.args public = False published = None if project_visibility: public = True published = datetime.now() # Create project object project = Project( id=None, env_name=identifier, project_name=name, description=req.args.get('prj_description'), author_id=author.id, created=None, # Use default which is now() public=public, published=published) # Create project environment projects = Projects() try: projects.create_project(project, settings) except ProjectValidationException as exc: self.log.warning( 'Project creation failed due the validation: {0}'.format( exc.value)) return self.create_failure(req, exc.value) except: self.log.exception('Project creation failed') return self.create_failure( req, _("Creating project failed. Try again later.")) if public: projects.add_public_project_visibility(project.id) #Add author to follow project watch_store = CQDEWatchlistStore() watch_store.watch_project(author.id, project.id) #Change project trac.ini to support multiple repositories project_env_path = conf.getEnvironmentSysPath(project.env_name) repo_env_path = conf.getEnvironmentVcsPath(project.env_name, vcs_type, vcs_name) os.rename(project_env_path + '/conf/trac.ini', project_env_path + '/conf/trac.ini.bak') oldfile = open(project_env_path + '/conf/trac.ini.bak', 'r') newfile = open(project_env_path + '/conf/trac.ini', 'w') lines = oldfile.readlines() for line in lines: newfile.write(line) if line.startswith('database ='): break newfile.write( 'repository_dir =\nrepository_type = svn\n\n[repositories]\n') newfile.write('%s.dir = %s\n' % (vcs_name, repo_env_path)) newfile.write('%s.type = %s\n' % (vcs_name, vcs_type)) newfile.close() oldfile.close() os.remove(project_env_path + '/conf/trac.ini.bak') # Notify listeners. The project object still exists, but database does not for listener in self.project_change_listeners: try: listener.project_created(project) listener.project_watchers(project) if public: listener.project_set_public(project) except: pass return self.create_success(req, project)
def process_request(self, req): """ Handles the profile box request, which is expected to be in format:: /user/<username>/profilebox :returns: Pre-rendered user profile box HTML using templates: - multiproject_user_profilebox.html: To show the content - multiproject_user_profilebox_default.html: In a case of failures, missing data etc """ # Read and validate arguments #account_id = req.args.get('user_id', None) #account_username = req.args.get('username', None) match = MATCHREGX.match(req.path_info) if not match: msg = _('Account cannot be found') return 'multiproject_user_profilebox_default.html', {'msg': msg}, False # Load specified account userstore = get_userstore() account = userstore.getUser(match.group('username')) if not account: msg = _('Account cannot be found') return 'multiproject_user_profilebox_default.html', {'msg': msg}, False # Check if user has USER_VIEW permission to view other users in home project homeperm = self._get_home_perm(req) if req.authname != account.username and 'USER_VIEW' not in homeperm: msg = _('Access denied') return 'multiproject_user_profilebox_default.html', {'msg': msg}, False # Load registered actions actions = [] for aprovider in self.profile_action_providers: # Iterate actions and validate them for new_action in aprovider.get_profile_actions(req, account): # Ensure each action is in tuple format: (order, fragment) if not isinstance(new_action, tuple): new_action = (0, new_action) # Add to list actions.append(new_action) # Sort the actions by hints given in actions: # the smaller the value, the higher the priority actions.sort(key=lambda tup: tup[0]) # Construct list items: put first and class values litems = [] llen = len(actions) for index, action in enumerate(actions): classes = [] # If last if index == 0: classes.append('first') if llen == index + 1: classes.append('last') litems.append(tag.li(action[1], **{'class': ' '.join(classes)})) # If empty, put empty list element in place for easier styling if not litems: litems.append(tag.li('li', **{'class':'first last'})) # Pass data to template. Generate ul/li list from registered actions data = { 'account':account, 'actionlist':tag.ul(*litems) } return 'multiproject_user_profilebox.html', data, False
def edit_user(self, req): """ Handle user edit: view & save """ changes = {} username = req.args.get('username') if not username: add_warning(req, _('Invalid username')) return self.list_users(req) # Load user being edited userstore = get_userstore() user = userstore.getUser(username) if not user: add_warning(req, _('Invalid username (or connection error)')) return self.list_users(req) # Load user who's doing the edit changed_by = userstore.getUser(req.authname) papi = Projects() # Check permissions and redirect to user listing (handy after editing the user) req.perm.require('USER_AUTHOR', Resource('user', id=user.id)) data = req.args data['user'] = user data['author'] = userstore.getUserWhereId(user.author_id) if user.author_id else None data['base_path'] = req.base_path data['dateformats'] = DATEFORMATS data['is_local'] = userstore.is_local(user) data['now'] = datetime.utcnow() data['expired'] = user.expires and ((user.expires - datetime.utcnow()).days < 0) data['states'] = userstore.USER_STATUS_LABELS data['projects'] = papi.get_authored_projects(user) # Add javascript libraries for datepicker and autocomplete add_script(req, 'multiproject/js/jquery-ui.js') add_stylesheet(req, 'multiproject/css/jquery-ui.css') add_script(req, 'multiproject/js/multiproject.js') add_script(req, 'multiproject/js/admin_user_edit.js') # If get request show edit if req.method.upper() == 'GET': return 'admin_user_edit.html', data # Close pressed: get back to user listing if req.args.get('close'): return req.redirect(req.href('admin/users/manage')) # Handle save if 'limitexceeded' in req.args: add_warning(req, _('Picture you tried to upload was too big. Try a smaller one')) # Update author if changed author_id = req.args.get('author_id', None) # If id field is empty but name is not: manual input if not author_id and req.args.get('author_text'): add_warning(req, _('Author cannot be found')) return 'admin_user_edit.html', data # Check set reset the author if author_id: author = userstore.getUserWhereId(int(author_id)) if not author: add_warning(req, _('Author cannot be found')) return 'admin_user_edit.html', data # Check if author is valid: has permission to author? perm = PermissionCache(self.env, author.username) if 'USER_AUTHOR' not in perm: add_warning(req, _('User %s cannot work as an author (does not have USER_AUTHOR permissions)' % author)) return self.back(req) user.author_id = author.id changes['author'] = author else: user.author_id = None user.lastName = req.args.get('last') if not user.lastName: add_warning(req, _('Last name required')) return self.back(req) old_mail = user.mail user.mail = req.args.get('email') if not user.mail: add_warning(req, _('Email address required')) return self.back(req) if old_mail != user.mail: changes['email'] = user.mail org_store = CQDEOrganizationStore.instance() # TODO: is this correct? # When changing email, reset organizations to which the user belongs in user.organization_keys = org_store.get_organization_keys(user) or None # Update password if changed password = req.args.get('password') if password: if not userstore.is_local(user): add_warning(req, _("Can't change password for user that uses external authentication method")) return self.back(req) if len(password) < 7: add_warning(req, _("Password must be at least 7 characters long - please provide longer password")) return self.back(req) if password != req.args.get('confirmpw'): add_warning(req, _("Password do not match - please check")) return self.back(req) user.givenName = req.args.get('first') user.mobile = req.args.get('mobile') # Set or reset account expiration date expiration_str = req.args.get('expires', '') if expiration_str: try: # Parse date and set expiration time in the end of the day expires = datetime.strptime(expiration_str, DATEFORMATS['py']) expires += timedelta(hours=23, minutes=59, seconds=59) # If changed if expires != user.expires: user.expires = expires changes['expiration_date'] = user.expires.strftime(DATEFORMATS['py']) except Exception: self.log.exception('Date formatting failed') add_warning(req, _('Non-recognized expiration format')) pass # Remove expiration date elif user.expires: changes['expiration_date'] = 'Never expires' user.expires = None # Update status if set status = int(req.args.get('status', 0)) if status and status in userstore.USER_STATUS_LABELS.keys() and user.status != status: changes['status'] = userstore.USER_STATUS_LABELS[status] user.status = status if req.args.get('removeicon'): user.icon = None else: icon = req.args.get('icon') if not isinstance(icon, unicode) and icon.filename: user.createIcon(req.args.get('icon')) self.log.info('Saving changes to user: %s' % user) ok = userstore.updateUser(user) if ok and password: changes['password'] = password ok = userstore.updatePassword(user, password) if not ok: add_warning(req, _("Could not save changes")) add_notice(req, _("User %s updated" % username)) # Notify user about changes via email? if req.args.get('notify'): data = { 'user':user, 'changed_by':changed_by, 'changes':changes } try: enotify = EmailNotifier(self.env, "Account updated", data) enotify.template_name = 'account_edited.txt' enotify.notify(user.mail) add_notice(req, _("Notified user about the changes")) except TracError: add_warning(req, _("Failed to send email notification - user changed anyway")) # Check if user has still (after modification) permission to modify user # NOTE: req.perm cannot be used here because it is not updated yet resource = Resource('user', id=user.id) perm = PermissionCache(self.env, username=req.authname) if perm.has_permission('USER_AUTHOR', resource): return self.back(req) add_notice(req, _('You have no longer permission to modify the account: %s' % user.username)) return req.redirect(req.href('admin/users/manage'))
def get_navigation_items(self, req): if req.authname and req.authname != 'anonymous': yield ('metanav', 'notifications', tag.a(_('Notifications'), **{'class': 'notifications', 'href': '#'}))
def get_navigation_items(self, req): yield ('metanav', 'site_admin', tag.a(_('Site admin'), **{'class': 'site_admin', 'href': '#'}))
def create_project(self, req): """ Handler for creating project request """ req.perm.require("PROJECT_CREATE") if req.method != 'POST': return self.create_failure(req, 'POST request needed when creating a new project') author = get_context(req)['author'] # If agreement needed but not getting it, show failure if conf.project_requires_agreed_terms and not self._is_active_user(req): return self.create_failure(req, 'You need to approve legal text to create a project!') # Read and transform some variables vcs_type = req.args.get('vcstype') parent_project = None if "_project_" in req.args: parent_project = Project.get(env_name=req.args.get('_project_')) self.__require_permissions_for_cloning(req.authname, parent_project) vcs_type = conf.getVersionControlType(parent_project.env_name) # TODO: expensive call, probably needed # Read settings settings = {} if vcs_type: settings['vcs_type'] = vcs_type identifier = req.args.get('prj_short_name') name = req.args.get('prj_long_name') public = 'prj_is_public' in req.args published = None if public: published = datetime.now() # Create project object project = Project( id = None, env_name = identifier, project_name = name, description = req.args.get('prj_description'), author_id = author.id, created = None, # Use default which is now() published = published ) # Create project environment projects = Projects() try: projects.create_project(project, settings) except ProjectValidationException as exc: self.log.warning('Project creation failed due the validation: {0}'.format(exc.value)) return self.create_failure(req, exc.value) except: self.log.exception('Project creation failed') return self.create_failure(req, _("Creating project failed. Try again later.")) if public: projects.add_public_project_visibility(project.id) # Notify listeners. The project object still exists, but database does not for listener in self.project_change_listeners: listener.project_created(project) if public: listener.project_set_public(project) return self.create_success(req, project)