Example #1
0
async def signature(request: requests.Request) -> None:
    # Only SHA1 is supported
    header_signature = request.headers.get("X-Hub-Signature")
    if header_signature is None:
        LOG.warning("Webhook without signature")
        raise fastapi.HTTPException(status_code=403)

    try:
        sha_name, signature = header_signature.split("=")
    except ValueError:
        sha_name = None

    if sha_name != "sha1":
        LOG.warning("Webhook signature malformed")
        raise fastapi.HTTPException(status_code=403)

    body = await request.body()

    current_hmac = utils.compute_hmac(body, config.WEBHOOK_SECRET)
    if hmac.compare_digest(current_hmac, str(signature)):
        return

    if config.WEBHOOK_SECRET_PRE_ROTATION is not None:
        future_hmac = utils.compute_hmac(body,
                                         config.WEBHOOK_SECRET_PRE_ROTATION)
        if hmac.compare_digest(future_hmac, str(signature)):
            return

    LOG.warning("Webhook signature invalid")
    raise fastapi.HTTPException(status_code=403)
Example #2
0
def run():
    parser = argparse.ArgumentParser()
    parser.add_argument("--clean", action="store_true")
    parser.add_argument("--dest", default="http://localhost:8802/event")

    args = parser.parse_args()

    logs.setup_logging()

    session = httpx.Client(trust_env=False)

    payload_data = os.urandom(250)
    payload_hmac = utils.compute_hmac(payload_data)

    if args.clean:
        # httpx doesn't allow data= here yet: https://github.com/encode/httpx/pull/900
        r = session.request(
            "DELETE",
            "https://gh.mergify.io/events-testing",
            data=payload_data,
            headers={"X-Hub-Signature": "sha1=" + payload_hmac},
        )
        r.raise_for_status()

    while True:
        try:
            # httpx doesn't allow data= here yet: https://github.com/encode/httpx/pull/900
            resp = session.request(
                "GET",
                "https://gh.mergify.io/events-testing",
                data=payload_data,
                headers={"X-Hub-Signature": "sha1=" + payload_hmac},
            )
            events = resp.json()
            for event in reversed(events):
                LOG.info("")
                LOG.info("==================================================")
                LOG.info(
                    ">>> GOT EVENT: %s %s/%s",
                    event["id"],
                    event["type"],
                    event["payload"].get("state", event["payload"].get("action")),
                )
                data = json.dumps(event["payload"])
                hmac = utils.compute_hmac(data.encode("utf8"))
                session.post(
                    args.dest,
                    headers={
                        "X-GitHub-Event": event["type"],
                        "X-GitHub-Delivery": event["id"],
                        "X-Hub-Signature": "sha1=%s" % hmac,
                        "Content-type": "application/json",
                    },
                    data=data,
                    verify=False,
                )
        except Exception:
            LOG.error("event handling failure", exc_info=True)
        time.sleep(1)
Example #3
0
async def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--clean", action="store_true")
    parser.add_argument("--dest", default="http://localhost:8802/event")

    args = parser.parse_args()

    logs.setup_logging()

    payload_data = os.urandom(250)
    payload_hmac = utils.compute_hmac(payload_data)

    async with http.AsyncClient(
            base_url="https://test-forwarder.mergify.io",
            headers={"X-Hub-Signature": "sha1=" + payload_hmac},
    ) as session:

        if args.clean:
            r = await session.request("DELETE",
                                      "/events-testing",
                                      data=payload_data)
            r.raise_for_status()

        while True:
            try:
                resp = await session.request("GET",
                                             "/events-testing",
                                             data=payload_data)
                events = resp.json()
                for event in reversed(events):
                    LOG.info("")
                    LOG.info(
                        "==================================================")
                    LOG.info(
                        ">>> GOT EVENT: %s %s/%s",
                        event["id"],
                        event["type"],
                        event["payload"].get("state",
                                             event["payload"].get("action")),
                    )
                    data = json.dumps(event["payload"])
                    hmac = utils.compute_hmac(data.encode("utf8"))
                    await session.post(
                        args.dest,
                        headers={
                            "X-GitHub-Event": event["type"],
                            "X-GitHub-Delivery": event["id"],
                            "X-Hub-Signature": f"sha1={hmac}",
                            "Content-type": "application/json",
                        },
                        data=data,
                    )
            except Exception:
                LOG.error("event handling failure", exc_info=True)
            time.sleep(1)
Example #4
0
def run():
    parser = argparse.ArgumentParser()
    parser.add_argument('--clean', action='store_true')
    parser.add_argument('--dest', default="http://localhost:8802/event")

    args = parser.parse_args()

    utils.setup_logging()
    config.log()

    session = requests.Session()
    session.trust_env = False

    payload_data = os.urandom(250)
    payload_hmac = utils.compute_hmac(payload_data)

    if args.clean:
        r = session.delete("https://gh.mergify.io/events-testing",
                           data=payload_data,
                           headers={"X-Hub-Signature": "sha1=" + payload_hmac})
        r.raise_for_status()

    while True:
        try:
            resp = session.get(
                "https://gh.mergify.io/events-testing",
                data=payload_data,
                headers={"X-Hub-Signature": "sha1=" + payload_hmac},
            )
            events = resp.json()
            for event in reversed(events):
                LOG.info("")
                LOG.info("==================================================")
                LOG.info(
                    ">>> GOT EVENT: %s %s/%s", event['id'], event['type'],
                    event['payload'].get("state",
                                         event['payload'].get("action")))
                data = json.dumps(event['payload'])
                hmac = utils.compute_hmac(data.encode("utf8"))
                session.post(args.dest,
                             headers={
                                 "X-GitHub-Event": event['type'],
                                 "X-GitHub-Delivery": event['id'],
                                 "X-Hub-Signature": "sha1=%s" % hmac,
                                 "Content-type": "application/json",
                             },
                             data=data,
                             verify=False)
        except Exception:
            LOG.error("event handling failure", exc_info=True)
        time.sleep(1)
Example #5
0
def test_market_event_forward(_, __, httpserver):

    with open(
            os.path.join(os.path.dirname(__file__), "events",
                         "market_event.json")) as f:
        data = f.read()

    headers = {
        "X-GitHub-Delivery": str(uuid.uuid4()),
        "X-GitHub-Event": "purchased",
        "X-Hub-Signature": "sha1=%s" % utils.compute_hmac(data.encode()),
        "User-Agent": "GitHub-Hookshot/044aadd",
        "Content-Type": "application/json",
    }
    httpserver.expect_request("/", method="POST", data=data,
                              headers=headers).respond_with_data("")

    with mock.patch(
            "mergify_engine.config.WEBHOOK_MARKETPLACE_FORWARD_URL",
            httpserver.url_for("/"),
    ):
        with testclient.TestClient(web.app) as client:
            client.post("/marketplace", data=data, headers=headers)

    httpserver.check_assertions()
def test_app_event_testing():
    with open(
        os.path.join(os.path.dirname(__file__), "events", "push_event.json"), "rb"
    ) as f:
        data = f.read()

    headers = {
        "X-GitHub-Delivery": str(uuid.uuid4()),
        "X-GitHub-Event": "push",
        "X-Hub-Signature": "sha1=%s" % utils.compute_hmac(data),
        "User-Agent": "GitHub-Hookshot/044aadd",
        "Content-Type": "application/json",
    }
    with testclient.TestClient(web.app) as client:
        client.delete("/events-testing", data=data, headers=headers)
        client.post("/events-testing", data=data, headers=headers)
        client.post("/events-testing", data=data, headers=headers)
        client.post("/events-testing", data=data, headers=headers)
        client.post("/events-testing", data=data, headers=headers)
        client.post("/events-testing", data=data, headers=headers)
        events = client.get(
            "/events-testing?number=3", data=data, headers=headers
        ).json()
        assert 3 == len(events)
        events = client.get("/events-testing", data=data, headers=headers).json()
        assert 2 == len(events)
        events = client.get("/events-testing", data=data, headers=headers).json()
        assert 0 == len(events)
        client.post("/events-testing", data=data, headers=headers)
        client.delete("/events-testing", data=data, headers=headers)
        events = client.get("/events-testing", data=data, headers=headers).json()
        assert 0 == len(events)
Example #7
0
def refresher() -> None:
    parser = argparse.ArgumentParser(
        description="Force refresh of mergify_engine")
    parser.add_argument("--action",
                        default="user",
                        choices=["user", "admin", "internal"])
    parser.add_argument(
        "urls",
        nargs="*",
        help=("<owner>/<repo>, <owner>/<repo>/branch/<branch>, "
              "<owner>/<repo>/pull/<pull#> or "
              "https://github.com/<owner>/<repo>/pull/<pull#>"),
    )

    args = parser.parse_args()

    if args.urls:
        for url in args.urls:
            url = url.replace("https://github.com/", "")
            data = secrets.token_hex(250)
            hmac = utils.compute_hmac(data.encode(), config.WEBHOOK_SECRET)
            asyncio.run(
                api_call(
                    "POST",
                    f"{config.BASE_URL}/refresh/{url}?action={args.action}",
                    headers={"X-Hub-Signature": "sha1=" + hmac},
                    content=data,
                ))
    else:
        parser.print_help()
Example #8
0
def api_call(url, method="post"):
    data = os.urandom(250)
    hmac = utils.compute_hmac(data)

    r = requests.request(
        method, url, headers={"X-Hub-Signature": "sha1=" + hmac}, data=data
    )
    r.raise_for_status()
    print(r.text)
Example #9
0
async def api_call(url, method="post"):
    data = os.urandom(250)
    hmac = utils.compute_hmac(data)

    async with http.AsyncClient() as client:
        r = await client.request(
            method, url, headers={"X-Hub-Signature": "sha1=" + hmac}, data=data
        )
    r.raise_for_status()
    print(r.text)
Example #10
0
def refresh(slug=None):
    data = os.urandom(250)
    hmac = utils.compute_hmac(data)

    url = config.BASE_URL + "/refresh"
    if slug:
        url = url + "/" + slug
    r = requests.post(url,
                      headers={"X-Hub-Signature": "sha1=" + hmac},
                      data=data)
    r.raise_for_status()
    print(r.text)
Example #11
0
def test_subscription_cache_delete():
    installation_id = 123

    data = None
    headers = {
        "X-Hub-Signature": "sha1=%s" % utils.compute_hmac(data),
    }
    with web.app.test_client() as client:
        reply = client.delete(f"/subscription-cache/{installation_id}",
                              data=data,
                              headers=headers)
        assert reply.status_code == 200
        assert reply.data == b"Cache cleaned"
Example #12
0
def test_subscription_cache_delete():
    owner_id = 123

    data = None
    headers = {
        "X-Hub-Signature": "sha1=%s" % utils.compute_hmac(data),
    }
    with testclient.TestClient(web.app) as client:
        reply = client.delete(f"/subscription-cache/{owner_id}",
                              data=data,
                              headers=headers)
        assert reply.status_code == 200
        assert reply.content == b"Cache cleaned"
Example #13
0
def test_subscription_cache_update():
    installation_id = 123
    charset = "utf-8"

    data = json.dumps({}).encode(
        charset)  # for this test the content does not matter
    headers = {
        "X-Hub-Signature": "sha1=%s" % utils.compute_hmac(data),
        "Content-Type": f"application/json; charset={charset}",
    }
    with testclient.TestClient(web.app) as client:
        reply = client.put(f"/subscription-cache/{installation_id}",
                           data=data,
                           headers=headers)
        assert reply.status_code == 200
        assert reply.content == b"Cache updated"
Example #14
0
def test_push_event(event, event_type, status_code, reason):
    with testclient.TestClient(web.app) as client:
        charset = "utf-8"
        data = json.dumps(event).encode(charset)
        headers = {
            "X-Hub-Signature": "sha1=%s" % utils.compute_hmac(data),
            "X-GitHub-Event": event_type,
            "Content-Type": f"application/json; charset={charset}",
        }
        reply = client.post(
            "/event",
            data=data,
            headers=headers,
        )
        assert reply.content == reason
        assert reply.status_code == status_code
Example #15
0
def test_market_event_forward(mocked_http_client, _, __, ___):

    with open(os.path.dirname(__file__) + "/market_event.json", "rb") as f:
        data = f.read()

    headers = {
        "X-GitHub-Delivery": str(uuid.uuid4()),
        "X-GitHub-Event": "purchased",
        "X-Hub-Signature": "sha1=%s" % utils.compute_hmac(data),
        "User-Agent": "GitHub-Hookshot/044aadd",
        "Content-Type": "application/json",
    }
    with testclient.TestClient(web.app) as client:
        client.post("/marketplace", data=data, headers=headers)

    mocked_http_client.return_value.__enter__.return_value.post.assert_called_with(
        "https://foobar/engine/market", data=data, headers=headers)
Example #16
0
def test_app_event_forward(mocked_requests_post, _, __, ___):

    with open(os.path.dirname(__file__) + "/push_event.json", "rb") as f:
        data = f.read()

    headers = {
        "X-GitHub-Delivery": str(uuid.uuid4()),
        "X-GitHub-Event": "push",
        "X-Hub-Signature": "sha1=%s" % utils.compute_hmac(data),
        "User-Agent": "GitHub-Hookshot/044aadd",
        "Content-Type": "application/json",
    }
    with web.app.test_client() as client:
        client.post("/event", data=data, headers=headers)

    mocked_requests_post.assert_called_with(
        "https://foobar/engine/app", data=data, headers=headers
    )
Example #17
0
def authentification():  # pragma: no cover
    # Only SHA1 is supported
    header_signature = flask.request.headers.get('X-Hub-Signature')
    if header_signature is None:
        LOG.warning("Webhook without signature")
        flask.abort(403)

    try:
        sha_name, signature = header_signature.split('=')
    except ValueError:
        sha_name = None

    if sha_name != 'sha1':
        LOG.warning("Webhook signature malformed")
        flask.abort(403)

    mac = utils.compute_hmac(flask.request.data)
    if not hmac.compare_digest(mac, str(signature)):
        LOG.warning("Webhook signature invalid")
        flask.abort(403)
Example #18
0
def test_subscription_cache_update():
    owner_id = 123
    charset = "utf-8"

    data = json.dumps({
        "subscription_active": True,
        "subscription_reason": "Customer",
        "tokens": {},
        "features": [],
    }).encode(charset)
    headers = {
        "X-Hub-Signature": "sha1=%s" % utils.compute_hmac(data),
        "Content-Type": f"application/json; charset={charset}",
    }
    with testclient.TestClient(web.app) as client:
        reply = client.put(f"/subscription-cache/{owner_id}",
                           data=data,
                           headers=headers)
        assert reply.status_code == 200
        assert reply.content == b"Cache updated"
Example #19
0
async def signature(request: requests.Request):
    # Only SHA1 is supported
    header_signature = request.headers.get("X-Hub-Signature")
    if header_signature is None:
        LOG.warning("Webhook without signature")
        raise fastapi.HTTPException(status_code=403)

    try:
        sha_name, signature = header_signature.split("=")
    except ValueError:
        sha_name = None

    if sha_name != "sha1":
        LOG.warning("Webhook signature malformed")
        raise fastapi.HTTPException(status_code=403)

    body = await request.body()
    mac = utils.compute_hmac(body)
    if not hmac.compare_digest(mac, str(signature)):
        LOG.warning("Webhook signature invalid")
        raise fastapi.HTTPException(status_code=403)
Example #20
0
import vcr

from mergify_engine import backports
from mergify_engine import branch_updater
from mergify_engine import check_api
from mergify_engine import config
from mergify_engine import utils
from mergify_engine import web
from mergify_engine import worker

LOG = daiquiri.getLogger(__name__)
RECORD = bool(os.getenv("MERGIFYENGINE_RECORD", False))
CASSETTE_LIBRARY_DIR_BASE = 'mergify_engine/tests/fixtures/cassettes'
FAKE_DATA = "whatdataisthat"
FAKE_HMAC = utils.compute_hmac(FAKE_DATA.encode("utf8"))


class GitterRecorder(utils.Gitter):
    def __init__(self, cassette_library_dir, suffix):
        super(GitterRecorder, self).__init__()
        self.cassette_path = os.path.join(cassette_library_dir,
                                          "git-%s.json" % suffix)
        if RECORD:
            self.records = []
        else:
            self.load_records()

    def load_records(self):
        if not os.path.exists(self.cassette_path):
            raise RuntimeError("Cassette %s not found" % self.cassette_path)
Example #21
0
from mergify_engine import github_types
from mergify_engine import gitter
from mergify_engine import redis_utils
from mergify_engine import utils
from mergify_engine import worker
from mergify_engine import worker_lua
from mergify_engine.clients import github
from mergify_engine.clients import http
from mergify_engine.dashboard import subscription
from mergify_engine.tests.functional import conftest as func_conftest
from mergify_engine.web import root

LOG = daiquiri.getLogger(__name__)
RECORD = bool(os.getenv("MERGIFYENGINE_RECORD", False))
FAKE_DATA = "whatdataisthat"
FAKE_HMAC = utils.compute_hmac(FAKE_DATA.encode("utf8"), config.WEBHOOK_SECRET)


class ForwardedEvent(typing.TypedDict):
    payload: github_types.GitHubEvent
    type: github_types.GitHubEventType
    id: str


class RecordException(typing.TypedDict):
    returncode: int
    output: str


class Record(typing.TypedDict):
    args: typing.List[typing.Any]