6161import  org .elasticsearch .index .mapper .NumberFieldMapper ;
6262import  org .elasticsearch .index .mapper .ObjectMapper ;
6363import  org .elasticsearch .index .mapper .RootObjectMapper ;
64+ import  org .elasticsearch .index .mapper .RootObjectMapperNamespaceValidator ;
6465import  org .elasticsearch .index .mapper .RuntimeField ;
6566import  org .elasticsearch .index .mapper .SourceFieldMapper ;
6667import  org .elasticsearch .index .mapper .TestRuntimeField ;
@@ -331,6 +332,89 @@ public void testSearchRequestRuntimeFields() {
331332        assertThat (context .getMatchingFieldNames ("*" ), equalTo (Set .of ("cat" , "dog" , "pig" , "runtime" )));
332333    }
333334
335+     public  void  testSearchRequestRuntimeFieldsWithNamespaceValidator () {
336+         final  String  errorMessage  = "error 12345" ;
337+         final  String  disallowed  = "_project" ;
338+ 
339+         RootObjectMapperNamespaceValidator  validator  = new  RootObjectMapperNamespaceValidator () {
340+             @ Override 
341+             public  void  validateNamespace (ObjectMapper .Subobjects  subobjects , String  name ) {
342+                 if  (name .equals (disallowed )) {
343+                     throw  new  IllegalArgumentException (errorMessage );
344+                 } else  if  (subobjects  != ObjectMapper .Subobjects .ENABLED ) {
345+                     // name here will be something like _project.my_field, rather than just _project 
346+                     if  (name .startsWith (disallowed  + "." )) {
347+                         throw  new  IllegalArgumentException (errorMessage );
348+                     }
349+                 }
350+             }
351+         };
352+ 
353+         {
354+             Map <String , Object > runtimeMappings  = Map .ofEntries (
355+                 Map .entry (disallowed , Map .of ("type" , randomFrom ("keyword" , "long" ))),
356+                 Map .entry ("dog" , Map .of ("type" , "long" ))
357+             );
358+ 
359+             Exception  e  = expectThrows (
360+                 IllegalArgumentException .class ,
361+                 () -> createSearchExecutionContext (
362+                     "uuid" ,
363+                     null ,
364+                     createMappingLookup (
365+                         List .of (new  MockFieldMapper .FakeFieldType ("pig" ), new  MockFieldMapper .FakeFieldType ("cat" )),
366+                         List .of (new  TestRuntimeField ("runtime" , "long" ))
367+                     ),
368+                     runtimeMappings ,
369+                     validator 
370+                 )
371+             );
372+             assertThat (e .getMessage (), equalTo (errorMessage ));
373+         }
374+ 
375+         {
376+             Map <String , Object > runtimeMappings  = Map .ofEntries (
377+                 Map .entry (disallowed  + ".subfield" , Map .of ("type" , randomFrom ("keyword" , "long" ))),
378+                 Map .entry ("dog" , Map .of ("type" , "long" ))
379+             );
380+ 
381+             Exception  e  = expectThrows (
382+                 IllegalArgumentException .class ,
383+                 () -> createSearchExecutionContext (
384+                     "uuid" ,
385+                     null ,
386+                     createMappingLookup (
387+                         List .of (new  MockFieldMapper .FakeFieldType ("pig" ), new  MockFieldMapper .FakeFieldType ("cat" )),
388+                         List .of (new  TestRuntimeField ("runtime" , "long" ))
389+                     ),
390+                     runtimeMappings ,
391+                     validator 
392+                 )
393+             );
394+             assertThat (e .getMessage (), equalTo (errorMessage ));
395+         }
396+ 
397+         // _projectx should be allowed 
398+         {
399+             Map <String , Object > runtimeMappings  = Map .ofEntries (
400+                 Map .entry (disallowed  + "x" , Map .of ("type" , "keyword" )),
401+                 Map .entry ("dog" , Map .of ("type" , "long" ))
402+             );
403+ 
404+             SearchExecutionContext  searchExecutionContext  = createSearchExecutionContext (
405+                 "uuid" ,
406+                 null ,
407+                 createMappingLookup (
408+                     List .of (new  MockFieldMapper .FakeFieldType ("pig" ), new  MockFieldMapper .FakeFieldType ("cat" )),
409+                     List .of (new  TestRuntimeField ("runtime" , "long" ))
410+                 ),
411+                 runtimeMappings ,
412+                 validator 
413+             );
414+             assertNotNull (searchExecutionContext );
415+         }
416+     }
417+ 
334418    public  void  testSearchRequestRuntimeFieldsWrongFormat () {
335419        Map <String , Object > runtimeMappings  = new  HashMap <>();
336420        runtimeMappings .put ("field" , Arrays .asList ("test1" , "test2" ));
@@ -500,12 +584,22 @@ private static SearchExecutionContext createSearchExecutionContext(
500584        String  clusterAlias ,
501585        MappingLookup  mappingLookup ,
502586        Map <String , Object > runtimeMappings 
587+     ) {
588+         return  createSearchExecutionContext (indexUuid , clusterAlias , mappingLookup , runtimeMappings , null );
589+     }
590+ 
591+     private  static  SearchExecutionContext  createSearchExecutionContext (
592+         String  indexUuid ,
593+         String  clusterAlias ,
594+         MappingLookup  mappingLookup ,
595+         Map <String , Object > runtimeMappings ,
596+         RootObjectMapperNamespaceValidator  namespaceValidator 
503597    ) {
504598        IndexMetadata .Builder  indexMetadataBuilder  = new  IndexMetadata .Builder ("index" );
505599        indexMetadataBuilder .settings (indexSettings (IndexVersion .current (), 1 , 1 ).put (IndexMetadata .SETTING_INDEX_UUID , indexUuid ));
506600        IndexMetadata  indexMetadata  = indexMetadataBuilder .build ();
507601        IndexSettings  indexSettings  = new  IndexSettings (indexMetadata , Settings .EMPTY );
508-         MapperService  mapperService  = createMapperService (indexSettings , mappingLookup );
602+         MapperService  mapperService  = createMapperServiceWithNamespaceValidator (indexSettings , mappingLookup ,  namespaceValidator );
509603        final  long  nowInMillis  = randomNonNegativeLong ();
510604        return  new  SearchExecutionContext (
511605            0 ,
@@ -532,8 +626,16 @@ private static SearchExecutionContext createSearchExecutionContext(
532626    }
533627
534628    private  static  MapperService  createMapperService (IndexSettings  indexSettings , MappingLookup  mappingLookup ) {
629+         return  createMapperServiceWithNamespaceValidator (indexSettings , mappingLookup , null );
630+     }
631+ 
632+     private  static  MapperService  createMapperServiceWithNamespaceValidator (
633+         IndexSettings  indexSettings ,
634+         MappingLookup  mappingLookup ,
635+         RootObjectMapperNamespaceValidator  namespaceValidator 
636+     ) {
535637        IndexAnalyzers  indexAnalyzers  = IndexAnalyzers .of (singletonMap ("default" , new  NamedAnalyzer ("default" , AnalyzerScope .INDEX , null )));
536-         IndicesModule  indicesModule  = new  IndicesModule (Collections .emptyList ());
638+         IndicesModule  indicesModule  = new  IndicesModule (Collections .emptyList (),  namespaceValidator );
537639        MapperRegistry  mapperRegistry  = indicesModule .getMapperRegistry ();
538640        Supplier <SearchExecutionContext > searchExecutionContextSupplier  = () -> { throw  new  UnsupportedOperationException (); };
539641        MapperService  mapperService  = mock (MapperService .class );
@@ -552,7 +654,8 @@ private static MapperService createMapperService(IndexSettings indexSettings, Ma
552654                indexSettings .getMode ().buildIdFieldMapper (() -> true ),
553655                query  -> {
554656                    throw  new  UnsupportedOperationException ();
555-                 }
657+                 },
658+                 namespaceValidator 
556659            )
557660        );
558661        when (mapperService .isMultiField (anyString ())).then (
0 commit comments