@@ -57,6 +57,10 @@ public class EsqlSecurityIT extends ESRestTestCase {
5757 .user ("user4" , "x-pack-test-password" , "user4" , false )
5858 .user ("user5" , "x-pack-test-password" , "user5" , false )
5959 .user ("fls_user" , "x-pack-test-password" , "fls_user" , false )
60+ .user ("fls_user2" , "x-pack-test-password" , "fls_user2" , false )
61+ .user ("fls_user3" , "x-pack-test-password" , "fls_user3" , false )
62+ .user ("fls_user4_1" , "x-pack-test-password" , "fls_user4_1" , false )
63+ .user ("dls_user" , "x-pack-test-password" , "dls_user" , false )
6064 .user ("metadata1_read2" , "x-pack-test-password" , "metadata1_read2" , false )
6165 .user ("alias_user1" , "x-pack-test-password" , "alias_user1" , false )
6266 .user ("alias_user2" , "x-pack-test-password" , "alias_user2" , false )
@@ -92,7 +96,7 @@ private void indexDocument(String index, int id, double value, String org) throw
9296 public void indexDocuments () throws IOException {
9397 Settings lookupSettings = Settings .builder ().put ("index.mode" , "lookup" ).build ();
9498 String mapping = """
95- "properties":{"value": {"type": "double"}, "org": {"type": "keyword"}}
99+ "properties":{"value": {"type": "double"}, "org": {"type": "keyword"}, "other": {"type": "keyword"} }
96100 """ ;
97101
98102 createIndex ("index" , Settings .EMPTY , mapping );
@@ -163,6 +167,32 @@ public void indexDocuments() throws IOException {
163167 """ );
164168 assertOK (client ().performRequest (aliasRequest ));
165169 }
170+
171+ createMultiRoleUsers ();
172+ }
173+
174+ private void createMultiRoleUsers () throws IOException {
175+ Request request = new Request ("POST" , "_security/user/dls_user2" );
176+ request .setJsonEntity ("""
177+ {
178+ "password" : "x-pack-test-password",
179+ "roles" : [ "dls_user", "dls_user2" ],
180+ "full_name" : "Test Role",
181+ 182+ }
183+ """ );
184+ assertOK (client ().performRequest (request ));
185+
186+ request = new Request ("POST" , "_security/user/fls_user4" );
187+ request .setJsonEntity ("""
188+ {
189+ "password" : "x-pack-test-password",
190+ "roles" : [ "fls_user4_1", "fls_user4_2" ],
191+ "full_name" : "Test Role",
192+ 193+ }
194+ """ );
195+ assertOK (client ().performRequest (request ));
166196 }
167197
168198 protected MapMatcher responseMatcher (Map <String , Object > result ) {
@@ -553,25 +583,130 @@ public void testLookupJoinIndexAllowed() throws Exception {
553583 );
554584 assertThat (respMap .get ("values" ), equalTo (List .of (List .of (40.0 , "sales" ))));
555585
556- // Alias, should find the index and the row
557- resp = runESQLCommand ("alias_user1" , "ROW x = 31.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org" );
586+ // Aliases are not allowed in LOOKUP JOIN
587+ var resp2 = expectThrows (
588+ ResponseException .class ,
589+ () -> runESQLCommand ("alias_user1" , "ROW x = 31.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org" )
590+ );
591+
592+ assertThat (resp2 .getMessage (), containsString ("Aliases and index patterns are not allowed for LOOKUP JOIN [lookup-first-alias]" ));
593+ assertThat (resp2 .getResponse ().getStatusLine ().getStatusCode (), equalTo (HttpStatus .SC_BAD_REQUEST ));
594+
595+ // Aliases are not allowed in LOOKUP JOIN, regardless of alias filters
596+ resp2 = expectThrows (
597+ ResponseException .class ,
598+ () -> runESQLCommand ("alias_user1" , "ROW x = 123.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org" )
599+ );
600+ assertThat (resp2 .getMessage (), containsString ("Aliases and index patterns are not allowed for LOOKUP JOIN [lookup-first-alias]" ));
601+ assertThat (resp2 .getResponse ().getStatusLine ().getStatusCode (), equalTo (HttpStatus .SC_BAD_REQUEST ));
602+ }
603+
604+ @ SuppressWarnings ("unchecked" )
605+ public void testLookupJoinDocLevelSecurity () throws Exception {
606+ assumeTrue (
607+ "Requires LOOKUP JOIN capability" ,
608+ EsqlSpecTestCase .hasCapabilities (adminClient (), List .of (EsqlCapabilities .Cap .JOIN_LOOKUP_V12 .capabilityName ()))
609+ );
610+
611+ Response resp = runESQLCommand ("dls_user" , "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value | KEEP x, org" );
612+ assertOK (resp );
613+ Map <String , Object > respMap = entityAsMap (resp );
614+ assertThat (
615+ respMap .get ("columns" ),
616+ equalTo (List .of (Map .of ("name" , "x" , "type" , "double" ), Map .of ("name" , "org" , "type" , "keyword" )))
617+ );
618+
619+ assertThat (respMap .get ("values" ), equalTo (List .of (Arrays .asList (40.0 , null ))));
620+
621+ resp = runESQLCommand ("dls_user" , "ROW x = 32.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value | KEEP x, org" );
622+ assertOK (resp );
623+ respMap = entityAsMap (resp );
624+ assertThat (
625+ respMap .get ("columns" ),
626+ equalTo (List .of (Map .of ("name" , "x" , "type" , "double" ), Map .of ("name" , "org" , "type" , "keyword" )))
627+ );
628+ assertThat (respMap .get ("values" ), equalTo (List .of (List .of (32.0 , "marketing" ))));
629+
630+ // same, but with a user that has two dls roles that allow him more visibility
631+
632+ resp = runESQLCommand ("dls_user2" , "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value | KEEP x, org" );
558633 assertOK (resp );
559634 respMap = entityAsMap (resp );
560635 assertThat (
561636 respMap .get ("columns" ),
562637 equalTo (List .of (Map .of ("name" , "x" , "type" , "double" ), Map .of ("name" , "org" , "type" , "keyword" )))
563638 );
564- assertThat (respMap .get ("values" ), equalTo (List .of (List .of (31.0 , "sales" ))));
565639
566- // Alias, for a row that's filtered out
567- resp = runESQLCommand ("alias_user1" , "ROW x = 123.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org" );
640+ assertThat (respMap .get ("values" ), equalTo (List .of (Arrays .asList (40.0 , "sales" ))));
641+
642+ resp = runESQLCommand ("dls_user2" , "ROW x = 32.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value | KEEP x, org" );
568643 assertOK (resp );
569644 respMap = entityAsMap (resp );
570645 assertThat (
571646 respMap .get ("columns" ),
572647 equalTo (List .of (Map .of ("name" , "x" , "type" , "double" ), Map .of ("name" , "org" , "type" , "keyword" )))
573648 );
574- assertThat (respMap .get ("values" ), equalTo (List .of (Arrays .asList (123.0 , null ))));
649+ assertThat (respMap .get ("values" ), equalTo (List .of (List .of (32.0 , "marketing" ))));
650+
651+ }
652+
653+ @ SuppressWarnings ("unchecked" )
654+ public void testLookupJoinFieldLevelSecurity () throws Exception {
655+ assumeTrue (
656+ "Requires LOOKUP JOIN capability" ,
657+ EsqlSpecTestCase .hasCapabilities (adminClient (), List .of (EsqlCapabilities .Cap .JOIN_LOOKUP_V12 .capabilityName ()))
658+ );
659+
660+ Response resp = runESQLCommand ("fls_user2" , "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value" );
661+ assertOK (resp );
662+ Map <String , Object > respMap = entityAsMap (resp );
663+ assertThat (
664+ respMap .get ("columns" ),
665+ equalTo (
666+ List .of (
667+ Map .of ("name" , "x" , "type" , "double" ),
668+ Map .of ("name" , "value" , "type" , "double" ),
669+ Map .of ("name" , "org" , "type" , "keyword" )
670+ )
671+ )
672+ );
673+
674+ resp = runESQLCommand ("fls_user3" , "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value" );
675+ assertOK (resp );
676+ respMap = entityAsMap (resp );
677+ assertThat (
678+ respMap .get ("columns" ),
679+ equalTo (
680+ List .of (
681+ Map .of ("name" , "x" , "type" , "double" ),
682+ Map .of ("name" , "value" , "type" , "double" ),
683+ Map .of ("name" , "org" , "type" , "keyword" ),
684+ Map .of ("name" , "other" , "type" , "keyword" )
685+ )
686+ )
687+
688+ );
689+
690+ resp = runESQLCommand ("fls_user4" , "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value" );
691+ assertOK (resp );
692+ respMap = entityAsMap (resp );
693+ assertThat (
694+ respMap .get ("columns" ),
695+ equalTo (
696+ List .of (
697+ Map .of ("name" , "x" , "type" , "double" ),
698+ Map .of ("name" , "value" , "type" , "double" ),
699+ Map .of ("name" , "org" , "type" , "keyword" )
700+ )
701+ )
702+ );
703+
704+ ResponseException error = expectThrows (
705+ ResponseException .class ,
706+ () -> runESQLCommand ("fls_user4_1" , "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value" )
707+ );
708+ assertThat (error .getMessage (), containsString ("Unknown column [value] in right side of join" ));
709+ assertThat (error .getResponse ().getStatusLine ().getStatusCode (), equalTo (HttpStatus .SC_BAD_REQUEST ));
575710 }
576711
577712 public void testLookupJoinIndexForbidden () throws Exception {
0 commit comments