@@ -118,11 +118,27 @@ public AuthenticationService(Settings settings, Realms realms, AuditTrailService
118118 * Authenticates the user that is associated with the given request. If the user was authenticated successfully (i.e.
119119 * a user was indeed associated with the request and the credentials were verified to be valid), the method returns
120120 * the user and that user is then "attached" to the request's context.
121+ * This method will authenticate as the anonymous user if the service is configured to allow anonymous access.
121122 *
122123 * @param request The request to be authenticated
123124 */
124125 public void authenticate (RestRequest request , ActionListener <Authentication > authenticationListener ) {
125- createAuthenticator (request , authenticationListener ).authenticateAsync ();
126+ authenticate (request , true , authenticationListener );
127+ }
128+
129+ /**
130+ * Authenticates the user that is associated with the given request. If the user was authenticated successfully (i.e.
131+ * a user was indeed associated with the request and the credentials were verified to be valid), the method returns
132+ * the user and that user is then "attached" to the request's context.
133+ * This method will optionally, authenticate as the anonymous user if the service is configured to allow anonymous access.
134+ *
135+ * @param request The request to be authenticated
136+ * @param allowAnonymous If {@code false}, then authentication will <em>not</em> fallback to anonymous.
137+ * If {@code true}, then authentication <em>will</em> fallback to anonymous, if this service is
138+ * configured to allow anonymous access (see {@link #isAnonymousUserEnabled}).
139+ */
140+ public void authenticate (RestRequest request , boolean allowAnonymous , ActionListener <Authentication > authenticationListener ) {
141+ createAuthenticator (request , allowAnonymous , authenticationListener ).authenticateAsync ();
126142 }
127143
128144 /**
@@ -133,15 +149,31 @@ public void authenticate(RestRequest request, ActionListener<Authentication> aut
133149 *
134150 * @param action The action of the message
135151 * @param message The message to be authenticated
136- * @param fallbackUser The default user that will be assumed if no other user is attached to the message. Can be
137- * {@code null}, in which case there will be no fallback user and the success/failure of the
138- * authentication will be based on the whether there's an attached user to in the message and
139- * if there is, whether its credentials are valid.
152+ * @param fallbackUser The default user that will be assumed if no other user is attached to the message. May not
153+ * be {@code null}.
140154 */
141155 public void authenticate (String action , TransportMessage message , User fallbackUser , ActionListener <Authentication > listener ) {
156+ Objects .requireNonNull (fallbackUser , "fallback user may not be null" );
142157 createAuthenticator (action , message , fallbackUser , listener ).authenticateAsync ();
143158 }
144159
160+ /**
161+ * Authenticates the user that is associated with the given message. If the user was authenticated successfully (i.e.
162+ * a user was indeed associated with the request and the credentials were verified to be valid), the method returns
163+ * the user and that user is then "attached" to the message's context.
164+ * If no user or credentials are found to be attached to the given message, and the caller allows anonymous access
165+ * ({@code allowAnonymous} parameter), and this service is configured for anonymous access (see {@link #isAnonymousUserEnabled} and
166+ * {@link #anonymousUser}), then the anonymous user will be returned instead.
167+ *
168+ * @param action The action of the message
169+ * @param message The message to be authenticated
170+ * @param allowAnonymous Whether to permit anonymous access for this request (this only relevant if the service is
171+ * {@link #isAnonymousUserEnabled configured for anonymous access}).
172+ */
173+ public void authenticate (String action , TransportMessage message , boolean allowAnonymous , ActionListener <Authentication > listener ) {
174+ createAuthenticator (action , message , allowAnonymous , listener ).authenticateAsync ();
175+ }
176+
145177 /**
146178 * Authenticates the user based on the contents of the token that is provided as parameter. This will not look at the values in the
147179 * ThreadContext for Authentication.
@@ -152,7 +184,7 @@ public void authenticate(String action, TransportMessage message, User fallbackU
152184 */
153185 public void authenticate (String action , TransportMessage message ,
154186 AuthenticationToken token , ActionListener <Authentication > listener ) {
155- new Authenticator (action , message , null , listener ).authenticateToken (token );
187+ new Authenticator (action , message , shouldFallbackToAnonymous ( true ) , listener ).authenticateToken (token );
156188 }
157189
158190 public void expire (String principal ) {
@@ -178,12 +210,19 @@ public void onSecurityIndexStateChange(SecurityIndexManager.State previousState,
178210 }
179211
180212 // pkg private method for testing
181- Authenticator createAuthenticator (RestRequest request , ActionListener <Authentication > listener ) {
182- return new Authenticator (request , listener );
213+ Authenticator createAuthenticator (RestRequest request , boolean fallbackToAnonymous , ActionListener <Authentication > listener ) {
214+ return new Authenticator (request , shouldFallbackToAnonymous ( fallbackToAnonymous ), listener );
183215 }
184216
185217 // pkg private method for testing
186- Authenticator createAuthenticator (String action , TransportMessage message , User fallbackUser , ActionListener <Authentication > listener ) {
218+ Authenticator createAuthenticator (String action , TransportMessage message , boolean fallbackToAnonymous ,
219+ ActionListener <Authentication > listener ) {
220+ return new Authenticator (action , message , shouldFallbackToAnonymous (fallbackToAnonymous ), listener );
221+ }
222+
223+ // pkg private method for testing
224+ Authenticator createAuthenticator (String action , TransportMessage message , User fallbackUser ,
225+ ActionListener <Authentication > listener ) {
187226 return new Authenticator (action , message , fallbackUser , listener );
188227 }
189228
@@ -192,6 +231,31 @@ long getNumInvalidation() {
192231 return numInvalidation .get ();
193232 }
194233
234+ /**
235+ * Determines whether to support anonymous access for the current request. Returns {@code true} if all of the following are true
236+ * <ul>
237+ * <li>The service has anonymous authentication enabled (see {@link #isAnonymousUserEnabled})</li>
238+ * <li>Anonymous access is accepted for this request ({@code allowAnonymousOnThisRequest} parameter)
239+ * <li>The {@link ThreadContext} does not provide API Key or Bearer Token credentials. If these are present, we
240+ * treat the request as though it attempted to authenticate (even if that failed), and will not fall back to anonymous.</li>
241+ * </ul>
242+ */
243+ boolean shouldFallbackToAnonymous (boolean allowAnonymousOnThisRequest ) {
244+ if (isAnonymousUserEnabled == false ) {
245+ return false ;
246+ }
247+ if (allowAnonymousOnThisRequest == false ) {
248+ return false ;
249+ }
250+ String header = threadContext .getHeader ("Authorization" );
251+ if (Strings .hasText (header ) &&
252+ ((header .regionMatches (true , 0 , "Bearer " , 0 , "Bearer " .length ()) && header .length () > "Bearer " .length ()) ||
253+ (header .regionMatches (true , 0 , "ApiKey " , 0 , "ApiKey " .length ()) && header .length () > "ApiKey " .length ()))) {
254+ return false ;
255+ }
256+ return true ;
257+ }
258+
195259 /**
196260 * This class is responsible for taking a request and executing the authentication. The authentication is executed in an asynchronous
197261 * fashion in order to avoid blocking calls on a network thread. This class also performs the auditing necessary around authentication
@@ -200,6 +264,7 @@ class Authenticator {
200264
201265 private final AuditableRequest request ;
202266 private final User fallbackUser ;
267+ private final boolean fallbackToAnonymous ;
203268 private final List <Realm > defaultOrderedRealmList ;
204269 private final ActionListener <Authentication > listener ;
205270
@@ -208,18 +273,25 @@ class Authenticator {
208273 private AuthenticationToken authenticationToken = null ;
209274 private AuthenticationResult authenticationResult = null ;
210275
211- Authenticator (RestRequest request , ActionListener <Authentication > listener ) {
212- this (new AuditableRestRequest (auditTrail , failureHandler , threadContext , request ), null , listener );
276+ Authenticator (RestRequest request , boolean fallbackToAnonymous , ActionListener <Authentication > listener ) {
277+ this (new AuditableRestRequest (auditTrail , failureHandler , threadContext , request ), null , fallbackToAnonymous , listener );
278+ }
279+
280+ Authenticator (String action , TransportMessage message , boolean fallbackToAnonymous , ActionListener <Authentication > listener ) {
281+ this (new AuditableTransportRequest (auditTrail , failureHandler , threadContext , action , message ),
282+ null , fallbackToAnonymous , listener );
213283 }
214284
215285 Authenticator (String action , TransportMessage message , User fallbackUser , ActionListener <Authentication > listener ) {
216- this (new AuditableTransportRequest (auditTrail , failureHandler , threadContext , action , message
217- ), fallbackUser , listener );
286+ this (new AuditableTransportRequest (auditTrail , failureHandler , threadContext , action , message ),
287+ Objects . requireNonNull ( fallbackUser , "Fallback user cannot be null" ), false , listener );
218288 }
219289
220- private Authenticator (AuditableRequest auditableRequest , User fallbackUser , ActionListener <Authentication > listener ) {
290+ private Authenticator (AuditableRequest auditableRequest , User fallbackUser , boolean fallbackToAnonymous ,
291+ ActionListener <Authentication > listener ) {
221292 this .request = auditableRequest ;
222293 this .fallbackUser = fallbackUser ;
294+ this .fallbackToAnonymous = fallbackToAnonymous ;
223295 this .defaultOrderedRealmList = realms .asList ();
224296 this .listener = listener ;
225297 }
@@ -479,7 +551,7 @@ void handleNullToken() {
479551 RealmRef authenticatedBy = new RealmRef ("__fallback" , "__fallback" , nodeName );
480552 authentication = new Authentication (fallbackUser , authenticatedBy , null , Version .CURRENT , AuthenticationType .INTERNAL ,
481553 Collections .emptyMap ());
482- } else if (isAnonymousUserEnabled && shouldFallbackToAnonymous () ) {
554+ } else if (fallbackToAnonymous ) {
483555 logger .trace ("No valid credentials found in request [{}], using anonymous [{}]" , request , anonymousUser .principal ());
484556 RealmRef authenticatedBy = new RealmRef ("__anonymous" , "__anonymous" , nodeName );
485557 authentication = new Authentication (anonymousUser , authenticatedBy , null , Version .CURRENT , AuthenticationType .ANONYMOUS ,
@@ -503,20 +575,6 @@ void handleNullToken() {
503575 action .run ();
504576 }
505577
506- /**
507- * When an API Key or an Elasticsearch Token Service token is used for authentication and authentication fails (as indicated by
508- * a null AuthenticationToken) we should not fallback to the anonymous user.
509- */
510- boolean shouldFallbackToAnonymous (){
511- String header = threadContext .getHeader ("Authorization" );
512- if (Strings .hasText (header ) &&
513- ((header .regionMatches (true , 0 , "Bearer " , 0 , "Bearer " .length ()) && header .length () > "Bearer " .length ()) ||
514- (header .regionMatches (true , 0 , "ApiKey " , 0 , "ApiKey " .length ()) && header .length () > "ApiKey " .length ()))) {
515- return false ;
516- }
517- return true ;
518- }
519-
520578 /**
521579 * Consumes the {@link User} that resulted from attempting to authenticate a token against the {@link Realms}. When the user is
522580 * {@code null}, authentication fails and does not proceed. When there is a user, the request is inspected to see if the run as
0 commit comments