|
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
|