Beispiel #1
0
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")
Beispiel #2
0
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)
Beispiel #3
0
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)
Beispiel #4
0
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()
Beispiel #5
0
 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
Beispiel #6
0
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
Beispiel #7
0
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",
    )
Beispiel #8
0
    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
Beispiel #9
0
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,
    )
Beispiel #10
0
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))
Beispiel #11
0
 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)
Beispiel #12
0
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
Beispiel #13
0
 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)
Beispiel #14
0
 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)
Beispiel #15
0
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
Beispiel #16
0
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
Beispiel #17
0
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
Beispiel #18
0
 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
Beispiel #19
0
 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
Beispiel #20
0
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)
Beispiel #21
0
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"))
Beispiel #22
0
    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
Beispiel #23
0
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
Beispiel #24
0
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
    )
Beispiel #25
0
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
    )
Beispiel #26
0
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"
    )
Beispiel #27
0
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",
    )
Beispiel #28
0
    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)
Beispiel #29
0
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")
Beispiel #30
0
 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)
Beispiel #31
0
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,
    )
Beispiel #32
0
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))
Beispiel #33
0
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,
    )
Beispiel #34
0
    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)
Beispiel #35
0
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)
Beispiel #36
0
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,
    )
Beispiel #37
0
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. """
Beispiel #38
0
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
Beispiel #39
0
    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)
Beispiel #40
0
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)
Beispiel #41
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")
Beispiel #42
0
    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
Beispiel #43
0
    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():
    """
Beispiel #44
0
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)
Beispiel #45
0
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()
Beispiel #46
0
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
Beispiel #47
0
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
    )
Beispiel #48
0
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
Beispiel #49
0
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
Beispiel #50
0
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)
Beispiel #51
0
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,
    )