Skip to content

Support RP initated single log out #38475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 41 commits into from
Feb 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6448271
Implement code and implicit flows
jkakavas Jan 23, 2019
92f63ef
fix licenses and SHAs for introduced dependencies
jkakavas Jan 23, 2019
19034ce
Support userinfo requests
jkakavas Jan 23, 2019
aaa5426
Merge remote-tracking branch 'origin/master' into oidc-realm-authenti…
jkakavas Jan 24, 2019
6614057
Add few tests
jkakavas Jan 24, 2019
b70526a
Fix bugs and add unit tests
jkakavas Jan 25, 2019
422319d
Merge remote-tracking branch 'origin/feature-oidc-realm' into oidc-re…
jkakavas Jan 25, 2019
05910d9
Address Feedback
jkakavas Jan 29, 2019
22e8bb7
Merge remote-tracking branch 'origin/feature-oidc-realm' into oidc-re…
jkakavas Jan 29, 2019
b8aa5be
Fix Access Token validation
jkakavas Jan 30, 2019
e3204f5
Completes security tests
jkakavas Jan 30, 2019
ad493a9
Merge remote-tracking branch 'origin/feature-oidc-realm' into oidc-re…
jkakavas Jan 30, 2019
b3cbd8d
Fix checkstyle
jkakavas Jan 30, 2019
0a0ae0e
Make JWKSource reloading async
jkakavas Feb 1, 2019
bc1f552
Merge remote-tracking branch 'origin/feature-oidc-realm' into oidc-re…
jkakavas Feb 1, 2019
baadcdb
fix thirdPartyAudit
jkakavas Feb 1, 2019
56b86eb
Adds logout support to the OIDC realm
jkakavas Feb 1, 2019
09413b5
address feedback
jkakavas Feb 1, 2019
300f523
Merge branch 'oidc-realm-authentication-flows' into oidc-realm-logout
jkakavas Feb 2, 2019
3e67b2b
Finalizes logout support
jkakavas Feb 2, 2019
59bc122
cleanup
jkakavas Feb 2, 2019
58e62a9
Merge branch 'oidc-realm-authentication-flows' into oidc-realm-logout
jkakavas Feb 2, 2019
924f427
handle not existing end session url in config
jkakavas Feb 3, 2019
86e2ca4
re-introduce the option for facilitators to pass state and nonce values
jkakavas Feb 3, 2019
79d5971
Merge branch 'oidc-realm-authentication-flows' into oidc-realm-logout
jkakavas Feb 3, 2019
dd7023d
re-introduce the option for facilitators to pass state and nonce values
jkakavas Feb 3, 2019
5db0c04
Merge branch 'oidc-realm-authentication-flows' into oidc-realm-logout
jkakavas Feb 3, 2019
b368ffc
re-introduce the option for facilitators to pass state and nonce values
jkakavas Feb 3, 2019
509c141
Merge branch 'oidc-realm-authentication-flows' into oidc-realm-logout
jkakavas Feb 3, 2019
81a1770
remove unused import
jkakavas Feb 3, 2019
c9c1cf7
Merge branch 'feature-oidc-realm' into oidc-realm-authentication-flows
jkakavas Feb 3, 2019
3450541
fix tests
jkakavas Feb 3, 2019
42b48b8
Merge branch 'oidc-realm-authentication-flows' into oidc-realm-logout
jkakavas Feb 4, 2019
fdf6f9b
Merge branch 'feature-oidc-realm' into oidc-realm-logout
jkakavas Feb 5, 2019
838b941
address feedback
jkakavas Feb 11, 2019
9d92b1c
Merge branch 'feature-oidc-realm' into oidc-realm-logout
jkakavas Feb 11, 2019
a9b37ca
fix test
jkakavas Feb 11, 2019
4b0e14c
address feedback
jkakavas Feb 13, 2019
25f59bc
Merge remote-tracking branch 'origin/feature-oidc-realm' into oidc-re…
jkakavas Feb 22, 2019
8683b44
allign
jkakavas Feb 22, 2019
2e3c7d5
Merge branch 'feature-oidc-realm' into oidc-realm-logout
jkakavas Feb 26, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.oidc;

import org.elasticsearch.action.Action;
import org.elasticsearch.common.io.stream.Writeable;

public class OpenIdConnectLogoutAction extends Action<OpenIdConnectLogoutResponse> {

public static final OpenIdConnectLogoutAction INSTANCE = new OpenIdConnectLogoutAction();
public static final String NAME = "cluster:admin/xpack/security/oidc/logout";

private OpenIdConnectLogoutAction() {
super(NAME);
}

@Override
public OpenIdConnectLogoutResponse newResponse() {
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

@Override
public Writeable.Reader<OpenIdConnectLogoutResponse> getResponseReader() {
return OpenIdConnectLogoutResponse::new;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.oidc;

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;

import java.io.IOException;

import static org.elasticsearch.action.ValidateActions.addValidationError;

public final class OpenIdConnectLogoutRequest extends ActionRequest {

private String token;
@Nullable
private String refreshToken;

public OpenIdConnectLogoutRequest() {

}

public OpenIdConnectLogoutRequest(StreamInput in) throws IOException {
super.readFrom(in);
token = in.readString();
refreshToken = in.readOptionalString();
}

@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (Strings.isNullOrEmpty(token)) {
validationException = addValidationError("token is missing", validationException);
}
return validationException;
}

public String getToken() {
return token;
}

public void setToken(String token) {
this.token = token;
}

public String getRefreshToken() {
return refreshToken;
}

public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(token);
out.writeOptionalString(refreshToken);
}

@Override
public void readFrom(StreamInput in) {
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.security.action.oidc;

import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;

import java.io.IOException;

public final class OpenIdConnectLogoutResponse extends ActionResponse {

private String endSessionUrl;

public OpenIdConnectLogoutResponse(StreamInput in) throws IOException {
super.readFrom(in);
this.endSessionUrl = in.readString();
}

public OpenIdConnectLogoutResponse(String endSessionUrl) {
this.endSessionUrl = endSessionUrl;
}

@Override
public void readFrom(StreamInput in) {
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(endSessionUrl);
}

public String toString() {
return "{endSessionUrl=" + endSessionUrl + "}";
}

public String getEndSessionUrl() {
return endSessionUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ private OpenIdConnectRealmSettings() {
throw new IllegalArgumentException("Invalid value [" + v + "] for [" + key + "]. Not a valid URI.", e);
}
}, Setting.Property.NodeScope));
public static final Setting.AffixSetting<String> RP_POST_LOGOUT_REDIRECT_URI
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "rp.post_logout_redirect_uri",
key -> Setting.simpleString(key, v -> {
try {
new URI(v);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid value [" + v + "] for [" + key + "]. Not a valid URI.", e);
}
}, Setting.Property.NodeScope));
public static final Setting.AffixSetting<String> RP_RESPONSE_TYPE
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "rp.response_type",
key -> Setting.simpleString(key, v -> {
Expand Down Expand Up @@ -95,6 +104,15 @@ private OpenIdConnectRealmSettings() {
throw new IllegalArgumentException("Invalid value [" + v + "] for [" + key + "]. Not a valid URI.", e);
}
}, Setting.Property.NodeScope));
public static final Setting.AffixSetting<String> OP_ENDSESSION_ENDPOINT
= Setting.affixKeySetting(RealmSettings.realmSettingPrefix(TYPE), "op.endsession_endpoint",
key -> Setting.simpleString(key, v -> {
try {
new URI(v);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid value [" + v + "] for [" + key + "]. Not a valid URI.", e);
}
}, Setting.Property.NodeScope));
public static final Setting.AffixSetting<String> OP_ISSUER
= RealmSettings.simpleString(TYPE, "op.issuer", Setting.Property.NodeScope);
public static final Setting.AffixSetting<String> OP_JWKSET_PATH
Expand Down Expand Up @@ -132,9 +150,9 @@ private OpenIdConnectRealmSettings() {
public static Set<Setting.AffixSetting<?>> getSettings() {
final Set<Setting.AffixSetting<?>> set = Sets.newHashSet(
RP_CLIENT_ID, RP_REDIRECT_URI, RP_RESPONSE_TYPE, RP_REQUESTED_SCOPES, RP_CLIENT_SECRET, RP_SIGNATURE_ALGORITHM,
OP_NAME, OP_AUTHORIZATION_ENDPOINT, OP_TOKEN_ENDPOINT, OP_USERINFO_ENDPOINT, OP_ISSUER, OP_JWKSET_PATH,
HTTP_CONNECT_TIMEOUT, HTTP_CONNECTION_READ_TIMEOUT, HTTP_SOCKET_TIMEOUT, HTTP_MAX_CONNECTIONS, HTTP_MAX_ENDPOINT_CONNECTIONS,
ALLOWED_CLOCK_SKEW);
RP_POST_LOGOUT_REDIRECT_URI, OP_NAME, OP_AUTHORIZATION_ENDPOINT, OP_TOKEN_ENDPOINT, OP_USERINFO_ENDPOINT,
OP_ENDSESSION_ENDPOINT, OP_ISSUER, OP_JWKSET_PATH, HTTP_CONNECT_TIMEOUT, HTTP_CONNECTION_READ_TIMEOUT, HTTP_SOCKET_TIMEOUT,
HTTP_MAX_CONNECTIONS, HTTP_MAX_ENDPOINT_CONNECTIONS, ALLOWED_CLOCK_SKEW);
set.addAll(DelegatedAuthorizationSettings.getSettings(TYPE));
set.addAll(RealmSettings.getStandardSettings(TYPE));
set.addAll(SSLConfigurationSettings.getRealmSettings(TYPE));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import org.elasticsearch.xpack.core.security.action.GetApiKeyAction;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyAction;
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectAuthenticateAction;
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectLogoutAction;
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectPrepareAuthenticationAction;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction;
Expand Down Expand Up @@ -138,6 +139,7 @@
import org.elasticsearch.xpack.security.action.TransportInvalidateApiKeyAction;
import org.elasticsearch.xpack.security.action.filter.SecurityActionFilter;
import org.elasticsearch.xpack.security.action.oidc.TransportOpenIdConnectAuthenticateAction;
import org.elasticsearch.xpack.security.action.oidc.TransportOpenIdConnectLogoutAction;
import org.elasticsearch.xpack.security.action.oidc.TransportOpenIdConnectPrepareAuthenticationAction;
import org.elasticsearch.xpack.security.action.privilege.TransportDeletePrivilegesAction;
import org.elasticsearch.xpack.security.action.privilege.TransportGetPrivilegesAction;
Expand Down Expand Up @@ -197,6 +199,7 @@
import org.elasticsearch.xpack.security.rest.action.RestInvalidateApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.oauth2.RestGetTokenAction;
import org.elasticsearch.xpack.security.rest.action.oauth2.RestInvalidateTokenAction;
import org.elasticsearch.xpack.security.rest.action.oidc.RestOpenIdConnectLogoutAction;
import org.elasticsearch.xpack.security.rest.action.privilege.RestDeletePrivilegesAction;
import org.elasticsearch.xpack.security.rest.action.privilege.RestGetPrivilegesAction;
import org.elasticsearch.xpack.security.rest.action.privilege.RestPutPrivilegesAction;
Expand Down Expand Up @@ -745,6 +748,7 @@ public void onIndexModule(IndexModule module) {
new ActionHandler<>(OpenIdConnectPrepareAuthenticationAction.INSTANCE,
TransportOpenIdConnectPrepareAuthenticationAction.class),
new ActionHandler<>(OpenIdConnectAuthenticateAction.INSTANCE, TransportOpenIdConnectAuthenticateAction.class),
new ActionHandler<>(OpenIdConnectLogoutAction.INSTANCE, TransportOpenIdConnectLogoutAction.class),
new ActionHandler<>(GetPrivilegesAction.INSTANCE, TransportGetPrivilegesAction.class),
new ActionHandler<>(PutPrivilegesAction.INSTANCE, TransportPutPrivilegesAction.class),
new ActionHandler<>(DeletePrivilegesAction.INSTANCE, TransportDeletePrivilegesAction.class),
Expand Down Expand Up @@ -799,6 +803,7 @@ public List<RestHandler> getRestHandlers(Settings settings, RestController restC
new RestSamlInvalidateSessionAction(settings, restController, getLicenseState()),
new RestOpenIdConnectPrepareAuthenticationAction(settings, restController, getLicenseState()),
new RestOpenIdConnectAuthenticateAction(settings, restController, getLicenseState()),
new RestOpenIdConnectLogoutAction(settings, restController, getLicenseState()),
new RestGetPrivilegesAction(settings, restController, getLicenseState()),
new RestPutPrivilegesAction(settings, restController, getLicenseState()),
new RestDeletePrivilegesAction(settings, restController, getLicenseState()),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.security.action.oidc;

import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectLogoutAction;
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectLogoutRequest;
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectLogoutResponse;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.Realm;
import org.elasticsearch.xpack.core.security.authc.support.TokensInvalidationResult;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.Realms;
import org.elasticsearch.xpack.security.authc.TokenService;
import org.elasticsearch.xpack.security.authc.oidc.OpenIdConnectRealm;

import java.io.IOException;
import java.text.ParseException;
import java.util.Map;

/**
* Transport action responsible for generating an OpenID connect logout request to be sent to an OpenID Connect Provider
*/
public class TransportOpenIdConnectLogoutAction extends HandledTransportAction<OpenIdConnectLogoutRequest, OpenIdConnectLogoutResponse> {

private final Realms realms;
private final TokenService tokenService;
private static final Logger logger = LogManager.getLogger(TransportOpenIdConnectLogoutAction.class);

@Inject
public TransportOpenIdConnectLogoutAction(TransportService transportService, ActionFilters actionFilters, Realms realms,
TokenService tokenService) {
super(OpenIdConnectLogoutAction.NAME, transportService, actionFilters,
(Writeable.Reader<OpenIdConnectLogoutRequest>) OpenIdConnectLogoutRequest::new);
this.realms = realms;
this.tokenService = tokenService;
}

@Override
protected void doExecute(Task task, OpenIdConnectLogoutRequest request, ActionListener<OpenIdConnectLogoutResponse> listener) {
invalidateRefreshToken(request.getRefreshToken(), ActionListener.wrap(ignore -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave this last, after we make sure the token is valid and the authn token has been invalidated. This way the refresh_token is usable even when this API returns an error (validation or otherwise).
Do you think otherwise?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea behind invalidating the refresh token first (both here and in the SAML logout , or the invalidate API) is that since our refresh tokens and access tokens are linked, invalidation usually means both tokens. Now if we invalidate the access token before the refresh token, theoretically we allow for an extra window (while we invalidate the access token) where the refresh token can be used to get a new refresh token, thus persisting access.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, it makes sense, thanks.

I think, it would be better to address the root problem, to not allow refreshing invalidated access tokens or that invalidating the refresh token would also invalidate the access token.

Should probably be done in a follow-up, WDYT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to not allow refreshing invalidated access tokens

This is not the root problem. The root problem here is that we are "logging out a user" and that user has both an access and a refresh token. Since the refresh token can be used to get a new refresh token (and access token) , and since we use this step invalidation process, it makes sense that the refresh token should be invalidated first.

or that invalidating the refresh token would also invalidate the access token.

We don't necessarily want to do this. There is a valid use case where a client might want to invalidate the refresh token so that the user can only use their current access token for the remaining duration of it but not be able to refresh.

I think there is value in your suggestion and we could add an invalidatedTokenDocument method in the TokenService and call that from the various TransportRealmLogoutAction's. I'll address this in a follow up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a valid use case where a client might want to invalidate the refresh token so that the user can only use their current access token for the remaining duration of it but not be able to refresh.

Sure we can be flexible here, but the client should not act like he owns it . The authorization server can invalidate tokens even without the client calling an API (to mitigate threats, etc) I am leaning towards this being the wrong kind of flexibility because it doesn't bring any benefit to the client.

I think there is value in your suggestion and we could add an invalidatedTokenDocument method in the TokenService and call that from the various TransportRealmLogoutAction's.

I am 👍 to this, but see below:

I flagged this as a problem because it makes things "smart". I understand the reasoning from a protocol point of view, but I don't like it as a programmer that these functions should be called in order if they don't pass a return value from one to the other.

As a data point, the RFC https://tools.ietf.org/html/rfc7009#section-2.1 says:

Depending on the authorization server's revocation policy, the
revocation of a particular token may cause the revocation of related
tokens and the underlying authorization grant. If the particular
token is a refresh token and the authorization server supports the
revocation of access tokens, then the authorization server SHOULD
also invalidate all access tokens based on the same authorization
grant (see Implementation Note). If the token passed to the request
is an access token, the server MAY revoke the respective refresh
token as well.

I think we should go with SHOULD and invalidate the access token when the refresh token is invalidated.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're digressing but I'm enjoying the discussion :)

We had gone back and forth on this also when the refresh functionality was introduced back in January. I don't have a very clear recollection on whether we consciously decided to not invalidate access tokens when invalidating refresh tokens, I do remember we consciously decided to not invalidate refresh tokens when invalidating the corresponding access token.

I think your RTFRFC persuaded me, this could also simplify the TokenService code further. I'll be taking it up as a follow up task and we can iterate on that PR more if needed

try {
final String token = request.getToken();
tokenService.getAuthenticationAndMetaData(token, ActionListener.wrap(
tuple -> {
final Authentication authentication = tuple.v1();
final Map<String, Object> tokenMetadata = tuple.v2();
validateAuthenticationAndMetadata(authentication, tokenMetadata);
tokenService.invalidateAccessToken(token, ActionListener.wrap(
result -> {
if (logger.isTraceEnabled()) {
logger.trace("OpenID Connect Logout for user [{}] and token [{}...{}]",
authentication.getUser().principal(),
token.substring(0, 8),
token.substring(token.length() - 8));
}
OpenIdConnectLogoutResponse response = buildResponse(authentication, tokenMetadata);
listener.onResponse(response);
}, listener::onFailure)
);
}, listener::onFailure));
} catch (IOException e) {
listener.onFailure(e);
}
}, listener::onFailure));
}

private OpenIdConnectLogoutResponse buildResponse(Authentication authentication, Map<String, Object> tokenMetadata) {
final String idTokenHint = (String) getFromMetadata(tokenMetadata, "id_token_hint");
final Realm realm = this.realms.realm(authentication.getAuthenticatedBy().getName());
final JWT idToken;
try {
idToken = JWTParser.parse(idTokenHint);
} catch (ParseException e) {
throw new ElasticsearchSecurityException("Token Metadata did not contain a valid IdToken", e);
}
return ((OpenIdConnectRealm) realm).buildLogoutResponse(idToken);
}

private void validateAuthenticationAndMetadata(Authentication authentication, Map<String, Object> tokenMetadata) {
if (tokenMetadata == null) {
throw new ElasticsearchSecurityException("Authentication did not contain metadata");
}
if (authentication == null) {
throw new ElasticsearchSecurityException("No active authentication");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I see these map to HTTP 500, I think 400 is better. So maybe throw IllegalArgumentException. I don't have a consistent perspective over these stuff (across all APIs), but 500 is the server's fault and 400 is the client's .

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the client giving the server a valid access token and the server fetching a UserToken from .security that has no Authentication. I would claim this is the server's error if the UserToken was malformed in storage or if it originally created one with no Authentication

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct, I was putting all the exceptions thrown in this method in the same bucket. This one is indeed internal server error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change the following:

if (realm instanceof OpenIdConnectRealm == false) {
            throw new IllegalArgumentException("Access token is not valid for an OpenID Connect realm");
}

This can't happen with Kibana, but we mean to allow these APIs to be used by custom web apps that might support more than one authentication realms and in that case these apps should use the correct rest API for the given realm that authenticated their user. Thanks for the feedback on this one

}
final User user = authentication.getUser();
if (user == null) {
throw new ElasticsearchSecurityException("No active user");
}

final Authentication.RealmRef ref = authentication.getAuthenticatedBy();
if (ref == null || Strings.isNullOrEmpty(ref.getName())) {
throw new ElasticsearchSecurityException("Authentication {} has no authenticating realm",
authentication);
}
final Realm realm = this.realms.realm(authentication.getAuthenticatedBy().getName());
if (realm == null) {
throw new ElasticsearchSecurityException("Authenticating realm {} does not exist", ref.getName());
}
if (realm instanceof OpenIdConnectRealm == false) {
throw new IllegalArgumentException("Access token is not valid for an OpenID Connect realm");
}
}

private Object getFromMetadata(Map<String, Object> metadata, String key) {
if (metadata.containsKey(key) == false) {
throw new ElasticsearchSecurityException("Authentication token does not have OpenID Connect metadata [{}]", key);
}
Object value = metadata.get(key);
if (null != value && value instanceof String == false) {
throw new ElasticsearchSecurityException("In authentication token, OpenID Connect metadata [{}] is [{}] rather than " +
"String", key, value.getClass());
}
return value;

}
private void invalidateRefreshToken(String refreshToken, ActionListener<TokensInvalidationResult> listener) {
if (refreshToken == null) {
listener.onResponse(null);
} else {
tokenService.invalidateRefreshToken(refreshToken, listener);
}
}
}
Loading