diff --git a/src/msg/core/executor.py b/src/msg/core/executor.py index 6c417e68..ca7bf051 100644 --- a/src/msg/core/executor.py +++ b/src/msg/core/executor.py @@ -55,7 +55,7 @@ async def execute(self,request, *, entry='network'): capacity_writes={'identity.register','identity.temporary','identity.custodial_create','content.topic_create', 'content.post_create','content.post_edit','content.file_put','content.attach', 'content.template_put','discussion.reply','discussion.quote','discussion.repost', - 'transfer.part_put','git.create','git.push','git.receive','hosting.deploy','keystore.put'} + 'transfer.part_put','git.create','git.push','git.receive','hosting.deploy','hosting.preview','keystore.put'} if spec.name in capacity_writes: require(session.setting('runtime_config',{}).get('accept_writes',True),'writes_paused') for rid,generation in request.expected_generations: diff --git a/src/msg/extensions/hosting.py b/src/msg/extensions/hosting.py index 634d7f78..b0817909 100644 --- a/src/msg/extensions/hosting.py +++ b/src/msg/extensions/hosting.py @@ -64,7 +64,7 @@ async def deploy(ctx,request,tx): resource=await revise_resource(app,ctx,request,tx,resource,body,'application/json') return output_for(resource,files=len(entries),url=app.settings.service_url.rstrip('/')+await tx.path(resource.id)+'/') - @op('hosting.preview',obj({'id':IDENTIFIER,'entries':{'type':'array','minItems':1, + @op('hosting.preview',obj({'id':IDENTIFIER,'entries':{'type':'array','minItems':1,'maxItems':128, 'items':obj({'path':STRING,'source':REF},('path','source'))}},('id','entries')),signature=True) async def preview(ctx,request,tx): website=await tx.resource(await resolve(tx,request.arguments['id'])) diff --git a/tests/test_hosting_same_origin.py b/tests/test_hosting_same_origin.py index a90ab6a9..896f42d5 100644 --- a/tests/test_hosting_same_origin.py +++ b/tests/test_hosting_same_origin.py @@ -2,8 +2,11 @@ import httpx import pytest from datetime import timedelta +from types import SimpleNamespace +from msg.application import Application from msg.core.codec import b64, canonical, loads, wire +from msg.core.errors import Failure from msg.core.models import ResourceRef from msg.core.requests import request_for from msg.transports.http import create_app @@ -19,6 +22,18 @@ def isolated(response): assert response.headers['x-content-type-options']=='nosniff' +def test_preview_entry_count_is_bounded(): + settings=SimpleNamespace(server=SimpleNamespace( + plugins=('identity','content','discussion','communication','discovery','achievements', + 'recovery','sharing','transfer','extensions','system','batch'))) + registry=Application(settings).registry + entries=[{'path':f'{index}.html','source':{'id':'unused','revision':'unused'}} + for index in range(129)] + spec=registry.operation('hosting.preview',1) + with pytest.raises(Failure,match='schema_validation'): + registry.validate(spec.input_schema,{'id':'unused','entries':entries}) + + @pytest.mark.asyncio async def test_same_domain_hosted_html_head_304_range_raw_and_no_write_route(installed): app, _ = installed diff --git a/tests/test_operations_admin.py b/tests/test_operations_admin.py index 26ca74bd..97fe6171 100644 --- a/tests/test_operations_admin.py +++ b/tests/test_operations_admin.py @@ -4,7 +4,7 @@ from pathlib import Path import pytest -from msg.core.codec import wire +from msg.core.codec import b64,wire from msg.core.errors import Failure from msg.admin.diagnostics import doctor, selftest from msg.workers.maintenance import run_maintenance @@ -16,6 +16,9 @@ async def test_network_system_configuration_cannot_change_root_trust(installed): app,root=installed key,uid,base=await register(app,'operator') + site=await call(app,'hosting.create',{'parent':'/@operator','name':'paused-preview'},key=key,subject=uid) + source=await call(app,'content.file_put',{'parent':'/@operator/files','name':'preview.html', + 'data':b64(b'preview'),'media_type':'text/html'},key=key,subject=uid) denied=await call(app,'system.config',{'values':{'accept_writes':False}},key=key,subject=uid) assert denied.error.code=='capability_required',wire(denied) cert=await approve(app,root,uid,key,(scoped(app,'system.config','r_root',('system.config@1',)),)) @@ -27,6 +30,9 @@ async def test_network_system_configuration_cannot_change_root_trust(installed): assert bad.status=='error' blocked=await call(app,'content.post_create',{'parent':'/main','body':'capacity is paused'},key=key,subject=uid) assert blocked.error.code=='writes_paused' + preview=await call(app,'hosting.preview',{'id':site.resources[0].id, + 'entries':[{'path':'index.html','source':wire(source.resources[0])}]},key=key,subject=uid) + assert preview.error.code=='writes_paused' reading=await call(app,'discovery.get',{'id':'/main'}) assert reading.status=='ok'