def stomp_publish(topic, message): """ Try to publish a message on a Stomp-compliant message bus. """ if not pagure_config.get("STOMP_NOTIFICATIONS", False): return # We catch Exception if we want :-p # pylint: disable=broad-except # Ignore message about stomp import # pylint: disable=import-error try: import stomp global stomp_conn if not stomp_conn or not stomp_conn.is_connected(): stomp_conn = stomp.Connection12(pagure_config["STOMP_BROKERS"]) if pagure_config.get("STOMP_SSL"): stomp_conn.set_ssl( pagure_config["STOMP_BROKERS"], key_file=pagure_config.get("STOMP_KEY_FILE"), cert_file=pagure_config.get("STOMP_CERT_FILE"), password=pagure_config.get("STOMP_CREDS_PASSWORD"), ) from stomp import PrintingListener stomp_conn.set_listener("", PrintingListener()) stomp_conn.start() stomp_conn.connect(wait=True) hierarchy = pagure_config["STOMP_HIERARCHY"] stomp_conn.send( destination=hierarchy + topic, body=json.dumps(message) ) except Exception: _log.exception("Error sending stomp message")
def is_admin(): """ Return whether the user is admin for this application or not. """ if not authenticated(): return False user = flask.g.fas_user auth_method = pagure_config.get("PAGURE_AUTH", None) if auth_method == "fas": if not user.cla_done: return False admin_users = pagure_config.get("PAGURE_ADMIN_USERS", []) if not isinstance(admin_users, list): admin_users = [admin_users] if user.username in admin_users: return True admins = pagure_config["ADMIN_GROUP"] if not isinstance(admins, list): admins = [admins] admins = set(admins or []) groups = set(flask.g.fas_user.groups) return not groups.isdisjoint(admins)
def add_group(): """ Endpoint to create groups """ if not pagure_config.get("ENABLE_USER_MNGT", True): flask.abort(404) if not pagure_config.get("ENABLE_GROUP_MNGT", False): flask.abort(404) user = pagure.lib.query.search_user( flask.g.session, username=flask.g.fas_user.username ) if not user: # pragma: no cover return flask.abort(403) group_types = ["user"] if pagure.utils.is_admin(): group_types = [ grp.group_type for grp in pagure.lib.query.get_group_types(flask.g.session) ] # Make sure the admin type is always the last one group_types.remove("admin") group_types.append("admin") form = pagure.forms.NewGroupForm(group_types=group_types) if not pagure.utils.is_admin(): form.group_type.data = "user" if form.validate_on_submit(): try: group_name = form.group_name.data.strip() display_name = form.display_name.data.strip() description = form.description.data.strip() msg = pagure.lib.query.add_group( session=flask.g.session, group_name=group_name, display_name=display_name, description=description, group_type=form.group_type.data, user=flask.g.fas_user.username, is_admin=pagure.utils.is_admin(), blacklist=pagure_config["BLACKLISTED_GROUPS"], ) flask.g.session.commit() flask.flash("Group `%s` created." % group_name) flask.flash(msg) return flask.redirect(flask.url_for("ui_ns.group_lists")) except pagure.exceptions.PagureException as err: flask.g.session.rollback() flask.flash("%s" % err, "error") except SQLAlchemyError: # pragma: no cover flask.g.session.rollback() flask.flash("Could not create group.") _log.exception("Could not create group.") return flask.render_template("add_group.html", form=form)
def run_hook_file(hooktype): """ Runs a specific hook by grabbing the changes and running functions. Args: hooktype (string): The name of the hook to run: pre-receive, update or post-receive """ if hooktype not in ("pre-receive", "update", "post-receive"): raise ValueError("Hook type %s not valid" % hooktype) changes = extract_changes(from_stdin=hooktype != "update") session = pagure.lib.model_base.create_session(pagure_config["DB_URL"]) if not session: raise Exception("Unable to initialize db session") pushuser = os.environ.get("GL_USER") is_internal = os.environ.get("internal", False) == "yes" pull_request = None if "pull_request_uid" in os.environ: pull_request = pagure.lib.query.get_request_by_uid( session, os.environ["pull_request_uid"] ) if pagure_config.get("HOOK_DEBUG", False): print("Changes: %s" % changes) gitdir = os.path.abspath(os.environ["GIT_DIR"]) ( repotype, username, namespace, repo, ) = pagure.lib.git.get_repo_info_from_path(gitdir) project = pagure.lib.query._get_project( session, repo, user=username, namespace=namespace ) if not project: raise Exception( "Not able to find the project corresponding to: %%s - s - " "%s - %s" % (repotype, username, namespace, repo) ) if pagure_config.get("HOOK_DEBUG", False): print("Running %s hooks for %s" % (hooktype, project.fullname)) run_project_hooks( session, pushuser, project, hooktype, repotype, gitdir, changes, is_internal, pull_request, ) session.close()
def _get_gitolite_command(): """ Return the gitolite command to run based on the info in the configuration file. """ _log.info("Compiling the gitolite configuration") gitolite_folder = pagure_config.get("GITOLITE_HOME", None) if gitolite_folder: cmd = "GL_RC=%s GL_BINDIR=%s gl-compile-conf" % ( pagure_config.get("GL_RC"), pagure_config.get("GL_BINDIR"), ) _log.debug("Command: %s", cmd) return cmd
def _get_emails_for_commit_notification(project): emails = set() for watcher in project.watchers: if watcher.watch_commits: emails.add(watcher.user.default_email) # Drop the email used by pagure when sending emails = _clean_emails( emails, pagure_config.get( pagure_config.get("FROM_EMAIL", "*****@*****.**") ), ) return emails
def view_user_issues(username): """ Shows the issues created or assigned to the specified user. :param username: The username to retrieve the issues for :type username: str """ if not pagure_config.get("ENABLE_TICKETS", True): flask.abort( 404, description="Tickets have been disabled on this pagure instance", ) user = _get_user(username=username) userprofile_common = get_userprofile_common(user) return flask.render_template( "userprofile_issues.html", username=username, user=user, repos_length=userprofile_common["repos_length"], forks_length=userprofile_common["forks_length"], select="issues", )
def check_acl( self, session, project, username, refname, pull_request, repotype, is_internal, **info ): if is_internal: self.info("Internal push allowed") return True # Check whether a PR is required for this repo or in general global_pr_only = pagure_config.get("PR_ONLY", False) pr_only = project.settings.get("pull_request_access_only", False) if repotype == "main": if ( pr_only or (global_pr_only and not project.is_fork) ) and not pull_request: self.info("Pull request required") return False # Determine whether the current user is allowed to push is_committer = is_repo_committer(project, username, session) deploykey = lookup_deploykey(project, username) if deploykey is not None: self.info("Deploykey used. Push access: %s" % deploykey.pushaccess) is_committer = deploykey.pushaccess self.info("Has commit access: %s" % is_committer) return is_committer
def notify_new_email(email, user): """ Ask the user to confirm to the email belong to them. """ root_url = pagure_config.get("APP_URL", flask.request.url_root) url = urljoin( root_url or flask.request.url_root, flask.url_for("ui_ns.confirm_email", token=email.token), ) text = """Dear %(username)s, You have registered a new email on pagure at %(root_url)s. To finish your validate this registration, please click on the following link or copy/paste it in your browser, this link will remain valid only 2 days: %(url)s The email will not be activated until you finish this step. Sincerely, Your pagure admin. """ % ( {"username": user.username, "url": url, "root_url": root_url} ) send_email( text, "Confirm new email", email.email, user_from=user.fullname or user.user, )
def group_user_delete(user, group): """ Delete an user from a certain group """ if not pagure_config.get("ENABLE_USER_MNGT", True): flask.abort(404) if not pagure_config.get("ENABLE_GROUP_MNGT", False): flask.abort(404) form = pagure.forms.ConfirmationForm() if form.validate_on_submit(): try: pagure.lib.query.delete_user_of_group( flask.g.session, username=user, groupname=group, user=flask.g.fas_user.username, is_admin=pagure.utils.is_admin(), ) flask.g.session.commit() pagure.lib.git.generate_gitolite_acls(project=None, group=group) flask.flash( "User `%s` removed from the group `%s`" % (user, group) ) except pagure.exceptions.PagureException as err: flask.g.session.rollback() flask.flash("%s" % err, "error") return flask.redirect( flask.url_for("ui_ns.view_group", group=group) ) except SQLAlchemyError: # pragma: no cover flask.g.session.rollback() flask.flash( "Could not remove user `%s` from the group `%s`." % (user.user, group), "error", ) _log.exception( "Could not remove user `%s` from the group `%s`." % (user.user, group) ) return flask.redirect(flask.url_for("ui_ns.view_group", group=group))
def _prepare_db(self): self.dbpath = "sqlite:///%s" % os.path.join( tests_state["path"], "db.sqlite" ) self.session = tests_state["db_session"] pagure.lib.model.create_default_status( self.session, acls=pagure_config.get("ACLS", {}) ) if self.populate_db: _populate_db(self.session)
def set_up_logging(app=None, force=False): global LOGGER_SETUP if LOGGER_SETUP and not force: _log.info("logging already setup") return logging.basicConfig() logging.config.dictConfig(pagure_config.get("LOGGING") or {"version": 1}) LOGGER_SETUP = True
def __init__(self, *args, **kwargs): delta = pagure_config.get("WTF_CSRF_TIME_LIMIT", 3600) if delta and WTF_VERSION < (0, 10, 0): self.TIME_LIMIT = datetime.timedelta(seconds=delta) else: self.TIME_LIMIT = delta if "csrf_enabled" in kwargs and kwargs["csrf_enabled"] is False: kwargs["meta"] = {"csrf": False} if WTF_VERSION >= (0, 14, 0): kwargs.pop("csrf_enabled") super(PagureForm, self).__init__(*args, **kwargs)
def post_compile_only(cls): """ This method runs `gitolite trigger POST_COMPILE` without touching any other gitolite configuration. Most importantly, this will process SSH keys used by gitolite. """ _log.info("Triggering gitolite POST_COMPILE") gitolite_folder = pagure_config.get("GITOLITE_HOME", None) if gitolite_folder: cmd = "HOME=%s gitolite trigger POST_COMPILE" % gitolite_folder _log.debug("Command: %s", cmd) cls._run_gitolite_cmd(cmd)
def is_repo_committer(repo_obj, username=None, session=None): """ Return whether the user is a committer of the provided repo. """ import pagure.lib.query usergroups = set() if username is None: if not authenticated(): return False if is_admin(): return True username = flask.g.fas_user.username usergroups = set(flask.g.fas_user.groups) if not session: session = flask.g.session try: user = pagure.lib.query.get_user(session, username) usergroups = usergroups.union(set(user.groups)) except pagure.exceptions.PagureException: return False # If the user is main admin -> yep if repo_obj.user.user == username: return True # If they are in the list of committers -> yep for user in repo_obj.committers: if user.user == username: return True # If they are in a group that has commit access -> yep for group in repo_obj.committer_groups: if group.group_name in usergroups: return True # If no direct committer, check EXTERNAL_COMMITTER info ext_committer = pagure_config.get("EXTERNAL_COMMITTER", None) if ext_committer: overlap = set(ext_committer) & usergroups if overlap: for grp in overlap: restrict = ext_committer[grp].get("restrict", []) exclude = ext_committer[grp].get("exclude", []) if restrict and repo_obj.fullname not in restrict: continue elif repo_obj.fullname in exclude: continue else: return True # The user is not in an external_committer group that grants access, and # not a direct committer -> You have no power here return False
def user_can_clone_ssh(username): has_ssh_keys = False if flask.g.authenticated: has_ssh_keys = ( len( pagure.lib.query.search_user( flask.g.session, username=flask.g.fas_user.username ).sshkeys ) != 0 ) always_render = pagure_config.get("ALWAYS_RENDER_SSH_CLONE_URL") return always_render or has_ssh_keys
def get_git_url_ssh(complement=""): """ Return the GIT SSH URL to be displayed in the UI based on the content of the configuration file. """ git_url_ssh = pagure_config.get("GIT_URL_SSH") if flask.g.authenticated and git_url_ssh: try: git_url_ssh = git_url_ssh.format( username=flask.g.fas_user.username ) except (KeyError, IndexError): pass return git_url_ssh + complement
def _get_gitolite_command(): """ Return the gitolite command to run based on the info in the configuration file. """ _log.info("Compiling the gitolite configuration") gitolite_folder = pagure_config.get("GITOLITE_HOME", None) if gitolite_folder: cmd = ( "HOME=%s gitolite compile && HOME=%s gitolite trigger " "POST_COMPILE" % (gitolite_folder, gitolite_folder) ) _log.debug("Command: %s", cmd) return cmd
def _individual_repos_command(config_file): _log.info( "Compiling gitolite configuration %s for single repository", config_file, ) gitolite_folder = pagure_config.get("GITOLITE_HOME", None) if gitolite_folder: cmd = "HOME=%s gitolite compile-1 %s" % ( gitolite_folder, config_file, ) _log.debug("Command: %s", cmd) return cmd
def edit_group(group): """ Allows editing the information about this group. """ if not pagure_config.get("ENABLE_USER_MNGT", True): flask.abort(404) group_type = "user" is_admin = pagure.utils.is_admin() if is_admin: group_type = None group = pagure.lib.query.search_groups( flask.g.session, group_name=group, group_type=group_type ) if not group: flask.abort(404, description="Group not found") # Edit group info form = pagure.forms.EditGroupForm() if form.validate_on_submit(): try: msg = pagure.lib.query.edit_group_info( flask.g.session, group=group, display_name=form.display_name.data, description=form.description.data, user=flask.g.fas_user.username, is_admin=is_admin, ) flask.g.session.commit() flask.flash(msg) return flask.redirect( flask.url_for("ui_ns.view_group", group=group.group_name) ) except pagure.exceptions.PagureException as err: flask.g.session.rollback() flask.flash("%s" % err, "error") return flask.redirect( flask.url_for("ui_ns.view_group", group=group.group_name) ) except SQLAlchemyError: # pragma: no cover flask.g.session.rollback() flask.flash( "Could not edit group `%s`." % (group.group_name), "error" ) _log.exception("Could not edit group `%s`." % (group.group_name)) elif flask.request.method == "GET": form.display_name.data = group.display_name form.description.data = group.description return flask.render_template("edit_group.html", group=group, form=form)
def group_delete(group): """ Delete a certain group """ if not pagure_config.get("ENABLE_USER_MNGT", True): flask.abort(404) if not pagure_config.get("ENABLE_GROUP_MNGT", False): flask.abort(404) form = pagure.forms.ConfirmationForm() if form.validate_on_submit(): group_obj = pagure.lib.query.search_groups( flask.g.session, group_name=group ) if not group_obj: flask.flash("No group `%s` found" % group, "error") return flask.redirect(flask.url_for("ui_ns.group_lists")) user = pagure.lib.query.search_user( flask.g.session, username=flask.g.fas_user.username ) if not user: flask.abort(404, description="User not found") if group not in user.groups: flask.flash( "You are not allowed to delete the group %s" % group, "error" ) return flask.redirect(flask.url_for("ui_ns.group_lists")) flask.g.session.delete(group_obj) flask.g.session.commit() pagure.lib.git.generate_gitolite_acls(project=None) flask.flash("Group `%s` has been deleted" % (group)) return flask.redirect(flask.url_for("ui_ns.group_lists"))
def __init__(self, *args, **kwargs): """ Calls the default constructor with the normal argument but uses the list of collection provided to fill the choices of the drop-down list. """ super(ProjectForm, self).__init__(*args, **kwargs) # set the name validator regex = pagure_config.get( "PROJECT_NAME_REGEX", "^[a-zA-z0-9_][a-zA-Z0-9-_.+]*$" ) self.name.validators = [ wtforms.validators.DataRequired(), wtforms.validators.Regexp(regex, flags=re.IGNORECASE), ] # Set the list of namespace if "namespaces" in kwargs: self.namespace.choices = [ (namespace, namespace) for namespace in kwargs["namespaces"] ] if not pagure_config.get("USER_NAMESPACE", False): self.namespace.choices.insert(0, ("", "")) if not ( is_admin() and pagure_config.get("ALLOW_ADMIN_IGNORE_EXISTING_REPOS") ) and ( flask.g.fas_user.username not in pagure_config["USERS_IGNORE_EXISTING_REPOS"] ): self.ignore_existing_repos = None if not ( is_admin() and pagure_config.get("REPOSPANNER_NEW_REPO_ADMIN_OVERRIDE") ): self.repospanner_region = None
def get_syntax_alias(filename): """ return an alias based on the filename that is used to override the automatic syntax highlighting dectection by highlight.js """ override_rules = pagure_config.get( "SYNTAX_ALIAS_OVERRIDES", {".spec": "specfile", ".patch": "diff"} ) fn, fn_ext = splitext(filename) output = "" if fn_ext == "": output = "lang-plaintext" elif fn_ext in override_rules: output = "lang-" + override_rules[fn_ext] return output
def project_dowait(self, session, name, namespace, user): """ This is a task used to test the locking systems. It should never be allowed to be called in production instances, since that would allow an attacker to basically DOS a project by calling this repeatedly. """ assert pagure_config.get("ALLOW_PROJECT_DOWAIT", False) project = pagure.lib.query._get_project( session, namespace=namespace, name=name, user=user ) with project.lock("WORKER"): time.sleep(10) return ret( "ui_ns.view_repo", repo=name, username=user, namespace=namespace )
def add_api_user_token(): """ Create an user token (not project specific). """ if admin_session_timedout(): if flask.request.method == "POST": flask.flash("Action canceled, try it again", "error") return flask.redirect( flask.url_for("auth_login", next=flask.request.url) ) # Ensure the user is in the DB at least user = _get_user(username=flask.g.fas_user.username) acls = pagure.lib.query.get_acls( flask.g.session, restrict=pagure_config.get("CROSS_PROJECT_ACLS") ) form = pagure.forms.NewTokenForm(acls=acls) if form.validate_on_submit(): try: pagure.lib.query.add_token_to_user( flask.g.session, project=None, description=form.description.data.strip() or None, acls=form.acls.data, username=user.username, ) flask.g.session.commit() flask.flash("Token created") return flask.redirect( flask.url_for("ui_ns.user_settings") + "#nav-api-tab" ) except SQLAlchemyError as err: # pragma: no cover flask.g.session.rollback() _log.exception(err) flask.flash("API key could not be added", "error") # When form is displayed after an empty submission, show an error. if form.errors.get("acls"): flask.flash("You must select at least one permission.", "error") return flask.render_template( "add_token.html", select="settings", form=form, acls=acls )
def add_user_sshkey(): """ Add the specified SSH key to the user. """ if admin_session_timedout(): if flask.request.method == "POST": flask.flash("Action canceled, try it again", "error") return flask.redirect( flask.url_for("auth_login", next=flask.request.url) ) form = pagure.forms.AddSSHKeyForm() if form.validate_on_submit(): user = _get_user(username=flask.g.fas_user.username) try: msg = pagure.lib.query.add_sshkey_to_project_or_user( flask.g.session, ssh_key=form.ssh_key.data, pushaccess=True, creator=user, user=user, ) flask.g.session.commit() pagure.lib.query.create_user_ssh_keys_on_disk( user, pagure_config.get("GITOLITE_KEYDIR", None) ) pagure.lib.tasks.gitolite_post_compile_only.delay() flask.flash(msg) return flask.redirect( flask.url_for("ui_ns.user_settings") + "#nav-ssh-tab" ) except pagure.exceptions.PagureException as msg: flask.g.session.rollback() _log.debug(msg) flask.flash(str(msg), "error") except SQLAlchemyError as err: # pragma: no cover flask.g.session.rollback() _log.exception(err) flask.flash("SSH key could not be added", "error") return flask.redirect( flask.url_for("ui_ns.user_settings") + "#nav-ssh-tab" )
def userprofile_projects(username): """ Public Profile view of a user's projects. """ user = _get_user(username=username) repopage = flask.request.args.get("repopage", 1) try: repopage = int(repopage) if repopage < 1: repopage = 1 except ValueError: repopage = 1 limit = pagure_config["ITEM_PER_PAGE"] repo_start = limit * (repopage - 1) repos = pagure.lib.query.search_projects( flask.g.session, username=username, fork=False, exclude_groups=pagure_config.get("EXCLUDE_GROUP_INDEX"), start=repo_start, limit=limit, private=False, ) userprofile_common = get_userprofile_common(user) total_page_repos = int( ceil(userprofile_common["repos_length"] / float(limit)) ) return flask.render_template( "userprofile_projects.html", username=username, user=user, repos=repos, total_page_repos=total_page_repos, repopage=repopage, repos_length=userprofile_common["repos_length"], forks_length=userprofile_common["forks_length"], select="projects", )
def extendMarkdown(self, md, md_globals): # First, make it so that bare links get automatically linkified. AUTOLINK_RE = "(%s)" % "|".join( [ r"<((?:[Ff]|[Hh][Tt])[Tt][Pp][Ss]?://[^>]*)>", r"\b(?:[Ff]|[Hh][Tt])[Tt][Pp][Ss]?://[^)<>\s]+[^.,)<>\s]", r"<(Ii][Rr][Cc][Ss]?://[^>]*)>", r"\b[Ii][Rr][Cc][Ss]?://[^)<>\s]+[^.,)<>\s]", ] ) markdown.inlinepatterns.AUTOLINK_RE = AUTOLINK_RE md.preprocessors["implicit_issue"] = ImplicitIssuePreprocessor() md.inlinePatterns["mention"] = MentionPattern(MENTION_RE) # Customize the image linking to support lazy loading md.inlinePatterns["image_link"] = ImagePatternLazyLoad( markdown.inlinepatterns.IMAGE_LINK_RE, md ) md.inlinePatterns["implicit_commit"] = ImplicitCommitPattern( IMPLICIT_COMMIT_RE ) md.inlinePatterns["commit_links"] = CommitLinkPattern(COMMIT_LINK_RE) md.inlinePatterns["autolink"] = AutolinkPattern2(AUTOLINK_RE, md) if pagure_config.get("ENABLE_TICKETS", True): md.inlinePatterns["implicit_pr"] = ImplicitPRPattern( IMPLICIT_PR_RE ) md.inlinePatterns["explicit_fork_issue"] = ExplicitLinkPattern( EXPLICIT_LINK_RE ) md.inlinePatterns["implicit_issue"] = ImplicitIssuePattern( IMPLICIT_ISSUE_RE ) md.inlinePatterns["striked"] = StrikeThroughPattern(STRIKE_THROUGH_RE) md.registerExtension(self)
def fedora_messaging_publish(topic, message): # pragma: no cover """ Try to publish a message on AMQP using fedora-messaging. """ if not pagure_config.get("FEDORA_MESSAGING_NOTIFICATIONS", False): return try: import fedora_messaging.api import fedora_messaging.exceptions msg = fedora_messaging.api.Message( topic="pagure.{}".format(topic), body=message ) fedora_messaging.api.publish(msg) except fedora_messaging.exceptions.PublishReturned as e: _log.warning( "Fedora Messaging broker rejected message %s: %s", msg.id, e ) except fedora_messaging.exceptions.ConnectionException as e: _log.warning("Error sending message %s: %s", msg.id, e) except Exception: _log.exception("Error sending fedora-messaging message")
def decorated_function(*args, **kwargs): """ Decorated function, actually does the work. """ auth_method = pagure_config.get("PAGURE_AUTH", None) if flask.session.get("_justloggedout", False): return flask.redirect(flask.url_for("ui_ns.index")) elif not authenticated(): return flask.redirect( flask.url_for("auth_login", next=flask.request.url) ) elif auth_method == "fas" and not flask.g.fas_user.cla_done: flask.session["_requires_fpca"] = True flask.flash( flask.Markup( 'You must <a href="https://admin.fedoraproject' '.org/accounts/">sign the FPCA</a> (Fedora Project ' "Contributor Agreement) to use pagure" ), "errors", ) return flask.redirect(flask.url_for("ui_ns.index")) return function(*args, **kwargs)
def new_project(): """ Form to create a new project. """ user = pagure.lib.search_user(flask.g.session, username=flask.g.fas_user.username) if not pagure_config.get('ENABLE_NEW_PROJECTS', True) or \ not pagure_config.get('ENABLE_UI_NEW_PROJECTS', True): flask.abort( 404, 'Creation of new project is not allowed on this \ pagure instance') namespaces = pagure_config['ALLOWED_PREFIX'][:] if user: namespaces.extend([grp for grp in user.groups]) if pagure_config.get('USER_NAMESPACE', False): namespaces.insert(0, flask.g.fas_user.username) form = pagure.forms.ProjectForm(namespaces=namespaces) if form.validate_on_submit(): name = form.name.data description = form.description.data url = form.url.data avatar_email = form.avatar_email.data create_readme = form.create_readme.data private = False if pagure_config.get('PRIVATE_PROJECTS', False): private = form.private.data namespace = form.namespace.data if namespace: namespace = namespace.strip() try: task = pagure.lib.new_project( flask.g.session, name=name, private=private, description=description, namespace=namespace, url=url, avatar_email=avatar_email, user=flask.g.fas_user.username, blacklist=pagure_config['BLACKLISTED_PROJECTS'], allowed_prefix=pagure_config['ALLOWED_PREFIX'], gitfolder=pagure_config['GIT_FOLDER'], docfolder=pagure_config.get('DOCS_FOLDER'), ticketfolder=pagure_config.get('TICKETS_FOLDER'), requestfolder=pagure_config['REQUESTS_FOLDER'], add_readme=create_readme, userobj=user, prevent_40_chars=pagure_config.get('OLD_VIEW_COMMIT_ENABLED', False), user_ns=pagure_config.get('USER_NAMESPACE', False), ) flask.g.session.commit() return pagure.utils.wait_for_task(task) except pagure.exceptions.PagureException as err: flask.flash(str(err), 'error') except SQLAlchemyError as err: # pragma: no cover flask.g.session.rollback() flask.flash(str(err), 'error') return flask.render_template( 'new_project.html', form=form, )
def give_group(group): """ Allows giving away a group. """ if not pagure_config.get("ENABLE_USER_MNGT", True): flask.abort(404) group_type = "user" is_admin = pagure.utils.is_admin() if is_admin: group_type = None group = pagure.lib.query.search_groups(flask.g.session, group_name=group, group_type=group_type) if not group: flask.abort(404, description="Group not found") if group.creator.user != flask.g.fas_user.username and not flask.g.admin: flask.abort(403, description="You are not allowed to give away this group") # Give away group form = pagure.forms.ConfirmationForm() if form.validate_on_submit(): username = flask.request.form.get("username") if not username: flask.flash("No user %s found to give this group to" % username, "error") return flask.redirect( flask.url_for("ui_ns.view_group", group=group.group_name)) user = pagure.lib.query.search_user(flask.g.session, username=username) if not user: flask.flash("No user %s found to give this group to" % username, "error") return flask.redirect( flask.url_for("ui_ns.view_group", group=group.group_name)) try: if user not in group.users: pagure.lib.query.add_user_to_group( session=flask.g.session, username=username, group=group, user=flask.g.fas_user.username, is_admin=flask.g.admin, from_external=False, ) group.user_id = user.id flask.g.session.add(group) flask.g.session.commit() flask.flash("Group given") return flask.redirect( flask.url_for("ui_ns.view_group", group=group.group_name)) except SQLAlchemyError: # pragma: no cover flask.g.session.rollback() flask.flash("Could not give away group `%s`." % (group.group_name), "error") _log.exception("Could not give away group `%s`." % (group.group_name)) return flask.redirect( flask.url_for("ui_ns.view_group", group=group.group_name))
def index_auth(): """ Front page for authenticated user. """ user = _get_user(username=flask.g.fas_user.username) acl = flask.request.args.get("acl", "").strip().lower() or None repopage = flask.request.args.get("repopage", 1) try: repopage = int(repopage) if repopage < 1: repopage = 1 except ValueError: repopage = 1 limit = pagure_config["ITEM_PER_PAGE"] # PROJECTS start = limit * (repopage - 1) repos = pagure.lib.query.search_projects( flask.g.session, username=flask.g.fas_user.username, exclude_groups=pagure_config.get("EXCLUDE_GROUP_INDEX"), fork=False, private=flask.g.fas_user.username, start=start, limit=limit, ) if repos and acl: repos = _filter_acls(repos, acl, user) repos_length = pagure.lib.query.search_projects( flask.g.session, username=flask.g.fas_user.username, exclude_groups=pagure_config.get("EXCLUDE_GROUP_INDEX"), fork=False, private=flask.g.fas_user.username, count=True, ) total_repo_page = int( ceil(repos_length / float(limit)) if repos_length > 0 else 1) # FORKS forkpage = flask.request.args.get("forkpage", 1) try: forkpage = int(forkpage) if forkpage < 1: forkpage = 1 except ValueError: forkpage = 1 start = limit * (forkpage - 1) forks = pagure.lib.query.search_projects( flask.g.session, username=flask.g.fas_user.username, fork=True, private=flask.g.fas_user.username, start=start, limit=limit, ) forks_length = pagure.lib.query.search_projects( flask.g.session, username=flask.g.fas_user.username, fork=True, private=flask.g.fas_user.username, start=start, limit=limit, count=True, ) total_fork_page = int( ceil(forks_length / float(limit)) if forks_length > 0 else 1) watch_list = pagure.lib.query.user_watch_list( flask.g.session, user=flask.g.fas_user.username, exclude_groups=pagure_config.get("EXCLUDE_GROUP_INDEX"), ) return flask.render_template( "userdash_projects.html", username=flask.g.fas_user.username, user=user, forks=forks, repos=repos, watch_list=watch_list, repopage=repopage, repos_length=repos_length, total_repo_page=total_repo_page, forkpage=forkpage, forks_length=forks_length, total_fork_page=total_fork_page, )
def generate_acls(cls, project, group=None): """ Generate the gitolite configuration file for all repos :arg project: the project to update in the gitolite configuration file. It can be of three types/values. If it is ``-1`` or if the file does not exist on disk, the entire gitolite configuration will be re-generated. If it is ``None``, the gitolite configuration will not be changed but will be re-compiled. If it is a ``pagure.lib.model.Project``, the gitolite configuration will be updated for just this project. :type project: None, int or pagure.lib.model.Project :kwarg group: the group to refresh the members of :type group: None or pagure.lib.model.PagureGroup """ _log.info("Refresh gitolite configuration") if project is not None or group is not None: session = pagure.lib.query.create_session(pagure_config["DB_URL"]) cls.write_gitolite_acls( session, project=project, configfile=pagure_config["GITOLITE_CONFIG"], preconf=pagure_config.get("GITOLITE_PRE_CONFIG") or None, postconf=pagure_config.get("GITOLITE_POST_CONFIG") or None, group=group, ) session.remove() if ( not group and project not in [None, -1] and hasattr(cls, "_individual_repos_command") and pagure_config.get("GITOLITE_HAS_COMPILE_1", False) ): # optimization for adding single repo - we don't want to recompile # whole gitolite.conf repos_config = [] cls._process_project( project, repos_config, pagure_config.get("PR_ONLY", False) ) # repos_config will contain lines for repo itself as well as # docs, requests, tickets; compile-1 only accepts one repo, # so we have to run it separately for all of them for repo in cls._repos_from_lines(repos_config): repopath = repo.splitlines()[0][len("repo ") :].strip() repotype = repopath.split("/")[0] if ( repotype == "docs" and not pagure_config.get("ENABLE_DOCS") ) or ( repotype == "tickets" and not pagure_config.get("ENABLE_TICKETS") ): continue with tempfile.NamedTemporaryFile() as f: f.write(repo) f.flush() cmd = cls._individual_repos_command(f.name) cls._run_gitolite_cmd(cmd) else: cmd = cls._get_gitolite_command() cls._run_gitolite_cmd(cmd)
def new_project(): """ Form to create a new project. """ user = pagure.lib.query.search_user(flask.g.session, username=flask.g.fas_user.username) if not pagure_config.get("ENABLE_NEW_PROJECTS", True) or not pagure_config.get( "ENABLE_UI_NEW_PROJECTS", True): flask.abort( 404, "Creation of new project is not allowed on this \ pagure instance", ) namespaces = pagure_config["ALLOWED_PREFIX"][:] if user: namespaces.extend([grp for grp in user.groups]) if pagure_config.get("USER_NAMESPACE", False): namespaces.insert(0, flask.g.fas_user.username) form = pagure.forms.ProjectForm(namespaces=namespaces) if form.validate_on_submit(): name = form.name.data description = form.description.data url = form.url.data avatar_email = form.avatar_email.data create_readme = form.create_readme.data private = False if pagure_config.get("PRIVATE_PROJECTS", False): private = form.private.data namespace = form.namespace.data if namespace: namespace = namespace.strip() if form.repospanner_region: repospanner_region = form.repospanner_region.data else: repospanner_region = None if form.ignore_existing_repos: ignore_existing_repos = form.ignore_existing_repos.data else: ignore_existing_repos = False mirrored_from = form.mirrored_from.data if mirrored_from and pagure_config.get("DISABLE_MIRROR_IN", False): flask.flash( "Mirroring in projects has been disabled in this instance", "error", ) return flask.render_template("new_project.html", form=form) try: task = pagure.lib.query.new_project( flask.g.session, name=name, private=private, description=description, namespace=namespace, repospanner_region=repospanner_region, url=url, avatar_email=avatar_email, user=flask.g.fas_user.username, blacklist=pagure_config["BLACKLISTED_PROJECTS"], allowed_prefix=pagure_config["ALLOWED_PREFIX"], add_readme=create_readme, mirrored_from=mirrored_from, userobj=user, prevent_40_chars=pagure_config.get("OLD_VIEW_COMMIT_ENABLED", False), user_ns=pagure_config.get("USER_NAMESPACE", False), ignore_existing_repo=ignore_existing_repos, ) flask.g.session.commit() return pagure.utils.wait_for_task(task) except pagure.exceptions.PagureException as err: flask.flash(str(err), "error") except SQLAlchemyError as err: # pragma: no cover flask.g.session.rollback() flask.flash(str(err), "error") return flask.render_template("new_project.html", form=form)
def view_user2(username): """ Front page of a specific user. """ user = _get_user(username=username) acl = flask.request.args.get("acl", "").strip().lower() or None repopage = flask.request.args.get("repopage", 1) try: repopage = int(repopage) if repopage < 1: repopage = 1 except ValueError: repopage = 1 forkpage = flask.request.args.get("forkpage", 1) try: forkpage = int(forkpage) if forkpage < 1: forkpage = 1 except ValueError: forkpage = 1 limit = pagure_config["ITEM_PER_PAGE"] repo_start = limit * (repopage - 1) fork_start = limit * (forkpage - 1) private = False if authenticated() and username == flask.g.fas_user.username: private = flask.g.fas_user.username repos = pagure.lib.query.search_projects( flask.g.session, username=username, fork=False, exclude_groups=pagure_config.get("EXCLUDE_GROUP_INDEX"), start=repo_start, limit=limit, private=private, ) if repos and acl: repos = _filter_acls(repos, acl, user) repos_length = pagure.lib.query.search_projects( flask.g.session, username=username, fork=False, exclude_groups=pagure_config.get("EXCLUDE_GROUP_INDEX"), private=private, count=True, ) forks = pagure.lib.query.search_projects( flask.g.session, username=username, fork=True, start=fork_start, limit=limit, private=private, ) forks_length = pagure.lib.query.search_projects( flask.g.session, username=username, fork=True, private=private, count=True, ) total_page_repos = int(ceil(repos_length / float(limit))) total_page_forks = int(ceil(forks_length / float(limit))) return flask.render_template( "userprofile_overview.html", username=username, user=user, repos=repos, total_page_repos=total_page_repos, forks=forks, total_page_forks=total_page_forks, repopage=repopage, forkpage=forkpage, repos_length=repos_length, forks_length=forks_length, )
from sqlalchemy.exc import SQLAlchemyError import pagure.lib.query from pagure.config import config as pagure_config from pagure.lib.tasks_utils import pagure_task from pagure.mail_logging import format_callstack from pagure.lib.lib_ci import trigger_jenkins_build from pagure.utils import split_project_fullname, set_up_logging # logging.config.dictConfig(pagure_config.get('LOGGING') or {'version': 1}) _log = get_task_logger(__name__) _i = 0 if os.environ.get("PAGURE_BROKER_URL"): # pragma: no cover broker_url = os.environ["PAGURE_BROKER_URL"] elif pagure_config.get("BROKER_URL"): broker_url = pagure_config["BROKER_URL"] else: broker_url = "redis://%s" % pagure_config["REDIS_HOST"] conn = Celery("tasks", broker=broker_url, backend=broker_url) conn.conf.update(pagure_config["CELERY_CONFIG"]) @after_setup_task_logger.connect def augment_celery_log(**kwargs): set_up_logging(force=True) def call_web_hooks(project, topic, msg, urls): """ Sends the web-hook notification. """
import pagure.mail_logging import pagure.proxy import pagure.utils from pagure.config import config as pagure_config from pagure.utils import get_repo_path if os.environ.get("PAGURE_PERFREPO"): import pagure.perfrepo as perfrepo else: perfrepo = None logger = logging.getLogger(__name__) REDIS = None if (pagure_config["EVENTSOURCE_SOURCE"] or pagure_config["WEBHOOK"] or pagure_config.get("PAGURE_CI_SERVICES")): pagure.lib.query.set_redis( host=pagure_config["REDIS_HOST"], port=pagure_config["REDIS_PORT"], dbname=pagure_config["REDIS_DB"], ) if pagure_config.get("PAGURE_CI_SERVICES"): pagure.lib.query.set_pagure_ci(pagure_config["PAGURE_CI_SERVICES"]) def create_app(config=None): """ Create the flask application. """ app = flask.Flask(__name__) app.config = pagure_config
def remove_acls(cls, session, project): """ Remove a project from the configuration file for gitolite. :arg cls: the current class :type: Gitolite2Auth :arg session: the session with which to connect to the database :arg project: the project to remove from the gitolite configuration file. :type project: pagure.lib.model.Project """ _log.info("Remove project from the gitolite configuration file") if not project: raise RuntimeError("Project undefined") configfile = pagure_config["GITOLITE_CONFIG"] preconf = pagure_config.get("GITOLITE_PRE_CONFIG") or None postconf = pagure_config.get("GITOLITE_POST_CONFIG") or None if not os.path.exists(configfile): _log.info("Not configuration file found at: %s... bailing" % configfile) return preconfig = None if preconf: _log.info( "Loading the file to include at the top of the generated one") preconfig = _read_file(preconf) postconfig = None if postconf: _log.info( "Loading the file to include at the end of the generated one") postconfig = _read_file(postconf) config = [] groups = cls._generate_groups_config(session) _log.info("Removing the project from the configuration") current_config = cls._get_current_config(configfile, preconfig, postconfig) current_config = cls._clean_current_config(current_config, project) config = current_config + config if config: _log.info("Cleaning the groups from the loaded config") config = cls._clean_groups(config) else: current_config = cls._get_current_config(configfile, preconfig, postconfig) _log.info("Cleaning the groups from the config on disk") config = cls._clean_groups(config) if not config: return _log.info("Writing the configuration to: %s", configfile) with open(configfile, "w", encoding="utf-8") as stream: if preconfig: stream.write(preconfig + "\n") stream.write("# end of header\n") if groups: for key in sorted(groups): stream.write("@%s = %s\n" % (key, " ".join(groups[key]))) stream.write("# end of groups\n\n") prev = None for row in config: if prev is None: prev = row if prev == row == "": continue stream.write(row + "\n") prev = row stream.write("# end of body\n") if postconfig: stream.write(postconfig + "\n") gl_cache_path = os.path.join(os.path.dirname(configfile), "..", "gl-conf.cache") if os.path.exists(gl_cache_path): cls._remove_from_gitolite_cache(gl_cache_path, project)
if not username_lookup: if not expect_username: print("Pagure keyhelper configured incorrectly", file=sys.stderr) sys.exit(1) if username != expect_username: # Nothing to look up, this user is not git-related sys.exit(0) url = "%s/pv/ssh/lookupkey/" % pagure_config["APP_URL"] data = {"search_key": fingerprint} if username_lookup: data["username"] = username headers = {} if pagure_config.get("SSH_ADMIN_TOKEN"): headers["Authorization"] = "token %s" % pagure_config["SSH_ADMIN_TOKEN"] resp = requests.post(url, data=data, headers=headers) if not resp.status_code == 200: print( "Error during lookup request: status: %s" % resp.status_code, file=sys.stderr, ) sys.exit(1) result = resp.json() if not result["found"]: # Everything OK, key just didn't exist. sys.exit(0)
def write_gitolite_acls( cls, session, configfile, project, preconf=None, postconf=None, group=None, ): """ Generate the configuration file for gitolite for all projects on the forge. :arg cls: the current class :type: Gitolite2Auth :arg session: a session to connect to the database with :arg configfile: the name of the configuration file to generate/write :type configfile: str :arg project: the project to update in the gitolite configuration file. It can be of three types/values. If it is ``-1`` or if the file does not exist on disk, the entire gitolite configuration will be re-generated. If it is ``None``, the gitolite configuration will have its groups information updated but not the projects and will be re-compiled. If it is a ``pagure.lib.model.Project``, the gitolite configuration will be updated for just this project. :type project: None, int or spagure.lib.model.Project :kwarg preconf: a file to include at the top of the configuration file :type preconf: None or str :kwarg postconf: a file to include at the bottom of the configuration file :type postconf: None or str :kwarg group: the group to refresh the members of :type group: None or pagure.lib.model.PagureGroup """ _log.info("Write down the gitolite configuration file") preconfig = None if preconf: _log.info( "Loading the file to include at the top of the generated one") preconfig = _read_file(preconf) postconfig = None if postconf: _log.info( "Loading the file to include at the end of the generated one") postconfig = _read_file(postconf) global_pr_only = pagure_config.get("PR_ONLY", False) config = [] groups = {} if group is None: groups = cls._generate_groups_config(session) if project == -1 or not os.path.exists(configfile): _log.info("Refreshing the configuration for all projects") query = session.query(model.Project).order_by(model.Project.id) for project in query.all(): config = cls._process_project(project, config, global_pr_only) elif project: _log.info("Refreshing the configuration for one project") config = cls._process_project(project, config, global_pr_only) current_config = cls._get_current_config(configfile, preconfig, postconfig) current_config = cls._clean_current_config(current_config, project) config = current_config + config if config: _log.info("Cleaning the group %s from the loaded config", group) config = cls._clean_groups(config, group=group) else: current_config = cls._get_current_config(configfile, preconfig, postconfig) _log.info("Cleaning the group %s from the config on disk", group) config = cls._clean_groups(current_config, group=group) if not config: return _log.info("Writing the configuration to: %s", configfile) with open(configfile, "w", encoding="utf-8") as stream: if preconfig: stream.write(preconfig + "\n") stream.write("# end of header\n") if groups: for key in sorted(groups): stream.write("@%s = %s\n" % (key, " ".join(groups[key]))) stream.write("# end of groups\n\n") prev = None for row in config: if prev is None: prev = row if prev == row == "": continue stream.write(row + "\n") prev = row stream.write("# end of body\n") if postconfig: stream.write(postconfig + "\n")
def _process_project(cls, project, config, global_pr_only): """ Generate the gitolite configuration for the specified project. :arg project: the project to generate the configuration for :type project: pagure.lib.model.Project :arg config: a list containing the different lines of the configuration file :type config: list :arg groups: a dictionary containing the group name as key and the users member of the group as values :type groups: dict(str: list) :arg global_pr_only: boolean on whether the pagure instance enforces the PR workflow only or not :type global_pr_only: bool :return: the updated config :return type: list """ _log.debug(" Processing project: %s", project.fullname) # Check if the project or the pagure instance enforce the PR only # development model. pr_only = project.settings.get("pull_request_access_only", False) repos_to_create = ["repos"] if pagure_config.get("ENABLE_DOCS", True): repos_to_create.append("docs/") if pagure_config.get("ENABLE_TICKETS", True): repos_to_create.append("tickets/") # no setting yet to disable pull-requests repos_to_create.append("requests/") for repos in repos_to_create: if repos == "repos": # Do not grant access to project enforcing the PR model if pr_only or (global_pr_only and not project.is_fork): continue repos = "" config.append("repo %s%s" % (repos, project.fullname)) if not project.private and repos not in ["tickets/", "requests/"]: config.append(" R = @all") if project.committer_groups: config.append(" RW+ = @%s" % " @".join( [group.group_name for group in project.committer_groups])) config.append(" RW+ = %s" % project.user.user) for user in project.committers: # This should never be the case (that the project.user # is in the committers) but better safe than sorry if user.user != project.user.user: config.append(" RW+ = %s" % user.user) for deploykey in project.deploykeys: access = "R" if deploykey.pushaccess: access = "RW+" # Note: the replace of / with _ is because gitolite # users can't contain a /. At first, this might look # like deploy keys in a project called # $namespace_$project would give access to the repos of # a project $namespace/$project or vica versa, however # this is NOT the case because we add the deploykey.id # to the end of the deploykey name, which means it is # unique. The project name is solely there to make it # easier to determine what project created the deploykey # for admins. config.append(" %s = deploykey_%s_%s" % ( access, werkzeug.utils.secure_filename(project.fullname), deploykey.id, )) config.append("") return config
per_page = flask.request.values.get("per_page", None) or 20 if per_page: try: per_page = int(per_page) except (TypeError, ValueError): raise pagure.exceptions.APIError(400, error_code=APIERROR.EINVALIDREQ) if per_page < 1 or per_page > 100: raise pagure.exceptions.APIError( 400, error_code=APIERROR.EINVALIDPERPAGEVALUE) return per_page if pagure_config.get("ENABLE_TICKETS", True): from pagure.api import issue # noqa: E402 from pagure.api import fork # noqa: E402 from pagure.api import project # noqa: E402 from pagure.api import user # noqa: E402 from pagure.api import group # noqa: E402 if pagure_config.get("PAGURE_CI_SERVICES", False): from pagure.api.ci import jenkins # noqa: E402 @API.route("/version/") @API.route("/version") @API.route("/-/version") def api_version(): """
def run_project_hooks( session, username, project, hooktype, repotype, repodir, changes, is_internal, pull_request, ): """ Function to run the hooks on a project This will first call all the plugins with a Runner on the project, and afterwards, for a non-repoSpanner repo, run all hooks/<hooktype>.* scripts in the repo. Args: session: Database session username (string): The user performing a push project (model.Project): The project this call is made for repotype (string): Value of lib.REPOTYPES indicating for which repo the currnet call is repodir (string): Directory where a clone of the specified repo is located. Do note that this might or might not be a writable clone. hooktype (string): The type of hook to run: pre-receive, update or post-receive changes (dict): A dict with keys being the ref to update, values being a tuple of (from, to). is_internal (bool): Whether this push originated from Pagure internally pull_request (model.PullRequest or None): The pull request whose merge is initiating this hook run. """ debug = pagure_config.get("HOOK_DEBUG", False) # First we run dynamic ACLs authbackend = get_git_auth_helper() if (is_internal and username == "pagure" and repotype in ("tickets", "requests")): if debug: print("This is an internal push, dynamic ACL is pre-approved") elif not authbackend.is_dynamic: if debug: print("Auth backend %s is static-only" % authbackend) elif hooktype == "post-receive": if debug: print("Skipping auth backend during post-receive") else: if debug: print("Checking push request against auth backend %s" % authbackend) todeny = [] for refname in changes: change = changes[refname] authresult = authbackend.check_acl( session, project, username, refname, is_update=hooktype == "update", revfrom=change[0], revto=change[1], is_internal=is_internal, pull_request=pull_request, repotype=repotype, repodir=repodir, ) if debug: print("Auth result for ref %s: %s" % (refname, "Accepted" if authresult else "Denied")) if not authresult: print("Denied push for ref '%s' for user '%s'" % (refname, username)) todeny.append(refname) for toremove in todeny: del changes[toremove] if not changes: print("All changes have been rejected") sys.exit(1) # Now we run the hooks for plugins haderrors = False for plugin, _ in get_enabled_plugins(project, with_default=True): if not plugin.runner: if debug: print("Hook plugin %s should be ported to Runner" % plugin.name) else: if debug: print("Running plugin %s" % plugin.name) try: plugin.runner.runhook( session=session, username=username, hooktype=hooktype, project=project, repotype=repotype, repodir=repodir, changes=changes, ) except Exception: traceback.print_exc() haderrors = True if project.is_on_repospanner: # We are done. We are not doing any legacy hooks for repoSpanner return hookdir = os.path.join(repodir, "hooks") if not os.path.exists(hookdir): return stdin = "" args = [] if hooktype == "update": refname = six.next(six.iterkeys(changes)) (revfrom, revto) = changes[refname] args = [refname, revfrom, revto] else: stdin = ("\n".join([ "%s %s %s" % (changes[refname] + (refname, )) for refname in changes ]) + "\n") stdin = stdin.encode("utf-8") if debug: print("Running legacy hooks with args: %s, stdin: %s" % (args, stdin)) for hook in os.listdir(hookdir): # This is for legacy hooks, which create symlinks in the form of # "post-receive.$pluginname" if hook.startswith(hooktype + "."): hookfile = os.path.join(hookdir, hook) # Determine if this is an actual hook, or if it's a remnant # from a hook that was installed before it was moved to the # runner system. if os.path.realpath(hookfile) == pagure.lib.query.HOOK_DNE_TARGET: continue # Execute print("Running legacy hook %s. " "Please ask your admin to port this to the new plugin " "format, as the current system will cease functioning " "in a future Pagure release" % hook) # Using subprocess.Popen rather than check_call so that stdin # can be passed without having to use a temporary file. proc = subprocess.Popen([hookfile] + args, cwd=repodir, stdin=subprocess.PIPE) proc.communicate(stdin) ecode = proc.wait() if ecode != 0: print("Hook %s errored out" % hook) haderrors = True if haderrors: session.close() raise SystemExit(1)
def log_commit_send_notifications( self, session, name, commits, abspath, branch, default_branch, namespace=None, username=None, ): """ Send webhook notifications about an event on that project. :arg session: SQLAlchemy session object :type session: sqlalchemy.orm.session.Session :arg topic: the topic for the notification :type topic: str :arg msg: the message to send via web-hook :type msg: str :kwarg namespace: the namespace of the project :type namespace: None or str :kwarg name: the name of the project :type name: None or str :kwarg user: the user of the project, only set if the project is a fork :type user: None or str """ _log.info( "Looking for project: %s%s of %s", "%s/" % namespace if namespace else "", name, username, ) project = pagure.lib.query._get_project(session, name, user=username, namespace=namespace) if not project: _log.info("No project found") return _log.info("Found project: %s", project.fullname) _log.info("Processing %s commits in %s", len(commits), abspath) # Only log commits when the branch is the default branch log_all = pagure_config.get("LOG_ALL_COMMITS", False) if log_all or branch == default_branch: pagure.lib.git.log_commits_to_db(session, project, commits, abspath) else: _log.info( "Not logging commits not made on the default branch: %s", default_branch, ) # Notify subscribed users that there are new commits email_watchcommits = pagure_config.get("EMAIL_ON_WATCHCOMMITS", True) _log.info("Sending notification about the commit: %s", email_watchcommits) if email_watchcommits: pagure.lib.notify.notify_new_commits(abspath, project, branch, commits) try: session.commit() except SQLAlchemyError as err: # pragma: no cover _log.exception(err) session.rollback()
def set_request(): """ Prepare every request. """ flask.session.permanent = True if not hasattr(flask.g, "session") or not flask.g.session: flask.g.session = pagure.lib.query.create_session( flask.current_app.config["DB_URL"]) flask.g.version = pagure.__version__ flask.g.confirmationform = pagure.forms.ConfirmationForm() # The API namespace has its own way of getting repo and username and # of handling errors if flask.request.blueprint == "api_ns": return flask.g.forkbuttonform = None if pagure.utils.authenticated(): flask.g.forkbuttonform = pagure.forms.ConfirmationForm() # Force logout if current session started before users' # refuse_sessions_before login_time = flask.g.fas_user.login_time # This is because flask_fas_openid will store this as a posix timestamp if not isinstance(login_time, datetime.datetime): login_time = datetime.datetime.utcfromtimestamp(login_time) user = _get_user(username=flask.g.fas_user.username) if (user.refuse_sessions_before and login_time < user.refuse_sessions_before): logout() return flask.redirect(flask.url_for("ui_ns.index")) flask.g.justlogedout = flask.session.get("_justloggedout", False) if flask.g.justlogedout: flask.session["_justloggedout"] = None flask.g.new_user = False if flask.session.get("_new_user"): flask.g.new_user = True flask.session["_new_user"] = False flask.g.authenticated = pagure.utils.authenticated() flask.g.admin = pagure.utils.is_admin() # Retrieve the variables in the URL args = flask.request.view_args or {} # Check if there is a `repo` and an `username` repo = args.get("repo") username = args.get("username") namespace = args.get("namespace") # If there isn't a `repo` in the URL path, or if there is but the # endpoint called is part of the API, just don't do anything if repo: flask.g.repo = pagure.lib.query.get_authorized_project( flask.g.session, repo, user=username, namespace=namespace) if flask.g.authenticated: flask.g.repo_forked = pagure.lib.query.get_authorized_project( flask.g.session, repo, user=flask.g.fas_user.username, namespace=namespace, ) flask.g.repo_starred = pagure.lib.query.has_starred( flask.g.session, flask.g.repo, user=flask.g.fas_user.username) if (not flask.g.repo and namespace and pagure_config.get("OLD_VIEW_COMMIT_ENABLED", False) and len(repo) == 40): return flask.redirect( flask.url_for( "ui_ns.view_commit", repo=namespace, commitid=repo, username=username, namespace=None, )) if flask.g.repo is None: flask.abort(404, "Project not found") flask.g.reponame = get_repo_path(flask.g.repo) flask.g.repo_obj = pygit2.Repository(flask.g.reponame) flask.g.repo_admin = pagure.utils.is_repo_admin(flask.g.repo) flask.g.repo_committer = pagure.utils.is_repo_committer(flask.g.repo) flask.g.repo_user = pagure.utils.is_repo_user(flask.g.repo) flask.g.branches = sorted(flask.g.repo_obj.listall_branches()) repouser = flask.g.repo.user.user if flask.g.repo.is_fork else None fas_user = flask.g.fas_user if pagure.utils.authenticated() else None flask.g.repo_watch_levels = pagure.lib.query.get_watch_level_on_repo( flask.g.session, fas_user, flask.g.repo.name, repouser=repouser, namespace=namespace, ) items_per_page = pagure_config["ITEM_PER_PAGE"] flask.g.offset = 0 flask.g.page = 1 flask.g.limit = items_per_page page = flask.request.args.get("page") limit = flask.request.args.get("n") if limit: try: limit = int(limit) except ValueError: limit = 10 if limit > 500 or limit <= 0: limit = items_per_page flask.g.limit = limit if page: try: page = abs(int(page)) except ValueError: page = 1 if page <= 0: page = 1 flask.g.page = page flask.g.offset = (page - 1) * flask.g.limit
def view_group(group): """ Displays information about this group. """ if flask.request.method == "POST" and not pagure_config.get( "ENABLE_USER_MNGT", True ): flask.abort(404) group_type = "user" if pagure.utils.is_admin(): group_type = None group = pagure.lib.query.search_groups( flask.g.session, group_name=group, group_type=group_type ) if not group: flask.abort(404, "Group not found") # Add new user to the group if asked form = pagure.forms.AddUserToGroupForm() if ( flask.g.authenticated and form.validate_on_submit() and pagure_config.get("ENABLE_GROUP_MNGT", False) ): username = form.user.data try: msg = pagure.lib.query.add_user_to_group( flask.g.session, username=username, group=group, user=flask.g.fas_user.username, is_admin=pagure.utils.is_admin(), ) flask.g.session.commit() pagure.lib.git.generate_gitolite_acls( project=None, group=group.group_name ) flask.flash(msg) except pagure.exceptions.PagureException as err: flask.g.session.rollback() flask.flash("%s" % err, "error") return flask.redirect( flask.url_for("ui_ns.view_group", group=group.group_name) ) except SQLAlchemyError: # pragma: no cover flask.g.session.rollback() flask.flash( "Could not add user `%s` to group `%s`." % (username, group.group_name), "error", ) _log.exception( "Could not add user `%s` to group `%s`." % (username, group.group_name) ) member = False if flask.g.authenticated: member = pagure.lib.query.is_group_member( flask.g.session, flask.g.fas_user.username, group.group_name ) return flask.render_template( "group_info.html", group=group, form=form, member=member )
def create_app(config=None): """ Create the flask application. """ app = flask.Flask(__name__) app.config = pagure_config if config: app.config.update(config) if app.config.get("SESSION_TYPE", None) is not None: import flask_session flask_session.Session(app) pagure.utils.set_up_logging(app=app) @app.errorhandler(500) def fatal_error(error): # pragma: no cover """500 Fatal Error page""" logger.exception("Error while processing request") return flask.render_template("fatal_error.html", error=error), 500 app.jinja_env.trim_blocks = True app.jinja_env.lstrip_blocks = True if perfrepo: # Do this as early as possible. # We want the perfrepo before_request to be the very first thing # to be run, so that we can properly setup the stats before the # request. app.before_request(perfrepo.reset_stats) auth = pagure_config.get("PAGURE_AUTH", None) if auth in ["fas", "openid"]: # Only import and set flask_fas_openid if it is needed from pagure.ui.fas_login import FAS FAS.init_app(app) elif auth == "oidc": # Only import and set flask_fas_openid if it is needed from pagure.ui.oidc_login import oidc, fas_user_from_oidc oidc.init_app(app) app.before_request(fas_user_from_oidc) if auth == "local": # Only import the login controller if the app is set up for local login import pagure.ui.login as login app.before_request(login._check_session_cookie) app.after_request(login._send_session_cookie) # Support proxy app.wsgi_app = pagure.proxy.ReverseProxied(app.wsgi_app) # Back port 'equalto' to older version of jinja2 app.jinja_env.tests.setdefault("equalto", lambda value, other: value == other) # Import the application from pagure.api import API # noqa: E402 app.register_blueprint(API) from pagure.ui import UI_NS # noqa: E402 app.register_blueprint(UI_NS) from pagure.internal import PV # noqa: E402 app.register_blueprint(PV) # Import 3rd party blueprints plugin_config = flask.config.Config("") if "PAGURE_PLUGIN" in os.environ: plugin_config.from_envvar("PAGURE_PLUGIN") for blueprint in plugin_config.get("PLUGINS") or []: logger.info("Loading blueprint: %s", blueprint.name) app.register_blueprint(blueprint) themename = pagure_config.get("THEME", "default") here = os.path.abspath( os.path.join(os.path.dirname(os.path.abspath(__file__)))) themeblueprint = flask.Blueprint( "theme", __name__, static_url_path="/theme/static", static_folder=os.path.join(here, "themes", themename, "static"), ) # Jinja can be told to look for templates in different folders # That's what we do here template_folders = os.path.join( app.root_path, app.template_folder, os.path.join(here, "themes", themename, "templates"), ) import jinja2 # Jinja looks for the template in the order of the folders specified templ_loaders = [ jinja2.FileSystemLoader(template_folders), app.jinja_loader, ] app.jinja_loader = jinja2.ChoiceLoader(templ_loaders) app.register_blueprint(themeblueprint) app.before_request(set_request) app.teardown_request(end_request) if perfrepo: # Do this at the very end, so that this after_request comes last. app.after_request(perfrepo.print_stats) app.add_url_rule("/login/", view_func=auth_login, methods=["GET", "POST"]) app.add_url_rule("/logout/", view_func=auth_logout) return app
from cryptography import utils from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization import pagure.lib.query from pagure.config import config as pagure_config from pagure.lib.tasks import pagure_task from pagure.utils import ssh_urlpattern # logging.config.dictConfig(pagure_config.get('LOGGING') or {'version': 1}) _log = logging.getLogger(__name__) if os.environ.get("PAGURE_BROKER_URL"): # pragma: no-cover broker_url = os.environ["PAGURE_BROKER_URL"] elif pagure_config.get("BROKER_URL"): broker_url = pagure_config["BROKER_URL"] else: broker_url = "redis://%s" % pagure_config["REDIS_HOST"] conn = Celery("tasks_mirror", broker=broker_url, backend=broker_url) conn.conf.update(pagure_config["CELERY_CONFIG"]) # Code from: # https://github.com/pyca/cryptography/blob/6b08aba7f1eb296461528328a3c9871fa7594fc4/src/cryptography/hazmat/primitives/serialization.py#L161 # Taken from upstream cryptography since the version we have is too old # and doesn't have this code (yet) def _ssh_write_string(data): return struct.pack(">I", len(data)) + data
def set_user(return_url): """ After login method. """ if flask.g.fas_user.username is None: flask.flash( "It looks like your OpenID provider did not provide an " "username we could retrieve, username being needed we cannot " "go further.", "error", ) logout() return flask.redirect(return_url) flask.session["_new_user"] = False user = pagure.lib.query.search_user(flask.g.session, username=flask.g.fas_user.username) if not user: flask.session["_new_user"] = True else: user_email = pagure.lib.query.search_user(flask.g.session, email=flask.g.fas_user.email) if user_email and user_email.user != user.user: flask.flash( "This email address seems to already be associated with " "another account and thus can not be associated with yours", "error", ) logout() return flask.redirect(return_url) try: pagure.lib.query.set_up_user( session=flask.g.session, username=flask.g.fas_user.username, fullname=flask.g.fas_user.fullname, default_email=flask.g.fas_user.email, ssh_key=flask.g.fas_user.get("ssh_key"), keydir=pagure_config.get("GITOLITE_KEYDIR", None), ) # If groups are managed outside pagure, set up the user at login if not pagure_config.get("ENABLE_GROUP_MNGT", False): user = pagure.lib.query.search_user( flask.g.session, username=flask.g.fas_user.username) groups = set(user.groups) fas_groups = set(flask.g.fas_user.groups) # Add the new groups for group in fas_groups - groups: groupobj = None if group: groupobj = pagure.lib.query.search_groups(flask.g.session, group_name=group) if groupobj: try: pagure.lib.query.add_user_to_group( session=flask.g.session, username=flask.g.fas_user.username, group=groupobj, user=flask.g.fas_user.username, is_admin=pagure.utils.is_admin(), from_external=True, ) except pagure.exceptions.PagureException as err: _log.error(err) # Remove the old groups for group in groups - fas_groups: if group: try: pagure.lib.query.delete_user_of_group( session=flask.g.session, username=flask.g.fas_user.username, groupname=group, user=flask.g.fas_user.username, is_admin=pagure.utils.is_admin(), force=True, from_external=True, ) except pagure.exceptions.PagureException as err: _log.error(err) flask.g.session.commit() except SQLAlchemyError as err: flask.g.session.rollback() _log.exception(err) message = Markup( "Could not set up you as a user properly," ' please <a href="/about">contact an administrator</a>') flask.flash(message, "error") # Ensure the user is logged out if we cannot set them up # correctly logout() return flask.redirect(return_url)
def api(): """ Display the api information page. """ api_project_doc = load_doc(project.api_project) api_projects_doc = load_doc(project.api_projects) api_project_watchers_doc = load_doc(project.api_project_watchers) api_git_tags_doc = load_doc(project.api_git_tags) api_project_git_urls_doc = load_doc(project.api_project_git_urls) api_git_branches_doc = load_doc(project.api_git_branches) api_new_project_doc = load_doc(project.api_new_project) api_modify_project_doc = load_doc(project.api_modify_project) api_fork_project_doc = load_doc(project.api_fork_project) api_modify_acls_doc = load_doc(project.api_modify_acls) api_generate_acls_doc = load_doc(project.api_generate_acls) api_new_branch_doc = load_doc(project.api_new_branch) api_commit_flags_doc = load_doc(project.api_commit_flags) api_commit_add_flag_doc = load_doc(project.api_commit_add_flag) api_update_project_watchers_doc = load_doc( project.api_update_project_watchers) issues = [] if pagure_config.get("ENABLE_TICKETS", True): issues.append(load_doc(issue.api_new_issue)) issues.append(load_doc(issue.api_view_issues)) issues.append(load_doc(issue.api_view_issue)) issues.append(load_doc(issue.api_view_issue_comment)) issues.append(load_doc(issue.api_comment_issue)) issues.append(load_doc(issue.api_update_custom_field)) issues.append(load_doc(issue.api_update_custom_fields)) issues.append(load_doc(issue.api_change_status_issue)) issues.append(load_doc(issue.api_change_milestone_issue)) issues.append(load_doc(issue.api_assign_issue)) issues.append(load_doc(issue.api_subscribe_issue)) issues.append(load_doc(user.api_view_user_issues)) ci_doc = [] if pagure_config.get("PAGURE_CI_SERVICES", False): if "jenkins" in pagure_config["PAGURE_CI_SERVICES"]: ci_doc.append(load_doc(jenkins.jenkins_ci_notification)) api_pull_request_views_doc = load_doc(fork.api_pull_request_views) api_pull_request_view_doc = load_doc(fork.api_pull_request_view) api_pull_request_diffstats_doc = load_doc(fork.api_pull_request_diffstats) api_pull_request_by_uid_view_doc = load_doc( fork.api_pull_request_by_uid_view) api_pull_request_merge_doc = load_doc(fork.api_pull_request_merge) api_pull_request_close_doc = load_doc(fork.api_pull_request_close) api_pull_request_add_comment_doc = load_doc( fork.api_pull_request_add_comment) api_pull_request_add_flag_doc = load_doc(fork.api_pull_request_add_flag) api_version_doc = load_doc(api_version) api_whoami_doc = load_doc(api_whoami) api_users_doc = load_doc(api_users) api_view_user_doc = load_doc(user.api_view_user) api_view_user_activity_stats_doc = load_doc( user.api_view_user_activity_stats) api_view_user_activity_date_doc = load_doc( user.api_view_user_activity_date) api_view_user_requests_filed_doc = load_doc( user.api_view_user_requests_filed) api_view_user_requests_actionable_doc = load_doc( user.api_view_user_requests_actionable) api_view_group_doc = load_doc(group.api_view_group) api_groups_doc = load_doc(group.api_groups) if pagure_config.get("ENABLE_TICKETS", True): api_project_tags_doc = load_doc(api_project_tags) api_error_codes_doc = load_doc(api_error_codes) extras = [api_whoami_doc, api_version_doc, api_error_codes_doc] if pagure_config.get("ENABLE_TICKETS", True): extras.append(api_project_tags_doc) return flask.render_template( "api.html", version=pagure.__api_version__, api_doc=APIDOC, projects=[ api_new_project_doc, api_modify_project_doc, api_project_doc, api_projects_doc, api_git_tags_doc, api_project_git_urls_doc, api_project_watchers_doc, api_git_branches_doc, api_fork_project_doc, api_modify_acls_doc, api_generate_acls_doc, api_new_branch_doc, api_commit_flags_doc, api_commit_add_flag_doc, api_update_project_watchers_doc, ], issues=issues, requests=[ api_pull_request_views_doc, api_pull_request_view_doc, api_pull_request_diffstats_doc, api_pull_request_by_uid_view_doc, api_pull_request_merge_doc, api_pull_request_close_doc, api_pull_request_add_comment_doc, api_pull_request_add_flag_doc, ], users=[ api_users_doc, api_view_user_doc, api_view_user_activity_stats_doc, api_view_user_activity_date_doc, api_view_user_requests_filed_doc, api_view_user_requests_actionable_doc, ], groups=[api_groups_doc, api_view_group_doc], ci=ci_doc, extras=extras, )