@@ -118,11 +118,27 @@ public AuthenticationService(Settings settings, Realms realms, AuditTrailService
118
118
* Authenticates the user that is associated with the given request. If the user was authenticated successfully (i.e.
119
119
* a user was indeed associated with the request and the credentials were verified to be valid), the method returns
120
120
* 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.
121
122
*
122
123
* @param request The request to be authenticated
123
124
*/
124
125
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 ();
126
142
}
127
143
128
144
/**
@@ -133,15 +149,31 @@ public void authenticate(RestRequest request, ActionListener<Authentication> aut
133
149
*
134
150
* @param action The action of the message
135
151
* @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}.
140
154
*/
141
155
public void authenticate (String action , TransportMessage message , User fallbackUser , ActionListener <Authentication > listener ) {
156
+ Objects .requireNonNull (fallbackUser , "fallback user may not be null" );
142
157
createAuthenticator (action , message , fallbackUser , listener ).authenticateAsync ();
143
158
}
144
159
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
+
145
177
/**
146
178
* Authenticates the user based on the contents of the token that is provided as parameter. This will not look at the values in the
147
179
* ThreadContext for Authentication.
@@ -152,7 +184,7 @@ public void authenticate(String action, TransportMessage message, User fallbackU
152
184
*/
153
185
public void authenticate (String action , TransportMessage message ,
154
186
AuthenticationToken token , ActionListener <Authentication > listener ) {
155
- new Authenticator (action , message , null , listener ).authenticateToken (token );
187
+ new Authenticator (action , message , shouldFallbackToAnonymous ( true ) , listener ).authenticateToken (token );
156
188
}
157
189
158
190
public void expire (String principal ) {
@@ -178,12 +210,19 @@ public void onSecurityIndexStateChange(SecurityIndexManager.State previousState,
178
210
}
179
211
180
212
// 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 );
183
215
}
184
216
185
217
// 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 ) {
187
226
return new Authenticator (action , message , fallbackUser , listener );
188
227
}
189
228
@@ -192,6 +231,31 @@ long getNumInvalidation() {
192
231
return numInvalidation .get ();
193
232
}
194
233
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
+
195
259
/**
196
260
* This class is responsible for taking a request and executing the authentication. The authentication is executed in an asynchronous
197
261
* 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 {
200
264
201
265
private final AuditableRequest request ;
202
266
private final User fallbackUser ;
267
+ private final boolean fallbackToAnonymous ;
203
268
private final List <Realm > defaultOrderedRealmList ;
204
269
private final ActionListener <Authentication > listener ;
205
270
@@ -208,18 +273,25 @@ class Authenticator {
208
273
private AuthenticationToken authenticationToken = null ;
209
274
private AuthenticationResult authenticationResult = null ;
210
275
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 );
213
283
}
214
284
215
285
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 );
218
288
}
219
289
220
- private Authenticator (AuditableRequest auditableRequest , User fallbackUser , ActionListener <Authentication > listener ) {
290
+ private Authenticator (AuditableRequest auditableRequest , User fallbackUser , boolean fallbackToAnonymous ,
291
+ ActionListener <Authentication > listener ) {
221
292
this .request = auditableRequest ;
222
293
this .fallbackUser = fallbackUser ;
294
+ this .fallbackToAnonymous = fallbackToAnonymous ;
223
295
this .defaultOrderedRealmList = realms .asList ();
224
296
this .listener = listener ;
225
297
}
@@ -479,7 +551,7 @@ void handleNullToken() {
479
551
RealmRef authenticatedBy = new RealmRef ("__fallback" , "__fallback" , nodeName );
480
552
authentication = new Authentication (fallbackUser , authenticatedBy , null , Version .CURRENT , AuthenticationType .INTERNAL ,
481
553
Collections .emptyMap ());
482
- } else if (isAnonymousUserEnabled && shouldFallbackToAnonymous () ) {
554
+ } else if (fallbackToAnonymous ) {
483
555
logger .trace ("No valid credentials found in request [{}], using anonymous [{}]" , request , anonymousUser .principal ());
484
556
RealmRef authenticatedBy = new RealmRef ("__anonymous" , "__anonymous" , nodeName );
485
557
authentication = new Authentication (anonymousUser , authenticatedBy , null , Version .CURRENT , AuthenticationType .ANONYMOUS ,
@@ -503,20 +575,6 @@ void handleNullToken() {
503
575
action .run ();
504
576
}
505
577
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
-
520
578
/**
521
579
* Consumes the {@link User} that resulted from attempting to authenticate a token against the {@link Realms}. When the user is
522
580
* {@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