6464import org .junit .Before ;
6565import org .mockito .Mockito ;
6666
67- import java .nio .charset .StandardCharsets ;
6867import java .time .Clock ;
69- import java .util .Base64 ;
7068import java .util .List ;
7169import java .util .Map ;
7270import java .util .Set ;
7371import java .util .concurrent .ExecutionException ;
7472import java .util .concurrent .atomic .AtomicReference ;
7573import java .util .function .Consumer ;
7674
75+ import static org .elasticsearch .test .ActionListenerUtils .anyActionListener ;
76+ import static org .elasticsearch .test .rest .ESRestTestCase .basicAuthHeaderValue ;
7777import static org .elasticsearch .xpack .security .authc .support .SecondaryAuthenticator .SECONDARY_AUTH_HEADER_NAME ;
78+ import static org .elasticsearch .xpack .security .authc .support .SecondaryAuthenticator .SECONDARY_X_CLIENT_AUTH_HEADER_NAME ;
7879import static org .hamcrest .Matchers .equalTo ;
80+ import static org .hamcrest .Matchers .notNullValue ;
7981import static org .hamcrest .Matchers .nullValue ;
8082import static org .mockito .ArgumentMatchers .any ;
8183import static org .mockito .Mockito .doAnswer ;
@@ -259,11 +261,7 @@ private SecondaryAuthentication assertAuthenticateWithBasicAuthentication(Consum
259261 final SecureString password = new SecureString (randomAlphaOfLengthBetween (8 , 24 ).toCharArray ());
260262 realm .defineUser (user , password );
261263
262- threadPool .getThreadContext ()
263- .putHeader (
264- SECONDARY_AUTH_HEADER_NAME ,
265- "Basic " + Base64 .getEncoder ().encodeToString ((user + ":" + password ).getBytes (StandardCharsets .UTF_8 ))
266- );
264+ threadPool .getThreadContext ().putHeader (SECONDARY_AUTH_HEADER_NAME , basicAuthHeaderValue (user , password ));
267265
268266 final PlainActionFuture <SecondaryAuthentication > future = new PlainActionFuture <>();
269267 final AtomicReference <ThreadContext .StoredContext > listenerContext = new AtomicReference <>();
@@ -273,8 +271,8 @@ private SecondaryAuthentication assertAuthenticateWithBasicAuthentication(Consum
273271 }, e -> future .onFailure (e )));
274272
275273 final SecondaryAuthentication secondaryAuthentication = future .result ();
276- assertThat (secondaryAuthentication , Matchers . notNullValue ());
277- assertThat (secondaryAuthentication .getAuthentication (), Matchers . notNullValue ());
274+ assertThat (secondaryAuthentication , notNullValue ());
275+ assertThat (secondaryAuthentication .getAuthentication (), notNullValue ());
278276 assertThat (secondaryAuthentication .getAuthentication ().getEffectiveSubject ().getUser ().principal (), equalTo (user ));
279277 assertThat (secondaryAuthentication .getAuthentication ().getAuthenticatingSubject ().getRealm ().getName (), equalTo (realm .name ()));
280278
@@ -303,10 +301,7 @@ private void assertAuthenticateWithIncorrectPassword(Consumer<ActionListener<Sec
303301 realm .defineUser (user , password );
304302
305303 threadPool .getThreadContext ()
306- .putHeader (
307- SECONDARY_AUTH_HEADER_NAME ,
308- "Basic " + Base64 .getEncoder ().encodeToString ((user + ":NOT-" + password ).getBytes (StandardCharsets .UTF_8 ))
309- );
304+ .putHeader (SECONDARY_AUTH_HEADER_NAME , basicAuthHeaderValue (user , new SecureString ("NOT-" + password )));
310305
311306 final PlainActionFuture <SecondaryAuthentication > future = new PlainActionFuture <>();
312307 final AtomicReference <ThreadContext .StoredContext > listenerContext = new AtomicReference <>();
@@ -354,10 +349,72 @@ public void testAuthenticateUsingBearerToken() throws Exception {
354349 authenticator .authenticate (AuthenticateAction .NAME , request , future );
355350
356351 final SecondaryAuthentication secondaryAuthentication = future .result ();
357- assertThat (secondaryAuthentication , Matchers . notNullValue ());
358- assertThat (secondaryAuthentication .getAuthentication (), Matchers . notNullValue ());
352+ assertThat (secondaryAuthentication , notNullValue ());
353+ assertThat (secondaryAuthentication .getAuthentication (), notNullValue ());
359354 assertThat (secondaryAuthentication .getAuthentication ().getEffectiveSubject ().getUser (), equalTo (user ));
360355 assertThat (secondaryAuthentication .getAuthentication ().getAuthenticationType (), equalTo (AuthenticationType .TOKEN ));
361356 }
362357
358+ public void testSecondaryXClientAuthHeaderIsPlacedInThreadContext () throws Exception {
359+ final String xClientAuthValue = randomAlphaOfLengthBetween (20 , 40 );
360+ final String capturedHeader = authenticateAndCaptureXClientAuthHeader (xClientAuthValue );
361+ assertThat (capturedHeader , equalTo (xClientAuthValue ));
362+ }
363+
364+ public void testSecondaryXClientAuthHeaderIsNotPlacedWhenNotProvided () throws Exception {
365+ final String capturedHeader = authenticateAndCaptureXClientAuthHeader (randomBoolean () ? null : "" );
366+ assertThat (capturedHeader , nullValue ());
367+ }
368+
369+ private String authenticateAndCaptureXClientAuthHeader (String xClientAuthValue ) throws Exception {
370+ final AtomicReference <String > capturedHeader = new AtomicReference <>();
371+ final Authentication authentication = AuthenticationTestHelper .builder ()
372+ .user (new User (randomAlphaOfLengthBetween (6 , 12 )))
373+ .realmRef (new RealmRef ("test_realm" , "dummy" , "node1" ))
374+ .build (false );
375+
376+ final AuthenticationService mockAuthService = mock (AuthenticationService .class );
377+ boolean useTransportRequest = randomBoolean ();
378+ if (useTransportRequest ) {
379+ doAnswer (invocation -> {
380+ capturedHeader .set (threadPool .getThreadContext ().getHeader ("X-Client-Authentication" ));
381+ @ SuppressWarnings ("unchecked" )
382+ ActionListener <Authentication > listener = (ActionListener <Authentication >) invocation .getArguments ()[3 ];
383+ listener .onResponse (authentication );
384+ return null ;
385+ }).when (mockAuthService ).authenticate (any (String .class ), any (TransportRequest .class ), any (Boolean .class ), anyActionListener ());
386+ } else {
387+ doAnswer (invocation -> {
388+ capturedHeader .set (threadPool .getThreadContext ().getHeader ("X-Client-Authentication" ));
389+ @ SuppressWarnings ("unchecked" )
390+ ActionListener <Authentication > listener = (ActionListener <Authentication >) invocation .getArguments ()[2 ];
391+ listener .onResponse (authentication );
392+ return null ;
393+ }).when (mockAuthService ).authenticate (any (), any (Boolean .class ), anyActionListener ());
394+ }
395+
396+ final SecondaryAuthenticator mockAuthenticator = new SecondaryAuthenticator (
397+ securityContext ,
398+ mockAuthService ,
399+ new AuditTrailService (null , null )
400+ );
401+
402+ threadPool .getThreadContext ()
403+ .putHeader (SECONDARY_AUTH_HEADER_NAME , basicAuthHeaderValue (randomAlphanumericOfLength (5 ), randomSecureStringOfLength (5 )));
404+
405+ if (xClientAuthValue != null ) {
406+ threadPool .getThreadContext ().putHeader (SECONDARY_X_CLIENT_AUTH_HEADER_NAME , xClientAuthValue );
407+ }
408+
409+ final PlainActionFuture <SecondaryAuthentication > future = new PlainActionFuture <>();
410+ if (useTransportRequest ) {
411+ mockAuthenticator .authenticate (AuthenticateAction .NAME , AuthenticateRequest .INSTANCE , future );
412+ } else {
413+ mockAuthenticator .authenticateAndAttachToContext (new FakeRestRequest (), future );
414+ }
415+
416+ assertThat (future .result (), notNullValue ());
417+ return capturedHeader .get ();
418+ }
419+
363420}
0 commit comments