Blame pagure/utils.py

Pierre-Yves Chibon b130e5
# -*- coding: utf-8 -*-
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
"""
Pierre-Yves Chibon b130e5
 (c) 2017 - Copyright Red Hat Inc
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
 Authors:
Pierre-Yves Chibon b130e5
   Pierre-Yves Chibon <pingou@pingoured.fr></pingou@pingoured.fr>
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
"""
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon 67d1cc
from __future__ import unicode_literals, absolute_import
Aurélien Bompard dcf6f6
Pierre-Yves Chibon 0ccdba
import datetime
Pierre-Yves Chibon 24747e
import logging
Pierre-Yves Chibon 24747e
import logging.config
Pierre-Yves Chibon b130e5
import os
Pierre-Yves Chibon b130e5
import re
Aurélien Bompard 831553
from six.moves.urllib.parse import urlparse, urljoin
Pierre-Yves Chibon b130e5
from functools import wraps
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
import flask
Pierre-Yves Chibon b130e5
import pygit2
Aurélien Bompard 831553
import six
Pierre-Yves Chibon b130e5
import werkzeug
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon 0ccdba
from pagure.exceptions import (
Pierre-Yves Chibon 0ccdba
    PagureException,
Pierre-Yves Chibon 0ccdba
    InvalidTimestampException,
Pierre-Yves Chibon 0ccdba
    InvalidDateformatException,
Pierre-Yves Chibon 0ccdba
)
Pierre-Yves Chibon b130e5
from pagure.config import config as pagure_config
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon 24747e
_log = logging.getLogger(__name__)
Pierre-Yves Chibon 24747e
LOGGER_SETUP = False
Pierre-Yves Chibon 24747e
Pierre-Yves Chibon 24747e
Pierre-Yves Chibon 24747e
def set_up_logging(app=None, force=False):
Pierre-Yves Chibon 24747e
    global LOGGER_SETUP
Pierre-Yves Chibon 24747e
    if LOGGER_SETUP and not force:
Pierre-Yves Chibon 9c2953
        _log.info("logging already setup")
Pierre-Yves Chibon 24747e
        return
Pierre-Yves Chibon 24747e
Pierre-Yves Chibon 24747e
    logging.basicConfig()
Pierre-Yves Chibon 9c2953
    logging.config.dictConfig(pagure_config.get("LOGGING") or {"version": 1})
Pierre-Yves Chibon 24747e
Pierre-Yves Chibon 24747e
    LOGGER_SETUP = True
Pierre-Yves Chibon 24747e
Pierre-Yves Chibon 24747e
Pierre-Yves Chibon b130e5
def authenticated():
Pierre-Yves Chibon 9c2953
    """ Utility function checking if the current user is logged in or not.
Pierre-Yves Chibon 9c2953
    """
Pierre-Yves Chibon 9c2953
    return hasattr(flask.g, "fas_user") and flask.g.fas_user is not None
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
def api_authenticated():
Pierre-Yves Chibon 9c2953
    """ Utility function checking if the current user is logged in or not
Pierre-Yves Chibon b130e5
    in the API.
Pierre-Yves Chibon 9c2953
    """
Pierre-Yves Chibon 9c2953
    return (
Pierre-Yves Chibon 9c2953
        hasattr(flask.g, "fas_user")
Pierre-Yves Chibon 9c2953
        and flask.g.fas_user is not None
Pierre-Yves Chibon 9c2953
        and hasattr(flask.g, "token")
Pierre-Yves Chibon b130e5
        and flask.g.token is not None
Pierre-Yves Chibon 9c2953
    )
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Slavek Kabrda 984d0f
def check_api_acls(acls, optional=False):
Slavek Kabrda 984d0f
    """ Checks if the user provided an API token with its request and if
Slavek Kabrda 984d0f
    this token allows the user to access the endpoint desired.
Slavek Kabrda 984d0f
    """
Slavek Kabrda 984d0f
    import pagure.api
Slavek Kabrda 984d0f
    import pagure.lib.query
Slavek Kabrda 984d0f
Slavek Kabrda 984d0f
    if authenticated():
Slavek Kabrda 984d0f
        return
Slavek Kabrda 984d0f
Slavek Kabrda 984d0f
    flask.g.token = None
Slavek Kabrda 984d0f
    flask.g.fas_user = None
Slavek Kabrda 984d0f
    token = None
Slavek Kabrda 984d0f
    token_str = None
Slavek Kabrda 984d0f
Slavek Kabrda 984d0f
    if "Authorization" in flask.request.headers:
Slavek Kabrda 984d0f
        authorization = flask.request.headers["Authorization"]
Slavek Kabrda 984d0f
        if "token" in authorization:
Slavek Kabrda 984d0f
            token_str = authorization.split("token", 1)[1].strip()
Slavek Kabrda 984d0f
Slavek Kabrda 984d0f
    token_auth = False
Pierre-Yves Chibon 0a54a2
    error_msg = None
Slavek Kabrda 984d0f
    if token_str:
Slavek Kabrda 984d0f
        token = pagure.lib.query.get_api_token(flask.g.session, token_str)
Pierre-Yves Chibon 0a54a2
        if token:
Pierre-Yves Chibon 0a54a2
            if token.expired:
Pierre-Yves Chibon 0a54a2
                error_msg = "Expired token"
Pierre-Yves Chibon 0a54a2
            else:
Slavek Kabrda 984d0f
                flask.g.authenticated = True
Pierre-Yves Chibon 0a54a2
Pierre-Yves Chibon 0a54a2
                # Some ACLs are required
Pierre-Yves Chibon 0a54a2
                if acls:
Pierre-Yves Chibon 0a54a2
                    token_acls_set = set(token.acls_list)
Pierre-Yves Chibon 0a54a2
                    needed_acls_set = set(acls or [])
Pierre-Yves Chibon 0a54a2
                    overlap = token_acls_set.intersection(needed_acls_set)
Pierre-Yves Chibon 0a54a2
                    # Our token has some of the required ACLs:  auth successful
Pierre-Yves Chibon 0a54a2
                    if overlap:
Pierre-Yves Chibon 0a54a2
                        token_auth = True
Pierre-Yves Chibon 0a54a2
                        flask.g.fas_user = token.user
Pierre-Yves Chibon 0a54a2
                        # To get a token, in the `fas` auth user must have
Pierre-Yves Chibon 0a54a2
                        # signed the CLA, so just set it to True
Pierre-Yves Chibon 0a54a2
                        flask.g.fas_user.cla_done = True
Pierre-Yves Chibon 0a54a2
                        flask.g.token = token
Pierre-Yves Chibon 0a54a2
                        flask.g.authenticated = True
Pierre-Yves Chibon 0a54a2
                    # Our token has none of the required ACLs -> auth fail
Pierre-Yves Chibon 0a54a2
                    else:
Pierre-Yves Chibon 0a54a2
                        error_msg = "Missing ACLs: %s" % ", ".join(
Pierre-Yves Chibon 0a54a2
                            sorted(set(acls) - set(token.acls_list))
Pierre-Yves Chibon 0a54a2
                        )
Pierre-Yves Chibon 0a54a2
                # No ACL required
Pierre-Yves Chibon 0a54a2
                else:
Pierre-Yves Chibon 0a54a2
                    if optional:
Pierre-Yves Chibon 0a54a2
                        token_auth = True
Pierre-Yves Chibon 0a54a2
                        flask.g.fas_user = token.user
Pierre-Yves Chibon 0a54a2
                        # To get a token, in the `fas` auth user must have
Pierre-Yves Chibon 0a54a2
                        # signed the CLA, so just set it to True
Pierre-Yves Chibon 0a54a2
                        flask.g.fas_user.cla_done = True
Pierre-Yves Chibon 0a54a2
                        flask.g.token = token
Pierre-Yves Chibon 0a54a2
                        flask.g.authenticated = True
Pierre-Yves Chibon 0a54a2
        else:
Pierre-Yves Chibon 0a54a2
            error_msg = "Invalid token"
Pierre-Yves Chibon 0a54a2
Slavek Kabrda 984d0f
    elif optional:
Slavek Kabrda 984d0f
        return
Slavek Kabrda 984d0f
Pierre-Yves Chibon 0a54a2
    else:
Pierre-Yves Chibon 0a54a2
        error_msg = "Invalid token"
Pierre-Yves Chibon 0a54a2
Slavek Kabrda 984d0f
    if not token_auth:
Slavek Kabrda 984d0f
        output = {
Slavek Kabrda 984d0f
            "error_code": pagure.api.APIERROR.EINVALIDTOK.name,
Slavek Kabrda 984d0f
            "error": pagure.api.APIERROR.EINVALIDTOK.value,
Pierre-Yves Chibon 0a54a2
            "errors": error_msg,
Slavek Kabrda 984d0f
        }
Slavek Kabrda 984d0f
        jsonout = flask.jsonify(output)
Slavek Kabrda 984d0f
        jsonout.status_code = 401
Slavek Kabrda 984d0f
        return jsonout
Slavek Kabrda 984d0f
Slavek Kabrda 984d0f
Pierre-Yves Chibon b130e5
def is_safe_url(target):  # pragma: no cover
Pierre-Yves Chibon b130e5
    """ Checks that the target url is safe and sending to the current
Pierre-Yves Chibon b130e5
    website not some other malicious one.
Pierre-Yves Chibon b130e5
    """
Aurélien Bompard 831553
    ref_url = urlparse(flask.request.host_url)
Aurélien Bompard 831553
    test_url = urlparse(urljoin(flask.request.host_url, target))
Pierre-Yves Chibon 9c2953
    return (
Pierre-Yves Chibon 9c2953
        test_url.scheme in ("http", "https")
Pierre-Yves Chibon 9c2953
        and ref_url.netloc == test_url.netloc
Pierre-Yves Chibon 9c2953
    )
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
def is_admin():
Pierre-Yves Chibon b130e5
    """ Return whether the user is admin for this application or not. """
Pierre-Yves Chibon a76b0f
    if not authenticated():
Pierre-Yves Chibon b130e5
        return False
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
    user = flask.g.fas_user
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon 9c2953
    auth_method = pagure_config.get("PAGURE_AUTH", None)
Pierre-Yves Chibon 9c2953
    if auth_method == "fas":
Pierre-Yves Chibon b130e5
        if not user.cla_done:
Pierre-Yves Chibon b130e5
            return False
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon 9c2953
    admin_users = pagure_config.get("PAGURE_ADMIN_USERS", [])
Pierre-Yves Chibon b130e5
    if not isinstance(admin_users, list):
Pierre-Yves Chibon b130e5
        admin_users = [admin_users]
Pierre-Yves Chibon b130e5
    if user.username in admin_users:
Pierre-Yves Chibon b130e5
        return True
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon 9c2953
    admins = pagure_config["ADMIN_GROUP"]
Pierre-Yves Chibon b130e5
    if not isinstance(admins, list):
Pierre-Yves Chibon b130e5
        admins = [admins]
Pierre-Yves Chibon b130e5
    admins = set(admins or [])
Pierre-Yves Chibon b130e5
    groups = set(flask.g.fas_user.groups)
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
    return not groups.isdisjoint(admins)
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Ryan Lerch 336cd6
def is_repo_admin(repo_obj, username=None):
Pierre-Yves Chibon b130e5
    """ Return whether the user is an admin of the provided repo. """
Pierre-Yves Chibon b130e5
    if not authenticated():
Pierre-Yves Chibon b130e5
        return False
Pierre-Yves Chibon b130e5
Ryan Lerch 336cd6
    if username:
Ryan Lerch 336cd6
        user = username
Ryan Lerch 336cd6
    else:
Ryan Lerch 336cd6
        user = flask.g.fas_user.username
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
    if is_admin():
Pierre-Yves Chibon b130e5
        return True
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon 9c2953
    usergrps = [usr.user for grp in repo_obj.admin_groups for usr in grp.users]
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon 9c2953
    return (
Pierre-Yves Chibon 9c2953
        user == repo_obj.user.user
Pierre-Yves Chibon 9c2953
        or (user in [usr.user for usr in repo_obj.admins])
Pierre-Yves Chibon 9c2953
        or (user in usergrps)
Pierre-Yves Chibon 9c2953
    )
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Patrick Uiterwijk 79ade1
def is_repo_committer(repo_obj, username=None, session=None):
Pierre-Yves Chibon b130e5
    """ Return whether the user is a committer of the provided repo. """
Pierre-Yves Chibon 930073
    import pagure.lib.query
Pierre-Yves Chibon 930073
Pierre-Yves Chibon 2a1d4d
    usergroups = set()
Patrick Uiterwijk 9c3eac
    if username is None:
Patrick Uiterwijk 9c3eac
        if not authenticated():
Patrick Uiterwijk 9c3eac
            return False
Patrick Uiterwijk 9c3eac
        if is_admin():
Patrick Uiterwijk 9c3eac
            return True
Patrick Uiterwijk 9c3eac
        username = flask.g.fas_user.username
Patrick Uiterwijk 9c3eac
        usergroups = set(flask.g.fas_user.groups)
Pierre-Yves Chibon 2a1d4d
Pierre-Yves Chibon 2a1d4d
    if not session:
Pierre-Yves Chibon 2a1d4d
        session = flask.g.session
Pierre-Yves Chibon 2a1d4d
    try:
Pierre-Yves Chibon 930073
        user = pagure.lib.query.get_user(session, username)
Pierre-Yves Chibon 2a1d4d
        usergroups = usergroups.union(set(user.groups))
Pierre-Yves Chibon 2a1d4d
    except pagure.exceptions.PagureException:
Pierre-Yves Chibon 2a1d4d
        return False
Pierre-Yves Chibon b130e5
Patrick Uiterwijk 9c3eac
    # If the user is main admin -> yep
Patrick Uiterwijk 9c3eac
    if repo_obj.user.user == username:
Pierre-Yves Chibon b130e5
        return True
Pierre-Yves Chibon b130e5
Patrick Uiterwijk 9c3eac
    # If they are in the list of committers -> yep
Patrick Uiterwijk 9c3eac
    for user in repo_obj.committers:
Patrick Uiterwijk 9c3eac
        if user.user == username:
Patrick Uiterwijk 9c3eac
            return True
Patrick Uiterwijk 9c3eac
Patrick Uiterwijk 9c3eac
    # If they are in a group that has commit access -> yep
Patrick Uiterwijk 9c3eac
    for group in repo_obj.committer_groups:
Pierre-Yves Chibon 2a1d4d
        if group.group_name in usergroups:
Patrick Uiterwijk 9c3eac
            return True
Patrick Uiterwijk 9c3eac
Patrick Uiterwijk 9c3eac
    # If no direct committer, check EXTERNAL_COMMITTER info
Pierre-Yves Chibon 9c2953
    ext_committer = pagure_config.get("EXTERNAL_COMMITTER", None)
Pierre-Yves Chibon b130e5
    if ext_committer:
Patrick Uiterwijk 9c3eac
        overlap = set(ext_committer) & usergroups
Pierre-Yves Chibon b130e5
        if overlap:
Pierre-Yves Chibon b130e5
            for grp in overlap:
Pierre-Yves Chibon 9c2953
                restrict = ext_committer[grp].get("restrict", [])
Pierre-Yves Chibon 9c2953
                exclude = ext_committer[grp].get("exclude", [])
Pierre-Yves Chibon b130e5
                if restrict and repo_obj.fullname not in restrict:
Patrick Uiterwijk 9c3eac
                    continue
Pierre-Yves Chibon b130e5
                elif repo_obj.fullname in exclude:
Patrick Uiterwijk 9c3eac
                    continue
Pierre-Yves Chibon b130e5
                else:
Pierre-Yves Chibon b130e5
                    return True
Pierre-Yves Chibon b130e5
Patrick Uiterwijk 9c3eac
    # The user is not in an external_committer group that grants access, and
Patrick Uiterwijk 9c3eac
    # not a direct committer -> You have no power here
Patrick Uiterwijk 9c3eac
    return False
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Ryan Lerch 336cd6
def is_repo_user(repo_obj, username=None):
Pierre-Yves Chibon b130e5
    """ Return whether the user has some access in the provided repo. """
Ryan Lerch 336cd6
    if username:
Ryan Lerch 336cd6
        user = username
Ryan Lerch 336cd6
    else:
Patrick Uiterwijk 7b7c23
        if not authenticated():
Patrick Uiterwijk 7b7c23
            return False
Ryan Lerch 336cd6
        user = flask.g.fas_user.username
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
    if is_admin():
Pierre-Yves Chibon b130e5
        return True
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon 9c2953
    usergrps = [usr.user for grp in repo_obj.groups for usr in grp.users]
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon 9c2953
    return (
Pierre-Yves Chibon 9c2953
        user == repo_obj.user.user
Pierre-Yves Chibon 9c2953
        or (user in [usr.user for usr in repo_obj.users])
Pierre-Yves Chibon 9c2953
        or (user in usergrps)
Pierre-Yves Chibon 9c2953
    )
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Ryan Lerch 336cd6
def get_user_repo_access(repo_obj, username):
Ryan Lerch 336cd6
    """ return a string of the highest level of access
Ryan Lerch 336cd6
        a user has on a repo.
Ryan Lerch 336cd6
    """
Ryan Lerch 336cd6
    if repo_obj.user.username == username:
Ryan Lerch 336cd6
        return "main admin"
Ryan Lerch 336cd6
Ryan Lerch 336cd6
    if is_repo_admin(repo_obj, username):
Ryan Lerch 336cd6
        return "admin"
Ryan Lerch 336cd6
Ryan Lerch 336cd6
    if is_repo_committer(repo_obj, username):
Ryan Lerch 336cd6
        return "commit"
Ryan Lerch 336cd6
Ryan Lerch 336cd6
    if is_repo_user(repo_obj, username):
Ryan Lerch 336cd6
        return "ticket"
Ryan Lerch 336cd6
Ryan Lerch 336cd6
    return None
Ryan Lerch 336cd6
Ryan Lerch 336cd6
Pierre-Yves Chibon b130e5
def login_required(function):
Pierre-Yves Chibon b130e5
    """ Flask decorator to retrict access to logged in user.
Pierre-Yves Chibon b130e5
    If the auth system is ``fas`` it will also require that the user sign
Pierre-Yves Chibon b130e5
    the FPCA.
Pierre-Yves Chibon b130e5
    """
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
    @wraps(function)
Pierre-Yves Chibon b130e5
    def decorated_function(*args, **kwargs):
Pierre-Yves Chibon b130e5
        """ Decorated function, actually does the work. """
Pierre-Yves Chibon 08da0d
        auth_method = pagure_config.get("PAGURE_AUTH", None)
Pierre-Yves Chibon 9c2953
        if flask.session.get("_justloggedout", False):
Pierre-Yves Chibon 9c2953
            return flask.redirect(flask.url_for("ui_ns.index"))
Pierre-Yves Chibon b130e5
        elif not authenticated():
Pierre-Yves Chibon b130e5
            return flask.redirect(
Pierre-Yves Chibon 9c2953
                flask.url_for("auth_login", next=flask.request.url)
Pierre-Yves Chibon 9c2953
            )
Pierre-Yves Chibon 9c2953
        elif auth_method == "fas" and not flask.g.fas_user.cla_done:
Pierre-Yves Chibon 08da0d
            flask.session["_requires_fpca"] = True
Pierre-Yves Chibon 9c2953
            flask.flash(
Pierre-Yves Chibon 9c2953
                flask.Markup(
Pierre-Yves Chibon 9c2953
                    'You must 
Pierre-Yves Chibon 9c2953
                    '.org/accounts/">sign the FPCA (Fedora Project '
Pierre-Yves Chibon 9c2953
                    "Contributor Agreement) to use pagure"
Pierre-Yves Chibon 9c2953
                ),
Pierre-Yves Chibon 9c2953
                "errors",
Pierre-Yves Chibon 9c2953
            )
Pierre-Yves Chibon 9c2953
            return flask.redirect(flask.url_for("ui_ns.index"))
Pierre-Yves Chibon b130e5
        return function(*args, **kwargs)
Pierre-Yves Chibon 9c2953
Pierre-Yves Chibon b130e5
    return decorated_function
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
def __get_file_in_tree(repo_obj, tree, filepath, bail_on_tree=False):
Pierre-Yves Chibon 9c2953
    """ Retrieve the entry corresponding to the provided filename in a
Pierre-Yves Chibon b130e5
    given tree.
Pierre-Yves Chibon 9c2953
    """
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
    filename = filepath[0]
Pierre-Yves Chibon b130e5
    if isinstance(tree, pygit2.Blob):
Pierre-Yves Chibon b130e5
        return
Pierre-Yves Chibon b130e5
    for entry in tree:
Aurélien Bompard 831553
        fname = entry.name
Aurélien Bompard 831553
        if six.PY2:
Pierre-Yves Chibon 9c2953
            fname = entry.name.decode("utf-8")
Pierre-Yves Chibon b130e5
        if fname == filename:
Pierre-Yves Chibon b130e5
            if len(filepath) == 1:
Pierre-Yves Chibon b130e5
                blob = repo_obj.get(entry.id)
Pierre-Yves Chibon b130e5
                # If we can't get the content (for example: an empty folder)
Pierre-Yves Chibon b130e5
                if blob is None:
Pierre-Yves Chibon b130e5
                    return
Pierre-Yves Chibon b130e5
                # If we get a tree instead of a blob, let's escape
Pierre-Yves Chibon b130e5
                if isinstance(blob, pygit2.Tree) and bail_on_tree:
Pierre-Yves Chibon b130e5
                    return blob
Pierre-Yves Chibon b130e5
                content = blob.data
Pierre-Yves Chibon b130e5
                # If it's a (sane) symlink, we try a single-level dereference
Pierre-Yves Chibon 9c2953
                if (
Pierre-Yves Chibon 9c2953
                    entry.filemode == pygit2.GIT_FILEMODE_LINK
Pierre-Yves Chibon 9c2953
                    and os.path.normpath(content) == content
Pierre-Yves Chibon 9c2953
                    and not os.path.isabs(content)
Pierre-Yves Chibon 9c2953
                ):
Pierre-Yves Chibon b130e5
                    try:
Pierre-Yves Chibon b130e5
                        dereferenced = tree[content]
Pierre-Yves Chibon b130e5
                    except KeyError:
Pierre-Yves Chibon b130e5
                        pass
Pierre-Yves Chibon b130e5
                    else:
Pierre-Yves Chibon b130e5
                        if dereferenced.filemode == pygit2.GIT_FILEMODE_BLOB:
Pierre-Yves Chibon b130e5
                            blob = repo_obj[dereferenced.oid]
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
                return blob
Pierre-Yves Chibon b130e5
            else:
Pierre-Yves Chibon b130e5
                try:
Pierre-Yves Chibon b130e5
                    nextitem = repo_obj[entry.oid]
Pierre-Yves Chibon b130e5
                except KeyError:
Pierre-Yves Chibon b130e5
                    # We could not find the blob/entry in the git repo
Pierre-Yves Chibon b130e5
                    # so we bail
Pierre-Yves Chibon b130e5
                    return
Pierre-Yves Chibon b130e5
                # If we can't get the content (for example: an empty folder)
Pierre-Yves Chibon b130e5
                if nextitem is None:
Pierre-Yves Chibon b130e5
                    return
Pierre-Yves Chibon b130e5
                return __get_file_in_tree(
Pierre-Yves Chibon 9c2953
                    repo_obj, nextitem, filepath[1:], bail_on_tree=bail_on_tree
Pierre-Yves Chibon 9c2953
                )
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Karsten Hopp b2ebf2
ip_middle_octet = r"(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5]))"
Karsten Hopp b2ebf2
ip_last_octet = r"(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))"
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
"""
Pierre-Yves Chibon b130e5
regex based on https://github.com/kvesteri/validators/blob/
Pierre-Yves Chibon b130e5
master/validators/url.py
Pierre-Yves Chibon b130e5
LICENSED on Dec 16th 2016 as MIT:
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
The MIT License (MIT)
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Copyright (c) 2013-2014 Konsta Vesterinen
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Permission is hereby granted, free of charge, to any person obtaining a
Pierre-Yves Chibon b130e5
copy of this software and associated documentation files (the "Software"),
Pierre-Yves Chibon b130e5
to deal in the Software without restriction, including without limitation
Pierre-Yves Chibon b130e5
the rights to use, copy, modify, merge, publish, distribute, sublicense,
Pierre-Yves Chibon b130e5
and/or sell copies of the Software, and to permit persons to whom the
Pierre-Yves Chibon b130e5
Software is furnished to do so, subject to the following conditions:
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
The above copyright notice and this permission notice shall be included in
Pierre-Yves Chibon b130e5
all copies or substantial portions of the Software.
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
Pierre-Yves Chibon b130e5
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
Pierre-Yves Chibon b130e5
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Pierre-Yves Chibon b130e5
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
Pierre-Yves Chibon b130e5
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
Pierre-Yves Chibon b130e5
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
Pierre-Yves Chibon b130e5
IN THE SOFTWARE.
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
"""
Pierre-Yves Chibon b130e5
urlregex = re.compile(
Pierre-Yves Chibon 9c2953
    "^"
Pierre-Yves Chibon b130e5
    # protocol identifier
Karsten Hopp b2ebf2
    r"(?:(?:https?|ftp|git)://)"
Pierre-Yves Chibon b130e5
    # user:pass authentication
Pierre-Yves Chibon b7dbd6
    "(?:[-a-z\u00a1-\uffff0-9._~%!$&'()*+,;=:]+"
Pierre-Yves Chibon b7dbd6
    "(?::[-a-z0-9._~%!$&'()*+,;=:]*)?@)?"
Pierre-Yves Chibon b7dbd6
    "(?:"
Pierre-Yves Chibon b7dbd6
    "(?P<private_ip>"</private_ip>
Pierre-Yves Chibon b130e5
    # IP address exclusion
Pierre-Yves Chibon b130e5
    # private & local networks
Pierre-Yves Chibon 9c2953
    "(?:(?:10|127)" + ip_middle_octet + "{2}" + ip_last_octet + ")|"
Karsten Hopp b2ebf2
    r"(?:(?:169\.254|192\.168)" + ip_middle_octet + ip_last_octet + ")|"
Karsten Hopp b2ebf2
    r"(?:172\.(?:1[6-9]|2\d|3[0-1])" + ip_middle_octet + ip_last_octet + "))"
Pierre-Yves Chibon 9c2953
    "|"
Pierre-Yves Chibon b7dbd6
    # private & local hosts
Pierre-Yves Chibon b7dbd6
    "(?P<private_host>" "(?:localhost))" "|"</private_host>
Pierre-Yves Chibon b130e5
    # IP address dotted notation octets
Pierre-Yves Chibon b130e5
    # excludes loopback network 0.0.0.0
Pierre-Yves Chibon b130e5
    # excludes reserved space >= 224.0.0.0
Pierre-Yves Chibon b130e5
    # excludes network & broadcast addresses
Pierre-Yves Chibon b130e5
    # (first & last IP address of each class)
Pierre-Yves Chibon 9c2953
    "(?P<public_ip>"</public_ip>
Karsten Hopp b2ebf2
    r"(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])"
Pierre-Yves Chibon 9c2953
    "" + ip_middle_octet + "{2}"
Pierre-Yves Chibon 9c2953
    "" + ip_last_octet + ")"
Pierre-Yves Chibon 9c2953
    "|"
Pierre-Yves Chibon b7dbd6
    # IPv6 RegEx from https://stackoverflow.com/a/17871737
Karsten Hopp b2ebf2
    r"\[("
Pierre-Yves Chibon b7dbd6
    # 1:2:3:4:5:6:7:8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|"
Pierre-Yves Chibon b7dbd6
    # 1::                              1:2:3:4:5:6:7::
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,7}:|"
Pierre-Yves Chibon b7dbd6
    # 1::8             1:2:3:4:5:6::8  1:2:3:4:5:6::8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"
Pierre-Yves Chibon b7dbd6
    # 1::7:8           1:2:3:4:5::7:8  1:2:3:4:5::8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"
Pierre-Yves Chibon b7dbd6
    # 1::6:7:8         1:2:3:4::6:7:8  1:2:3:4::8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"
Pierre-Yves Chibon b7dbd6
    # 1::5:6:7:8       1:2:3::5:6:7:8  1:2:3::8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"
Pierre-Yves Chibon b7dbd6
    # 1::4:5:6:7:8     1:2::4:5:6:7:8  1:2::8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"
Pierre-Yves Chibon b7dbd6
    # 1::3:4:5:6:7:8   1::3:4:5:6:7:8  1::8
Pierre-Yves Chibon b7dbd6
    "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|"
Pierre-Yves Chibon b7dbd6
    # ::2:3:4:5:6:7:8  ::2:3:4:5:6:7:8 ::8       ::
Pierre-Yves Chibon b7dbd6
    ":((:[0-9a-fA-F]{1,4}){1,7}|:)|"
Pierre-Yves Chibon b7dbd6
    # fe80::7:8%eth0   fe80::7:8%1
Pierre-Yves Chibon b7dbd6
    # (link-local IPv6 addresses with zone index)
Pierre-Yves Chibon b7dbd6
    "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|"
Pierre-Yves Chibon b7dbd6
    "::(ffff(:0{1,4}){0,1}:){0,1}"
Karsten Hopp b2ebf2
    r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"
Pierre-Yves Chibon b7dbd6
    # ::255.255.255.255   ::ffff:255.255.255.255  ::ffff:0:255.255.255.255
Pierre-Yves Chibon b7dbd6
    # (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
Pierre-Yves Chibon b7dbd6
    "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|"
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,4}:"
Karsten Hopp b2ebf2
    r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"
Pierre-Yves Chibon b7dbd6
    # 2001:db8:3:4::192.0.2.33  64:ff9b::192.0.2.33
Pierre-Yves Chibon b7dbd6
    # (IPv4-Embedded IPv6 Address)
Karsten Hopp b2ebf2
    "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" r")\]|"
Pierre-Yves Chibon b130e5
    # host name
Pierre-Yves Chibon 9c2953
    "(?:(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)"
Pierre-Yves Chibon b130e5
    # domain name
Karsten Hopp b2ebf2
    r"(?:\.(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)*"
Pierre-Yves Chibon b130e5
    # TLD identifier
Karsten Hopp b2ebf2
    r"(?:\.(?:[a-z\u00a1-\uffff]{2,}))" ")"
Pierre-Yves Chibon b130e5
    # port number
Karsten Hopp b2ebf2
    r"(?::\d{2,5})?"
Pierre-Yves Chibon b130e5
    # resource path
Karsten Hopp b2ebf2
    r"(?:/[-a-z\u00a1-\uffff0-9._~%!$&'()*+,;=:@/]*)?"
Pierre-Yves Chibon b7dbd6
    # query string
Karsten Hopp b2ebf2
    r"(?:\?\S*)?"
Pierre-Yves Chibon b7dbd6
    # fragment
Karsten Hopp b2ebf2
    r"(?:#\S*)?" "$",
Pierre-Yves Chibon 9c2953
    re.UNICODE | re.IGNORECASE,
Pierre-Yves Chibon b130e5
)
Pierre-Yves Chibon b130e5
urlpattern = re.compile(urlregex)
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon 893d4f
ssh_urlregex = re.compile(
Pierre-Yves Chibon 9c2953
    "^"
Pierre-Yves Chibon 893d4f
    # protocol identifier
Karsten Hopp b2ebf2
    r"(?:(?:ssh|git\+ssh)://)?"
Pierre-Yves Chibon b7dbd6
    # user@ authentication
Pierre-Yves Chibon b7dbd6
    "[-a-z\u00a1-\uffff0-9._~%!$&'()*+,;=:]+@"
Pierre-Yves Chibon b7dbd6
    # Opening section about host
Pierre-Yves Chibon b7dbd6
    "(?:"
Pierre-Yves Chibon 893d4f
    # IP address exclusion
Pierre-Yves Chibon b7dbd6
    "(?P<private_ip>"</private_ip>
Pierre-Yves Chibon 893d4f
    # private & local networks
Pierre-Yves Chibon 9c2953
    "(?:(?:10|127)" + ip_middle_octet + "{2}" + ip_last_octet + ")|"
Karsten Hopp b2ebf2
    r"(?:(?:169\.254|192\.168)" + ip_middle_octet + ip_last_octet + ")|"
Karsten Hopp b2ebf2
    r"(?:172\.(?:1[6-9]|2\d|3[0-1])" + ip_middle_octet + ip_last_octet + "))"
Pierre-Yves Chibon 9c2953
    "|"
Pierre-Yves Chibon b7dbd6
    # private & local hosts
Pierre-Yves Chibon b7dbd6
    "(?P<private_host>" "(?:localhost))" "|"</private_host>
Pierre-Yves Chibon 893d4f
    # IP address dotted notation octets
Pierre-Yves Chibon 893d4f
    # excludes loopback network 0.0.0.0
Pierre-Yves Chibon 893d4f
    # excludes reserved space >= 224.0.0.0
Pierre-Yves Chibon 893d4f
    # excludes network & broadcast addresses
Pierre-Yves Chibon 893d4f
    # (first & last IP address of each class)
Pierre-Yves Chibon 9c2953
    "(?P<public_ip>"</public_ip>
Karsten Hopp b2ebf2
    r"(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])"
Pierre-Yves Chibon 9c2953
    "" + ip_middle_octet + "{2}"
Pierre-Yves Chibon 9c2953
    "" + ip_last_octet + ")"
Pierre-Yves Chibon 9c2953
    "|"
Pierre-Yves Chibon b7dbd6
    # IPv6 RegEx from https://stackoverflow.com/a/17871737
Karsten Hopp b2ebf2
    r"\[("
Pierre-Yves Chibon b7dbd6
    # 1:2:3:4:5:6:7:8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|"
Pierre-Yves Chibon b7dbd6
    # 1::                              1:2:3:4:5:6:7::
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,7}:|"
Pierre-Yves Chibon b7dbd6
    # 1::8             1:2:3:4:5:6::8  1:2:3:4:5:6::8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"
Pierre-Yves Chibon b7dbd6
    # 1::7:8           1:2:3:4:5::7:8  1:2:3:4:5::8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"
Pierre-Yves Chibon b7dbd6
    # 1::6:7:8         1:2:3:4::6:7:8  1:2:3:4::8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"
Pierre-Yves Chibon b7dbd6
    # 1::5:6:7:8       1:2:3::5:6:7:8  1:2:3::8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"
Pierre-Yves Chibon b7dbd6
    # 1::4:5:6:7:8     1:2::4:5:6:7:8  1:2::8
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"
Pierre-Yves Chibon b7dbd6
    # 1::3:4:5:6:7:8   1::3:4:5:6:7:8  1::8
Pierre-Yves Chibon b7dbd6
    "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|"
Pierre-Yves Chibon b7dbd6
    # ::2:3:4:5:6:7:8  ::2:3:4:5:6:7:8 ::8       ::
Pierre-Yves Chibon b7dbd6
    ":((:[0-9a-fA-F]{1,4}){1,7}|:)|"
Pierre-Yves Chibon b7dbd6
    # fe80::7:8%eth0   fe80::7:8%1
Pierre-Yves Chibon b7dbd6
    # (link-local IPv6 addresses with zone index)
Pierre-Yves Chibon b7dbd6
    "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|"
Pierre-Yves Chibon b7dbd6
    "::(ffff(:0{1,4}){0,1}:){0,1}"
Karsten Hopp b2ebf2
    r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"
Pierre-Yves Chibon b7dbd6
    # ::255.255.255.255   ::ffff:255.255.255.255  ::ffff:0:255.255.255.255
Pierre-Yves Chibon b7dbd6
    # (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
Pierre-Yves Chibon b7dbd6
    "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|"
Pierre-Yves Chibon b7dbd6
    "([0-9a-fA-F]{1,4}:){1,4}:"
Karsten Hopp b2ebf2
    r"((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}"
Pierre-Yves Chibon b7dbd6
    # 2001:db8:3:4::192.0.2.33  64:ff9b::192.0.2.33
Pierre-Yves Chibon b7dbd6
    # (IPv4-Embedded IPv6 Address)
Karsten Hopp b2ebf2
    r"(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])" r")\]|"
Pierre-Yves Chibon 893d4f
    # host name
Karsten Hopp b2ebf2
    r"(?:(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)"
Pierre-Yves Chibon 893d4f
    # domain name
Karsten Hopp b2ebf2
    r"(?:\.(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)*"
Pierre-Yves Chibon 893d4f
    # TLD identifier
Karsten Hopp b2ebf2
    r"(?:\.(?:[a-z\u00a1-\uffff]{2,}))"
Pierre-Yves Chibon b7dbd6
    # Closing the entire section about host
Pierre-Yves Chibon b7dbd6
    ")"
Pierre-Yves Chibon 893d4f
    # port number
Karsten Hopp b2ebf2
    r"(?::\d{2,5})?"
Pierre-Yves Chibon 893d4f
    # resource path
Karsten Hopp b2ebf2
    r"(?:[:/][-a-z\u00a1-\uffff0-9._~%!$&'()*+,;=:@/]*)?"
Pierre-Yves Chibon b7dbd6
    # query string
Karsten Hopp b2ebf2
    r"(?:\?\S*)?"
Pierre-Yves Chibon b7dbd6
    # fragment
Karsten Hopp b2ebf2
    r"(?:#\S*)?" "$",
Pierre-Yves Chibon 9c2953
    re.UNICODE | re.IGNORECASE,
Pierre-Yves Chibon 893d4f
)
Pierre-Yves Chibon 893d4f
ssh_urlpattern = re.compile(ssh_urlregex)
Pierre-Yves Chibon 893d4f
Pierre-Yves Chibon 893d4f
Pierre-Yves Chibon b130e5
def get_repo_path(repo):
Pierre-Yves Chibon b130e5
    """ Return the path of the git repository corresponding to the provided
Pierre-Yves Chibon b130e5
    Repository object from the DB.
Pierre-Yves Chibon b130e5
    """
Patrick Uiterwijk 3f97f6
    repopath = repo.repopath("main")
Pierre-Yves Chibon b130e5
    if not os.path.exists(repopath):
Pierre-Yves Chibon c6cc5c
        flask.abort(404, description="No git repo found")
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
    return repopath
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
def get_remote_repo_path(remote_git, branch_from, ignore_non_exist=False):
Pierre-Yves Chibon b130e5
    """ Return the path of the remote git repository corresponding to the
Pierre-Yves Chibon b130e5
    provided information.
Pierre-Yves Chibon b130e5
    """
Pierre-Yves Chibon b130e5
    repopath = os.path.join(
Pierre-Yves Chibon 9c2953
        pagure_config["REMOTE_GIT_FOLDER"],
Pierre-Yves Chibon 9c2953
        werkzeug.secure_filename("%s_%s" % (remote_git, branch_from)),
Pierre-Yves Chibon b130e5
    )
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
    if not os.path.exists(repopath) and not ignore_non_exist:
Pierre-Yves Chibon b130e5
        return None
Pierre-Yves Chibon b130e5
    else:
Pierre-Yves Chibon b130e5
        return repopath
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Aurélien Bompard a7f281
def get_task_redirect_url(task, prev):
Aurélien Bompard a7f281
    if not task.ready():
Pierre-Yves Chibon 9c2953
        return flask.url_for("ui_ns.wait_task", taskid=task.id, prev=prev)
Aurélien Bompard a7f281
    result = task.get(timeout=0, propagate=False)
Aurélien Bompard a7f281
    if task.failed():
Pierre-Yves Chibon 9c2953
        flask.flash("Your task failed: %s" % result)
Aurélien Bompard a7f281
        task.forget()
Aurélien Bompard a7f281
        return prev
Pierre-Yves Chibon aac557
    if isinstance(result, dict):
Pierre-Yves Chibon 9c2953
        endpoint = result.pop("endpoint")
Pierre-Yves Chibon aac557
        task.forget()
Pierre-Yves Chibon aac557
        return flask.url_for(endpoint, **result)
Pierre-Yves Chibon aac557
    else:
Pierre-Yves Chibon aac557
        task.forget()
Pierre-Yves Chibon aac557
        flask.abort(418)
Aurélien Bompard a7f281
Aurélien Bompard a7f281
Aurélien Bompard a7f281
def wait_for_task(task, prev=None):
Pierre-Yves Chibon b130e5
    if prev is None:
Pierre-Yves Chibon b130e5
        prev = flask.request.full_path
Aurélien Bompard a7f281
    elif not is_safe_url(prev):
Pierre-Yves Chibon 9c2953
        prev = flask.url_for("index")
Aurélien Bompard a7f281
    return flask.redirect(get_task_redirect_url(task, prev))
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
Pierre-Yves Chibon b130e5
def wait_for_task_post(taskid, form, endpoint, initial=False, **kwargs):
Pierre-Yves Chibon b130e5
    form_action = flask.url_for(endpoint, **kwargs)
Pierre-Yves Chibon b130e5
    return flask.render_template(
Pierre-Yves Chibon 9c2953
        "waiting_post.html",
Pierre-Yves Chibon b130e5
        taskid=taskid,
Pierre-Yves Chibon b130e5
        form_action=form_action,
Pierre-Yves Chibon b130e5
        form_data=form.data,
Pierre-Yves Chibon b130e5
        csrf=form.csrf_token,
Pierre-Yves Chibon 9c2953
        initial=initial,
Pierre-Yves Chibon 9c2953
    )
Clement Verna ba73e0
Clement Verna ba73e0
Clement Verna ba73e0
def split_project_fullname(project_name):
Clement Verna ba73e0
    """Returns the user, namespace and
Clement Verna ba73e0
    project name from a project fullname"""
Clement Verna ba73e0
Clement Verna ba73e0
    user = None
Clement Verna ba73e0
    namespace = None
Pierre-Yves Chibon 9c2953
    if "/" in project_name:
Pierre-Yves Chibon 9c2953
        project_items = project_name.split("/")
Clement Verna ba73e0
Clement Verna ba73e0
        if len(project_items) == 2:
Clement Verna ba73e0
            namespace, project_name = project_items
Clement Verna ba73e0
        elif len(project_items) == 3:
Clement Verna ba73e0
            _, user, project_name = project_items
Clement Verna ba73e0
        elif len(project_items) == 4:
Clement Verna ba73e0
            _, user, namespace, project_name = project_items
Clement Verna ba73e0
Clement Verna ba73e0
    return (user, namespace, project_name)
Pierre-Yves Chibon 78ba11
Pierre-Yves Chibon 78ba11
Patrick Uiterwijk 3f97f6
def get_parent_repo_path(repo, repotype="main"):
Pierre-Yves Chibon 78ba11
    """ Return the path of the parent git repository corresponding to the
Pierre-Yves Chibon 78ba11
    provided Repository object from the DB.
Pierre-Yves Chibon 78ba11
    """
Pierre-Yves Chibon 78ba11
    if repo.parent:
Patrick Uiterwijk 3f97f6
        return repo.parent.repopath(repotype)
Pierre-Yves Chibon 78ba11
    else:
Patrick Uiterwijk 3f97f6
        return repo.repopath(repotype)
Patrick Uiterwijk 71124e
Patrick Uiterwijk 71124e
Patrick Uiterwijk 71124e
def stream_template(app, template_name, **context):
Patrick Uiterwijk 71124e
    app.update_template_context(context)
Patrick Uiterwijk 71124e
    t = app.jinja_env.get_template(template_name)
Patrick Uiterwijk 71124e
    rv = t.stream(context)
Patrick Uiterwijk 71124e
    rv.enable_buffering(5)
Patrick Uiterwijk 71124e
    return rv
Aurélien Bompard 619e2a
Aurélien Bompard 619e2a
Pierre-Yves Chibon 9c2953
def is_true(value, trueish=("1", "true", "t", "y")):
Aurélien Bompard 619e2a
    if isinstance(value, bool):
Aurélien Bompard 619e2a
        return value
Aurélien Bompard 619e2a
    if isinstance(value, six.binary_type):
Aurélien Bompard 619e2a
        # In Py3, str(b'true') == "b'true'", not b'true' as in Py2.
Aurélien Bompard 619e2a
        value = value.decode()
Aurélien Bompard 619e2a
    else:
Aurélien Bompard 619e2a
        value = str(value)
Aurélien Bompard 619e2a
    return value.strip().lower() in trueish
Slavek Kabrda cdb053
Slavek Kabrda cdb053
Pierre-Yves Chibon 0ccdba
def validate_date(input_date, allow_empty=False):
Pierre-Yves Chibon 0ccdba
    """ Validate a given time.
Pierre-Yves Chibon 0ccdba
    The time can either be given as an unix timestamp or using the
Pierre-Yves Chibon 0ccdba
    yyyy-mm-dd format.
Pierre-Yves Chibon 0ccdba
    If either fail to parse, we raise a 400 error
Pierre-Yves Chibon 0ccdba
    """
Pierre-Yves Chibon 0ccdba
    if allow_empty and input_date == "":
Pierre-Yves Chibon 0ccdba
        return None
Pierre-Yves Chibon 0ccdba
    # Validate and convert the time
Pierre-Yves Chibon 0ccdba
    if input_date.isdigit():
Pierre-Yves Chibon 0ccdba
        # We assume its a timestamp, so convert it to datetime
Pierre-Yves Chibon 0ccdba
        try:
Pierre-Yves Chibon 0ccdba
            output_date = datetime.datetime.fromtimestamp(int(input_date))
Pierre-Yves Chibon 0ccdba
        except ValueError:
Pierre-Yves Chibon 0ccdba
            raise InvalidTimestampException()
Pierre-Yves Chibon 0ccdba
    else:
Pierre-Yves Chibon 0ccdba
        # We assume datetime format, so validate it
Pierre-Yves Chibon 0ccdba
        try:
Pierre-Yves Chibon 0ccdba
            output_date = datetime.datetime.strptime(input_date, "%Y-%m-%d")
Pierre-Yves Chibon 0ccdba
        except ValueError:
Pierre-Yves Chibon 0ccdba
            raise InvalidDateformatException()
Pierre-Yves Chibon 0ccdba
Pierre-Yves Chibon 0ccdba
    return output_date
Pierre-Yves Chibon 0ccdba
Pierre-Yves Chibon 0ccdba
Pierre-Yves Chibon 0ccdba
def validate_date_range(value):
Pierre-Yves Chibon 0ccdba
    """ Validate a given date range specified using the format since..until.
Pierre-Yves Chibon 0ccdba
    If .. is not present in the range, it is assumed that only since was
Pierre-Yves Chibon 0ccdba
    provided.
Pierre-Yves Chibon 0ccdba
    """
Pierre-Yves Chibon 0ccdba
    since = until = None
Pierre-Yves Chibon 0ccdba
    if value is not None:
Pierre-Yves Chibon 0ccdba
        if ".." in value:
Pierre-Yves Chibon 0ccdba
            since, _, until = value.partition("..")
Pierre-Yves Chibon 0ccdba
        else:
Pierre-Yves Chibon 0ccdba
            since = value
Pierre-Yves Chibon 0ccdba
        if since is not None:
Pierre-Yves Chibon 0ccdba
            since = validate_date(since, allow_empty=True)
Pierre-Yves Chibon 0ccdba
        if until is not None:
Pierre-Yves Chibon 0ccdba
            until = validate_date(until, allow_empty=True)
Pierre-Yves Chibon 0ccdba
    return (since, until)
Pierre-Yves Chibon 0ccdba
Pierre-Yves Chibon 0ccdba
Slavek Kabrda cdb053
def get_merge_options(request, merge_status):
Slavek Kabrda cdb053
    MERGE_OPTIONS = {
Slavek Kabrda cdb053
        "NO_CHANGE": {
Slavek Kabrda cdb053
            "code": "NO_CHANGE",
Slavek Kabrda cdb053
            "short_code": "No changes",
Slavek Kabrda cdb053
            "message": "Nothing to change, git is up to date",
Slavek Kabrda cdb053
        },
Slavek Kabrda cdb053
        "FFORWARD": {
Slavek Kabrda cdb053
            "code": "FFORWARD",
Slavek Kabrda cdb053
            "short_code": "Ok",
Slavek Kabrda cdb053
            "message": "The pull-request can be merged and fast-forwarded",
Slavek Kabrda cdb053
        },
Slavek Kabrda cdb053
        "CONFLICTS": {
Slavek Kabrda cdb053
            "code": "CONFLICTS",
Slavek Kabrda cdb053
            "short_code": "Conflicts",
Slavek Kabrda cdb053
            "message": "The pull-request cannot be merged due to conflicts",
Slavek Kabrda cdb053
        },
Slavek Kabrda cdb053
        "MERGE-non-ff-ok": {
Slavek Kabrda cdb053
            "code": "MERGE",
Slavek Kabrda cdb053
            "short_code": "With merge",
Slavek Kabrda cdb053
            "message": "The pull-request can be merged with a merge commit",
Slavek Kabrda cdb053
        },
Slavek Kabrda cdb053
        "MERGE-non-ff-bad": {
Pierre-Yves Chibon d9e8b6
            "code": "NEEDSREBASE",
Pierre-Yves Chibon d9e8b6
            "short_code": "Needs rebase",
Slavek Kabrda cdb053
            "message": "The pull-request must be rebased before merging",
Pierre-Yves Chibon 998b35
        },
Slavek Kabrda cdb053
    }
Slavek Kabrda cdb053
Slavek Kabrda cdb053
    if merge_status == "MERGE":
Pierre-Yves Chibon 98d023
        if request.project.settings.get(
Pierre-Yves Chibon 998b35
            "disable_non_fast-forward_merges", False
Pierre-Yves Chibon 998b35
        ):
Slavek Kabrda cdb053
            merge_status += "-non-ff-bad"
Slavek Kabrda cdb053
        else:
Slavek Kabrda cdb053
            merge_status += "-non-ff-ok"
Slavek Kabrda cdb053
Slavek Kabrda cdb053
    return MERGE_OPTIONS[merge_status]
Patrick Uiterwijk 19d7c5
Patrick Uiterwijk 19d7c5
Patrick Uiterwijk 19d7c5
def lookup_deploykey(project, username):
Patrick Uiterwijk 19d7c5
    """ Finds the Deploy Key specified by the username.
Patrick Uiterwijk 19d7c5
Patrick Uiterwijk 19d7c5
    Args:
Patrick Uiterwijk 19d7c5
        project (model.Project): The project to look in
Patrick Uiterwijk 19d7c5
        username (string): The username string provided for the deploy key
Patrick Uiterwijk 19d7c5
    Returns (model.SSHKey or None): The SSHKey instance representing the
Patrick Uiterwijk 19d7c5
        project-specific deploy key by the username. None if the username is
Patrick Uiterwijk 19d7c5
        not a deploykey username or is not a valid deploy key for project.
Patrick Uiterwijk 19d7c5
    """
Patrick Uiterwijk 19d7c5
    # The username to look for is: deploykey_(filename(project.fullname))_keyid
Patrick Uiterwijk 19d7c5
    if not username.startswith("deploykey_"):
Patrick Uiterwijk 19d7c5
        return None
Patrick Uiterwijk 19d7c5
    username = username[len("deploykey_") :]
Patrick Uiterwijk 19d7c5
    rest, keyid = username.rsplit("_", 1)
Patrick Uiterwijk 19d7c5
    if rest != werkzeug.secure_filename(project.fullname):
Patrick Uiterwijk 19d7c5
        # This is not a deploykey for the specified project
Patrick Uiterwijk 19d7c5
        return None
Patrick Uiterwijk 19d7c5
    keyid = int(keyid)
Patrick Uiterwijk 19d7c5
    for key in project.deploykeys:
Patrick Uiterwijk 19d7c5
        if key.id == keyid:
Patrick Uiterwijk 19d7c5
            return key
Patrick Uiterwijk 19d7c5
    return None
Slavek Kabrda a80d7c
Slavek Kabrda a80d7c
Slavek Kabrda a80d7c
def project_has_hook_attr_value(project, hook, attr, value):
Slavek Kabrda a80d7c
    """ Finds out if project's hook has attribute of given value.
Slavek Kabrda a80d7c
Slavek Kabrda a80d7c
    :arg project: The project to inspect
Slavek Kabrda a80d7c
    :type project: pagure.lib.model.Project
Slavek Kabrda a80d7c
    :arg hook: Name of the hook to inspect
Slavek Kabrda a80d7c
    :type hook: str
Slavek Kabrda a80d7c
    :arg attr: Name of hook attribute to inspect
Slavek Kabrda a80d7c
    :type attr: str
Slavek Kabrda a80d7c
    :arg value: Value to compare project's hook attribute value with
Slavek Kabrda a80d7c
    :type value: object
Slavek Kabrda a80d7c
    :return: True if project's hook attribute value is equal with given
Slavek Kabrda a80d7c
        value, False otherwise
Slavek Kabrda a80d7c
    """
Slavek Kabrda a80d7c
    retval = False
Slavek Kabrda a80d7c
    hook_obj = getattr(project, hook, None)
Slavek Kabrda a80d7c
    if hook_obj is not None:
Slavek Kabrda a80d7c
        attr_obj = getattr(hook_obj, attr, None)
Slavek Kabrda a80d7c
        if attr_obj == value:
Slavek Kabrda a80d7c
            retval = True
Slavek Kabrda a80d7c
Slavek Kabrda a80d7c
    return retval
Pierre-Yves Chibon c13fca
Pierre-Yves Chibon c13fca
Pierre-Yves Chibon c13fca
def parse_path(path):
Pierre-Yves Chibon c13fca
    """Get the repo name, object type, object ID, and (if present)
Pierre-Yves Chibon c13fca
    username and/or namespace from a URL path component. Will only
Pierre-Yves Chibon c13fca
    handle the known object types from the OBJECTS dict. Assumes:
Pierre-Yves Chibon c13fca
    * Project name comes immediately before object type
Pierre-Yves Chibon c13fca
    * Object ID comes immediately after object type
Pierre-Yves Chibon c13fca
    * If a fork, path starts with /fork/(username)
Pierre-Yves Chibon c13fca
    * Namespace, if present, comes after fork username (if present) or at start
Pierre-Yves Chibon c13fca
    * No other components come before the project name
Pierre-Yves Chibon c13fca
    * None of the parsed items can contain a /
Pierre-Yves Chibon c13fca
    """
Pierre-Yves Chibon c13fca
    username = None
Pierre-Yves Chibon c13fca
    namespace = None
Pierre-Yves Chibon c13fca
    # path always starts with / so split and throw away first item
Pierre-Yves Chibon c13fca
    items = path.split("/")[1:]
Pierre-Yves Chibon c13fca
    # find the *last* match for any object type
Pierre-Yves Chibon c13fca
    try:
Pierre-Yves Chibon c13fca
        objtype = [
Pierre-Yves Chibon c13fca
            item for item in items if item in ["issue", "pull-request"]
Pierre-Yves Chibon c13fca
        ][-1]
Pierre-Yves Chibon c13fca
    except IndexError:
Pierre-Yves Chibon c13fca
        raise PagureException("No known object type found in path: %s" % path)
Pierre-Yves Chibon c13fca
    try:
Pierre-Yves Chibon c13fca
        # objid is the item after objtype, we need all items up to it
Pierre-Yves Chibon c13fca
        items = items[: items.index(objtype) + 2]
Pierre-Yves Chibon c13fca
        # now strip the repo, objtype and objid off the end
Pierre-Yves Chibon c13fca
        (repo, objtype, objid) = items[-3:]
Pierre-Yves Chibon c13fca
        items = items[:-3]
Pierre-Yves Chibon c13fca
    except (IndexError, ValueError):
Pierre-Yves Chibon c13fca
        raise PagureException(
Pierre-Yves Chibon c13fca
            "No project or object ID found in path: %s" % path
Pierre-Yves Chibon c13fca
        )
Pierre-Yves Chibon c13fca
    # now check for a fork
Pierre-Yves Chibon c13fca
    if items and items[0] == "fork":
Pierre-Yves Chibon c13fca
        try:
Pierre-Yves Chibon c13fca
            # get the username and strip it and 'fork'
Pierre-Yves Chibon c13fca
            username = items[1]
Pierre-Yves Chibon c13fca
            items = items[2:]
Pierre-Yves Chibon c13fca
        except IndexError:
Pierre-Yves Chibon c13fca
            raise PagureException(
Pierre-Yves Chibon c13fca
                "Path starts with /fork but no user found! Path: %s" % path
Pierre-Yves Chibon c13fca
            )
Pierre-Yves Chibon c13fca
    # if we still have an item left, it must be the namespace
Pierre-Yves Chibon c13fca
    if items:
Pierre-Yves Chibon c13fca
        namespace = items.pop(0)
Pierre-Yves Chibon c13fca
    # if we have any items left at this point, we've no idea
Pierre-Yves Chibon c13fca
    if items:
Pierre-Yves Chibon c13fca
        raise PagureException(
Pierre-Yves Chibon c13fca
            "More path components than expected! Path: %s" % path
Pierre-Yves Chibon c13fca
        )
Pierre-Yves Chibon c13fca
Pierre-Yves Chibon c13fca
    return username, namespace, repo, objtype, objid