def permits(self, context, principals, permission):
        # The Pyramid API doesn't let us access the request here, so we have to pull it
        # out of the thread local instead.
        # TODO: Work with Pyramid devs to figure out if there is a better way to support
        #       the worklow we are using here or not.
        request = get_current_request()

        # Our request could possibly be a None, if there isn't an active request, in
        # that case we're going to always deny, because without a request, we can't
        # determine if this request is authorized or not.
        if request is None:
            return WarehouseDenied("There was no active request.",
                                   reason="no_active_request")

        # Check if the subpolicy permits authorization
        subpolicy_permits = self.policy.permits(context, principals,
                                                permission)

        # If the request is permitted by the subpolicy, check if the context is
        # 2FA requireable, if 2FA is indeed required, and if the user has 2FA
        # enabled
        if subpolicy_permits and isinstance(context, TwoFactorRequireable):
            if (request.registry.
                    settings["warehouse.two_factor_requirement.enabled"]
                    and context.owners_require_2fa
                    and not request.user.has_two_factor):
                return WarehouseDenied(
                    "This project requires two factor authentication to be enabled "
                    "for all contributors.",
                    reason="owners_require_2fa",
                )
            if (request.registry.
                    settings["warehouse.two_factor_mandate.enabled"]
                    and context.pypi_mandates_2fa
                    and not request.user.has_two_factor):
                return WarehouseDenied(
                    "PyPI requires two factor authentication to be enabled "
                    "for all contributors to this project.",
                    reason="pypi_mandates_2fa",
                )
            if (request.registry.
                    settings["warehouse.two_factor_mandate.available"]
                    and context.pypi_mandates_2fa
                    and not request.user.has_two_factor):
                request.session.flash(
                    "This project is included in PyPI's two-factor mandate "
                    "for critical projects. In the future, you will be unable to "
                    "perform this action without enabling 2FA for your account",
                    queue="warning",
                )

        return subpolicy_permits
Exemple #2
0
    def test_two_factor_required(self, reason):
        result = WarehouseDenied("Some summary", reason=reason)
        exc = pretend.stub(result=result)
        request = pretend.stub(
            authenticated_userid=1,
            session=pretend.stub(
                flash=pretend.call_recorder(lambda x, queue: None)),
            path_qs="/foo/bar/?b=s",
            route_url=pretend.call_recorder(
                lambda route, _query: "/the/url/?next=/foo/bar/%3Fb%3Ds"),
            _=lambda x: x,
        )

        resp = forbidden(exc, request)

        assert resp.status_code == 303
        assert resp.headers["Location"] == "/the/url/?next=/foo/bar/%3Fb%3Ds"
        assert request.route_url.calls == [
            pretend.call("manage.account.two-factor",
                         _query={"next": "/foo/bar/?b=s"})
        ]
        assert request.session.flash.calls == [
            pretend.call(
                "Two-factor authentication must be enabled on your account to "
                "perform this action.",
                queue="error",
            )
        ]
Exemple #3
0
    def permits(self, context, principals, permission):
        # The Pyramid API doesn't let us access the request here, so we have to pull it
        # out of the thread local instead.
        # TODO: Work with Pyramid devs to figure out if there is a better way to support
        #       the worklow we are using here or not.
        request = get_current_request()

        # Our request could possibly be a None, if there isn't an active request, in
        # that case we're going to always deny, because without a request, we can't
        # determine if this request is authorized or not.
        if request is None:
            return WarehouseDenied("There was no active request.",
                                   reason="no_active_request")

        # Re-extract our Macaroon from the request, it sucks to have to do this work
        # twice, but I believe it is inevitable unless we pass the Macaroon back as
        # a principal-- which doesn't seem to be the right fit for it.
        macaroon = _extract_http_macaroon(request)

        # This logic will only happen on requests that are being authenticated with
        # Macaroons. Any other request will just fall back to the standard Authorization
        # policy.
        if macaroon is not None:
            valid_permissions = ["upload"]
            macaroon_service = request.find_service(IMacaroonService,
                                                    context=None)

            try:
                macaroon_service.verify(macaroon, context, principals,
                                        permission)
            except InvalidMacaroon as exc:
                return WarehouseDenied(f"Invalid API Token: {exc!r}",
                                       reason="invalid_api_token")

            # If our Macaroon is verified, and for a valid permission then we'll pass
            # this request to our underlying Authorization policy, so it can handle its
            # own authorization logic on the principal.
            if permission in valid_permissions:
                return self.policy.permits(context, principals, permission)
            else:
                return WarehouseDenied(
                    f"API tokens are not valid for permission: {permission}!",
                    reason="invalid_permission",
                )

        else:
            return self.policy.permits(context, principals, permission)
Exemple #4
0
    def test_permits_no_active_request(self, monkeypatch):
        get_current_request = pretend.call_recorder(lambda: None)
        monkeypatch.setattr(security_policy, "get_current_request",
                            get_current_request)

        backing_policy = pretend.stub(
            permits=pretend.call_recorder(lambda *a, **kw: pretend.stub()))
        policy = security_policy.TwoFactorAuthorizationPolicy(
            policy=backing_policy)
        result = policy.permits(pretend.stub(), pretend.stub(), pretend.stub())

        assert result == WarehouseDenied("")
        assert result.s == "There was no active request."
Exemple #5
0
    def test_generic_warehousedeined(self, pyramid_config):
        result = WarehouseDenied(
            "This project requires two factor authentication to be enabled "
            "for all contributors.",
            reason="some_other_reason",
        )
        exc = pretend.stub(result=result)

        renderer = pyramid_config.testing_add_renderer("403.html")

        exc = pretend.stub(status_code=403,
                           status="403 Forbidden",
                           headers={},
                           result=result)
        request = pretend.stub(authenticated_userid=1)
        resp = forbidden(exc, request)
        assert resp.status_code == 403
        renderer.assert_()
Exemple #6
0
    def test_denies_if_2fa_is_required_but_user_doesnt_have_2fa(
        self,
        monkeypatch,
        owners_require_2fa,
        pypi_mandates_2fa,
        reason,
        db_request,
    ):
        db_request.registry.settings = {
            "warehouse.two_factor_requirement.enabled": owners_require_2fa,
            "warehouse.two_factor_mandate.enabled": pypi_mandates_2fa,
        }
        user = pretend.stub(has_two_factor=False)
        db_request.user = user
        get_current_request = pretend.call_recorder(lambda: db_request)
        monkeypatch.setattr(security_policy, "get_current_request",
                            get_current_request)

        permits_result = Allowed("Because")
        backing_policy = pretend.stub(
            permits=pretend.call_recorder(lambda *a, **kw: permits_result))
        policy = security_policy.TwoFactorAuthorizationPolicy(
            policy=backing_policy)
        context = ProjectFactory.create(owners_require_2fa=owners_require_2fa,
                                        pypi_mandates_2fa=pypi_mandates_2fa)
        result = policy.permits(context, pretend.stub(), pretend.stub())

        summary = {
            "owners_require_2fa":
            ("This project requires two factor authentication to be enabled "
             "for all contributors.", ),
            "pypi_mandates_2fa":
            ("PyPI requires two factor authentication to be enabled "
             "for all contributors to this project.", ),
        }[reason]

        assert result == WarehouseDenied(summary, reason="two_factor_required")