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
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", ) ]
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)
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."
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_()
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")