Пример #1
0
async def test_redeem_code(tmp_path: Path, factory: ComponentFactory) -> None:
    clients = [
        OIDCClient(client_id="client-1", client_secret="client-1-secret"),
        OIDCClient(client_id="client-2", client_secret="client-2-secret"),
    ]
    config = await configure(tmp_path, "github", oidc_clients=clients)
    factory.reconfigure(config)
    oidc_service = factory.create_oidc_service()
    token_data = await create_session_token(factory)
    token = token_data.token
    redirect_uri = "https://example.com/"
    code = await oidc_service.issue_code("client-2", redirect_uri, token)

    oidc_token = await oidc_service.redeem_code("client-2", "client-2-secret",
                                                redirect_uri, code)
    assert oidc_token.claims == {
        "aud": config.issuer.aud,
        "iat": ANY,
        "exp": ANY,
        "iss": config.issuer.iss,
        "jti": code.key,
        "name": token_data.name,
        "preferred_username": token_data.username,
        "scope": "openid",
        "sub": token_data.username,
        config.issuer.username_claim: token_data.username,
        config.issuer.uid_claim: token_data.uid,
    }

    redis = await redis_dependency()
    assert not await redis.get(f"oidc:{code.key}")
Пример #2
0
async def test_redeem_code_errors(tmp_path: Path,
                                  factory: ComponentFactory) -> None:
    clients = [
        OIDCClient(client_id="client-1", client_secret="client-1-secret"),
        OIDCClient(client_id="client-2", client_secret="client-2-secret"),
    ]
    config = await configure(tmp_path, "github", oidc_clients=clients)
    factory.reconfigure(config)
    oidc_service = factory.create_oidc_service()
    token_data = await create_session_token(factory)
    token = token_data.token
    redirect_uri = "https://example.com/"
    code = await oidc_service.issue_code("client-2", redirect_uri, token)

    with pytest.raises(InvalidClientError):
        await oidc_service.redeem_code("some-client", "some-secret",
                                       redirect_uri, code)
    with pytest.raises(InvalidClientError):
        await oidc_service.redeem_code("client-2", "some-secret", redirect_uri,
                                       code)
    with pytest.raises(InvalidGrantError):
        await oidc_service.redeem_code(
            "client-2",
            "client-2-secret",
            redirect_uri,
            OIDCAuthorizationCode(),
        )
    with pytest.raises(InvalidGrantError):
        await oidc_service.redeem_code("client-1", "client-1-secret",
                                       redirect_uri, code)
    with pytest.raises(InvalidGrantError):
        await oidc_service.redeem_code("client-2", "client-2-secret",
                                       "https://foo.example.com/", code)
Пример #3
0
async def test_issue_code(tmp_path: Path, factory: ComponentFactory) -> None:
    clients = [OIDCClient(client_id="some-id", client_secret="some-secret")]
    config = await configure(tmp_path, "github", oidc_clients=clients)
    factory.reconfigure(config)
    oidc_service = factory.create_oidc_service()
    token_data = await create_session_token(factory)
    token = token_data.token
    redirect_uri = "https://example.com/"

    assert config.oidc_server
    assert list(config.oidc_server.clients) == clients

    with pytest.raises(UnauthorizedClientException):
        await oidc_service.issue_code("unknown-client", redirect_uri, token)

    code = await oidc_service.issue_code("some-id", redirect_uri, token)
    redis = await redis_dependency()
    encrypted_code = await redis.get(f"oidc:{code.key}")
    assert encrypted_code
    fernet = Fernet(config.session_secret.encode())
    serialized_code = json.loads(fernet.decrypt(encrypted_code))
    assert serialized_code == {
        "code": {
            "key": code.key,
            "secret": code.secret,
        },
        "client_id": "some-id",
        "redirect_uri": redirect_uri,
        "token": {
            "key": token.key,
            "secret": token.secret,
        },
        "created_at": ANY,
    }
    now = time.time()
    assert now - 2 < serialized_code["created_at"] < now
Пример #4
0
async def test_token_errors(
    tmp_path: Path,
    client: AsyncClient,
    factory: ComponentFactory,
    caplog: LogCaptureFixture,
) -> None:
    clients = [
        OIDCClient(client_id="some-id", client_secret="some-secret"),
        OIDCClient(client_id="other-id", client_secret="other-secret"),
    ]
    config = await configure(tmp_path, "github", oidc_clients=clients)
    factory.reconfigure(config)
    token_data = await create_session_token(factory)
    token = token_data.token
    oidc_service = factory.create_oidc_service()
    redirect_uri = f"https://{TEST_HOSTNAME}/app"
    code = await oidc_service.issue_code("some-id", redirect_uri, token)

    # Missing parameters.
    request: Dict[str, str] = {}
    caplog.clear()
    r = await client.post("/auth/openid/token", data=request)
    assert r.status_code == 400
    assert r.json() == {
        "error": "invalid_request",
        "error_description": "Invalid token request",
    }

    assert parse_log(caplog) == [{
        "error": "Invalid token request",
        "event": "Invalid request",
        "httpRequest": {
            "requestMethod": "POST",
            "requestUrl": f"https://{TEST_HOSTNAME}/auth/openid/token",
            "remoteIp": "127.0.0.1",
        },
        "severity": "warning",
    }]

    # Invalid grant type.
    request = {
        "grant_type": "bogus",
        "client_id": "other-client",
        "code": "nonsense",
        "redirect_uri": f"https://{TEST_HOSTNAME}/",
    }
    caplog.clear()
    r = await client.post("/auth/openid/token", data=request)
    assert r.status_code == 400
    assert r.json() == {
        "error": "unsupported_grant_type",
        "error_description": "Invalid grant type bogus",
    }

    assert parse_log(caplog) == [{
        "error": "Invalid grant type bogus",
        "event": "Unsupported grant type",
        "httpRequest": {
            "requestMethod": "POST",
            "requestUrl": f"https://{TEST_HOSTNAME}/auth/openid/token",
            "remoteIp": "127.0.0.1",
        },
        "severity": "warning",
    }]

    # Invalid code.
    request["grant_type"] = "authorization_code"
    r = await client.post("/auth/openid/token", data=request)
    assert r.status_code == 400
    assert r.json() == {
        "error": "invalid_grant",
        "error_description": "Invalid authorization code",
    }

    # No client_secret.
    request["code"] = str(OIDCAuthorizationCode())
    caplog.clear()
    r = await client.post("/auth/openid/token", data=request)
    assert r.status_code == 400
    assert r.json() == {
        "error": "invalid_client",
        "error_description": "No client_secret provided",
    }

    assert parse_log(caplog) == [{
        "error": "No client_secret provided",
        "event": "Unauthorized client",
        "httpRequest": {
            "requestMethod": "POST",
            "requestUrl": f"https://{TEST_HOSTNAME}/auth/openid/token",
            "remoteIp": "127.0.0.1",
        },
        "severity": "warning",
    }]

    # Incorrect client_id.
    request["client_secret"] = "other-secret"
    r = await client.post("/auth/openid/token", data=request)
    assert r.status_code == 400
    assert r.json() == {
        "error": "invalid_client",
        "error_description": "Unknown client ID other-client",
    }

    # Incorrect client_secret.
    request["client_id"] = "some-id"
    r = await client.post("/auth/openid/token", data=request)
    assert r.status_code == 400
    assert r.json() == {
        "error": "invalid_client",
        "error_description": "Invalid secret for some-id",
    }

    # No stored data.
    request["client_secret"] = "some-secret"
    bogus_code = OIDCAuthorizationCode()
    request["code"] = str(bogus_code)
    caplog.clear()
    r = await client.post("/auth/openid/token", data=request)
    assert r.status_code == 400
    assert r.json() == {
        "error": "invalid_grant",
        "error_description": "Invalid authorization code",
    }
    log = json.loads(caplog.record_tuples[0][2])
    assert log["event"] == "Invalid authorization code"
    assert log["error"] == f"Unknown authorization code {bogus_code.key}"

    # Corrupt stored data.
    redis = await redis_dependency()
    await redis.set(bogus_code.key, "XXXXXXX")
    r = await client.post("/auth/openid/token", data=request)
    assert r.status_code == 400
    assert r.json() == {
        "error": "invalid_grant",
        "error_description": "Invalid authorization code",
    }

    # Correct code, but invalid client_id for that code.
    bogus_code = await oidc_service.issue_code("other-id", redirect_uri, token)
    request["code"] = str(bogus_code)
    r = await client.post("/auth/openid/token", data=request)
    assert r.status_code == 400
    assert r.json() == {
        "error": "invalid_grant",
        "error_description": "Invalid authorization code",
    }

    # Correct code and client_id but invalid redirect_uri.
    request["code"] = str(code)
    r = await client.post("/auth/openid/token", data=request)
    assert r.status_code == 400
    assert r.json() == {
        "error": "invalid_grant",
        "error_description": "Invalid authorization code",
    }

    # Delete the underlying token.
    token_service = factory.create_token_service()
    async with factory.session.begin():
        await token_service.delete_token(token.key,
                                         token_data,
                                         token_data.username,
                                         ip_address="127.0.0.1")
    request["redirect_uri"] = redirect_uri
    r = await client.post("/auth/openid/token", data=request)
    assert r.status_code == 400
    assert r.json() == {
        "error": "invalid_grant",
        "error_description": "Invalid authorization code",
    }