Esempio n. 1
0
def test_mod_response():
    router = Router()
    route1a = router.get("https://foo.bar/baz/") % 409
    route1b = router.get("https://foo.bar/baz/") % 404
    route2 = router.get("https://foo.bar") % dict(status_code=201)
    route3 = router.post("https://fox.zoo/") % httpx.Response(401, json={"error": "x"})

    request = httpx.Request("GET", "https://foo.bar/baz/")
    resolved = router.resolve(request)
    assert resolved.response.status_code == 404
    assert resolved.route is route1b
    assert route1a is route1b

    request = httpx.Request("GET", "https://foo.bar/")
    resolved = router.resolve(request)
    assert resolved.response.status_code == 201
    assert resolved.route is route2

    request = httpx.Request("POST", "https://fox.zoo/")
    resolved = router.resolve(request)
    assert resolved.response.status_code == 401
    assert resolved.response.json() == {"error": "x"}
    assert resolved.route is route3

    with pytest.raises(TypeError, match="Route can only"):
        router.route() % []
Esempio n. 2
0
def test_mod_response():
    router = Router()
    route1a = router.get("https://foo.bar") % 404
    route1b = router.get("https://foo.bar") % dict(status_code=201)
    route2 = router.get("https://ham.spam/egg/") % MockResponse(202)
    route3 = router.post("https://fox.zoo/") % httpx.Response(
        401, json={"error": "x"})

    request = httpx.Request("GET", "https://foo.bar")
    matched_route, response = router.match(request)
    assert response.status_code == 404
    assert matched_route is route1a

    request = httpx.Request("GET", "https://foo.bar")
    matched_route, response = router.match(request)
    assert response.status_code == 201
    assert matched_route is route1b
    assert route1a is route1b

    request = httpx.Request("GET", "https://ham.spam/egg/")
    matched_route, response = router.match(request)
    assert response.status_code == 202
    assert matched_route is route2

    request = httpx.Request("POST", "https://fox.zoo/")
    matched_route, response = router.match(request)
    assert response.status_code == 401
    assert response.json() == {"error": "x"}
    assert matched_route is route3
Esempio n. 3
0
async def mock_oidc_provider_token(respx_mock: respx.Router, code: str,
                                   token: OIDCToken) -> None:
    """Mock out the API for the upstream OpenID Connect provider.

    Parameters
    ----------
    respx_mock : `respx.Router`
        The mock router.
    code : `str`
        The code that Gafaelfawr must send to redeem for a token.
    token : `gafaelfawr.models.oidc.OIDCToken`
        The token to return after authentication.
    """
    config = await config_dependency()
    assert config.oidc
    mock = MockOIDCToken(config, code, token)
    respx_mock.post(config.oidc.token_url).mock(side_effect=mock.post_token)
Esempio n. 4
0
async def mock_github(
    respx_mock: respx.Router,
    code: str,
    user_info: GitHubUserInfo,
    *,
    paginate_teams: bool = False,
    expect_revoke: bool = False,
) -> None:
    """Set up the mocks for a GitHub userinfo call.

    Parameters
    ----------
    respx_mock : `respx.Router`
        The mock router.
    code : `str`
        The code that Gafaelfawr must send to redeem a token.
    user_info : `gafaelfawr.providers.github.GitHubUserInfo`
        User information to use to synthesize GitHub API responses.
    paginate_teams : `bool`, optional
        Whether to paginate the team results.  Default: `False`
    expect_revoke : `bool`, optional
        Whether to expect a revocation of the token after returning all user
        information.  Default: `False`
    """
    config = await config_dependency()
    assert config.github
    mock = MockGitHub(
        respx_mock,
        config.github,
        code,
        user_info,
        paginate_teams,
        expect_revoke,
    )
    token_url = GitHubProvider._TOKEN_URL
    respx_mock.post(token_url).mock(side_effect=mock.post_token)
    emails_url = GitHubProvider._EMAILS_URL
    respx_mock.get(emails_url).mock(side_effect=mock.get_emails)
    teams_url = GitHubProvider._TEAMS_URL
    respx_mock.get(url__startswith=teams_url).mock(side_effect=mock.get_teams)
    respx_mock.get(GitHubProvider._USER_URL).mock(side_effect=mock.get_user)
Esempio n. 5
0
async def test_connection_error(tmp_path: Path, client: AsyncClient,
                                respx_mock: respx.Router) -> None:
    config = await configure(tmp_path, "oidc")
    assert config.oidc
    return_url = "https://example.com/foo"

    r = await client.get("/login", params={"rd": return_url})
    assert r.status_code == 307
    url = urlparse(r.headers["Location"])
    query = parse_qs(url.query)

    # Register a connection error for the callback request to the OIDC
    # provider and check that an appropriate error is shown to the user.
    token_url = config.oidc.token_url
    respx_mock.post(token_url).mock(side_effect=ConnectError)
    r = await client.get("/login",
                         params={
                             "code": "some-code",
                             "state": query["state"][0]
                         })
    assert r.status_code == 403
    assert "Cannot contact authentication provider" in r.text
Esempio n. 6
0
def test_side_effect_with_route_kwarg():
    router = Router()

    def foobar(request, route, slug):
        response = httpx.Response(201, json={"id": route.call_count + 1, "slug": slug})
        if route.call_count > 0:
            route.mock(return_value=httpx.Response(501))
        return response

    router.post(path__regex=r"/(?P<slug>\w+)/").mock(side_effect=foobar)

    request = httpx.Request("POST", "https://foo.bar/baz/")
    response = router.handler(request)
    assert response.status_code == 201
    assert response.json() == {"id": 1, "slug": "baz"}

    response = router.handler(request)
    assert response.status_code == 201
    assert response.json() == {"id": 2, "slug": "baz"}

    response = router.handler(request)
    assert response.status_code == 501
Esempio n. 7
0
async def test_callback_error(
    tmp_path: Path,
    client: AsyncClient,
    respx_mock: respx.Router,
    caplog: LogCaptureFixture,
) -> None:
    """Test an error return from the OIDC token endpoint."""
    config = await configure(tmp_path, "oidc")
    assert config.oidc
    return_url = "https://example.com/foo"

    r = await client.get("/login", params={"rd": return_url})
    assert r.status_code == 307
    url = urlparse(r.headers["Location"])
    query = parse_qs(url.query)

    # Build an error response to return from the OIDC token URL and register
    # it as a result.
    response = {
        "error": "error_code",
        "error_description": "description",
    }
    respx_mock.post(config.oidc.token_url).respond(400, json=response)

    # Simulate the return from the OpenID Connect provider.
    caplog.clear()
    r = await client.get(
        "/oauth2/callback",
        params={
            "code": "some-code",
            "state": query["state"][0]
        },
    )
    assert r.status_code == 403
    assert "error_code: description" in r.text
    assert parse_log(caplog) == [
        {
            "event": f"Retrieving ID token from {config.oidc.token_url}",
            "httpRequest": {
                "requestMethod": "GET",
                "requestUrl": ANY,
                "remoteIp": "127.0.0.1",
            },
            "return_url": return_url,
            "severity": "info",
        },
        {
            "error": "error_code: description",
            "event": "Authentication provider failed",
            "httpRequest": {
                "requestMethod": "GET",
                "requestUrl": ANY,
                "remoteIp": "127.0.0.1",
            },
            "return_url": return_url,
            "severity": "warning",
        },
    ]

    # Change the mock error response to not contain an error.  We should then
    # internally raise the exception for the return status, which should
    # translate into an internal server error.
    respx_mock.post(config.oidc.token_url).respond(400, json={"foo": "bar"})
    r = await client.get("/login", params={"rd": return_url})
    query = parse_qs(urlparse(r.headers["Location"]).query)
    r = await client.get(
        "/oauth2/callback",
        params={
            "code": "some-code",
            "state": query["state"][0]
        },
    )
    assert r.status_code == 403
    assert "Cannot contact authentication provider" in r.text

    # Now try a reply that returns 200 but doesn't have the field we
    # need.
    respx_mock.post(config.oidc.token_url).respond(json={"foo": "bar"})
    r = await client.get("/login", params={"rd": return_url})
    query = parse_qs(urlparse(r.headers["Location"]).query)
    r = await client.get(
        "/oauth2/callback",
        params={
            "code": "some-code",
            "state": query["state"][0]
        },
    )
    assert r.status_code == 403
    assert "No id_token in token reply" in r.text

    # Return invalid JSON, which should raise an error during JSON decoding.
    respx_mock.post(config.oidc.token_url).respond(content=b"foo")
    r = await client.get("/login", params={"rd": return_url})
    query = parse_qs(urlparse(r.headers["Location"]).query)
    r = await client.get(
        "/oauth2/callback",
        params={
            "code": "some-code",
            "state": query["state"][0]
        },
    )
    assert r.status_code == 403
    assert "not valid JSON" in r.text

    # Finally, return invalid JSON and an error reply.
    respx_mock.post(config.oidc.token_url).respond(400, content=b"foo")
    r = await client.get("/login", params={"rd": return_url})
    query = parse_qs(urlparse(r.headers["Location"]).query)
    r = await client.get(
        "/oauth2/callback",
        params={
            "code": "some-code",
            "state": query["state"][0]
        },
    )
    assert r.status_code == 403
    assert "Cannot contact authentication provider" in r.text
Esempio n. 8
0
def test_multiple_pattern_values_type_error():
    router = Router()
    with pytest.raises(TypeError, match="Got multiple values for pattern 'method'"):
        router.post(method__in=("PUT", "PATCH"))
    with pytest.raises(TypeError, match="Got multiple values for pattern 'url'"):
        router.get("https://foo.bar", url__regex=r"https://example.org$")
Esempio n. 9
0
def test_rollback():
    router = Router()
    route = router.get("https://foo.bar/") % 404
    pattern = route.pattern
    assert route.name is None

    router.snapshot()  # 1. get 404

    route.return_value = httpx.Response(200)
    router.post("https://foo.bar/").mock(
        side_effect=[httpx.Response(400), httpx.Response(201)]
    )

    router.snapshot()  # 2. get 200, post

    _route = router.get("https://foo.bar/", name="foobar")
    _route = router.get("https://foo.bar/baz/", name="foobar")
    assert _route is route
    assert route.name == "foobar"
    assert route.pattern != pattern
    route.return_value = httpx.Response(418)
    request = httpx.Request("GET", "https://foo.bar/baz/")
    response = router.handler(request)
    assert response.status_code == 418

    request = httpx.Request("POST", "https://foo.bar")
    response = router.handler(request)
    assert response.status_code == 400

    assert len(router.routes) == 2
    assert router.calls.call_count == 2
    assert route.call_count == 1
    assert route.return_value.status_code == 418

    router.snapshot()  # 3. directly rollback, should be identical
    router.rollback()
    assert len(router.routes) == 2
    assert router.calls.call_count == 2
    assert route.call_count == 1
    assert route.return_value.status_code == 418

    router.patch("https://foo.bar/")
    assert len(router.routes) == 3

    route.rollback()  # get 200

    assert router.calls.call_count == 2
    assert route.call_count == 0
    assert route.return_value.status_code == 200

    request = httpx.Request("GET", "https://foo.bar")
    response = router.handler(request)
    assert response.status_code == 200

    router.rollback()  # 2. get 404, post

    request = httpx.Request("POST", "https://foo.bar")
    response = router.handler(request)
    assert response.status_code == 400
    assert len(router.routes) == 2

    router.rollback()  # 1. get 404

    assert len(router.routes) == 1
    assert router.calls.call_count == 0
    assert route.return_value is None

    router.rollback()  # Empty inital state

    assert len(router.routes) == 0
    assert route.return_value is None

    # Idempotent
    route.rollback()
    router.rollback()
    assert len(router.routes) == 0
    assert route.name is None
    assert route.pattern == pattern
    assert route.return_value is None