Skip to content

Commit

Permalink
OpenID Connect Realm base functionality (elastic#37009)
Browse files Browse the repository at this point in the history
This commit adds

* An OpenID Connect Realm definition
* Necessary OpenID Connect Realm settings to support Authorization code
 grant and Implicit grant flows
* Rest and Transport Action and Request/Response objects for initiating and
 completing the authentication flow
* Functionality for generating OIDC Authentication Request URIs Unit tests

Notably missing (to be handled in subsequent PRs):
* The actual implementation of the authentication flows
* Necessary JW{T,S,E} functionality

Relates: elastic#35339
  • Loading branch information
jkakavas committed Jan 20, 2019
1 parent f843c92 commit 63e9e46
Show file tree
Hide file tree
Showing 19 changed files with 244 additions and 241 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.elasticsearch.xpack.core.security.action.oidc;

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

/**
* Action for initiating an authentication process using OpenID Connect
Expand All @@ -15,11 +16,17 @@ public final class OpenIdConnectAuthenticateAction extends Action<OpenIdConnectA
public static final OpenIdConnectAuthenticateAction INSTANCE = new OpenIdConnectAuthenticateAction();
public static final String NAME = "cluster:admin/xpack/security/oidc/authenticate";

protected OpenIdConnectAuthenticateAction() {
private OpenIdConnectAuthenticateAction() {
super(NAME);
}

@Override
public OpenIdConnectAuthenticateResponse newResponse() {
return new OpenIdConnectAuthenticateResponse();
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
}

@Override
public Writeable.Reader<OpenIdConnectAuthenticateResponse> getResponseReader() {
return OpenIdConnectAuthenticateResponse::new;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,32 @@
public class OpenIdConnectAuthenticateRequest extends ActionRequest {

/**
* The URI were the OP redirected the browser after the authentication attempt. This is passed as is from the
* The URI where the OP redirected the browser after the authentication attempt. This is passed as is from the
* facilitator entity (i.e. Kibana)
*/
private String redirectUri;

/**
* The state value that either we or the facilitator generated for this specific flow and that was stored at the user's session with
* The state value that we generated for this specific flow and that should be stored at the user's session with
* the facilitator
*/
private String state;

/**
* The nonce value that the facilitator generated for this specific flow and that was stored at the user's session with
* The nonce value that we generated for this specific flow and that should be stored at the user's session with
* the facilitator
*/
private String nonce;

public OpenIdConnectAuthenticateRequest() {

}

public OpenIdConnectAuthenticateRequest(StreamInput in) throws IOException {
super.readFrom(in);
redirectUri = in.readString();
state = in.readString();
nonce = in.readOptionalString();
}

public String getRedirectUri() {
Expand Down Expand Up @@ -76,11 +84,8 @@ public void writeTo(StreamOutput out) throws IOException {
}

@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
redirectUri = in.readString();
state = in.readString();
nonce = in.readOptionalString();
public void readFrom(StreamInput in) {
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
}

public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ public OpenIdConnectAuthenticateResponse(String principal, String accessTokenStr
this.expiresIn = expiresIn;
}

public OpenIdConnectAuthenticateResponse() {
public OpenIdConnectAuthenticateResponse(StreamInput in) throws IOException {
super.readFrom(in);
principal = in.readString();
accessTokenString = in.readString();
refreshTokenString = in.readString();
expiresIn = in.readTimeValue();
}

public String getPrincipal() {
Expand All @@ -45,12 +50,8 @@ public TimeValue getExpiresIn() {
}

@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
principal = in.readString();
accessTokenString = in.readString();
refreshTokenString = in.readString();
expiresIn = in.readTimeValue();
public void readFrom(StreamInput in) {
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@
package org.elasticsearch.xpack.core.security.action.oidc;

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

public class OpenIdConnectPrepareAuthenticationAction extends Action<OpenIdConnectPrepareAuthenticationResponse> {

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

protected OpenIdConnectPrepareAuthenticationAction() {
private OpenIdConnectPrepareAuthenticationAction() {
super(NAME);
}

@Override
public OpenIdConnectPrepareAuthenticationResponse newResponse() {
return new OpenIdConnectPrepareAuthenticationResponse();
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
}

@Override
public Writeable.Reader<OpenIdConnectPrepareAuthenticationResponse> getResponseReader() {
return OpenIdConnectPrepareAuthenticationResponse::new;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,26 @@
import static org.elasticsearch.action.ValidateActions.addValidationError;

/**
* Represents a request to prepare an OAuth 2.0 authentication request
* Represents a request to prepare an OAuth 2.0 authorization request
*/
public class OpenIdConnectPrepareAuthenticationRequest extends ActionRequest {

private String realmName;
private String state;
private String nonce;

public String getRealmName() {
return realmName;
}

public String getState() {
return state;
}

public String getNonce() {
return nonce;
}

public void setRealmName(String realmName) {
this.realmName = realmName;
}

public void setState(String state) {
this.state = state;
public OpenIdConnectPrepareAuthenticationRequest() {
}

public void setNonce(String nonce) {
this.nonce = nonce;
public OpenIdConnectPrepareAuthenticationRequest(StreamInput in) throws IOException {
super.readFrom(in);
realmName = in.readString();
}

@Override
Expand All @@ -61,20 +51,15 @@ public ActionRequestValidationException validate() {
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(realmName);
out.writeOptionalString(state);
out.writeOptionalString(nonce);
}

@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
realmName = in.readString();
state = in.readOptionalString();
nonce = in.readOptionalString();
public void readFrom(StreamInput in) {
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
}

public String toString() {
return "{realmName=" + realmName + ", state=" + state + ", nonce=" + nonce + "}";
return "{realmName=" + realmName + "}";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,4 @@ public OpenIdConnectPrepareAuthenticationRequestBuilder realmName(String name) {
request.setRealmName(name);
return this;
}

public OpenIdConnectPrepareAuthenticationRequestBuilder state(String state) {
request.setState(state);
return this;
}

public OpenIdConnectPrepareAuthenticationRequestBuilder nonce(String nonce) {
request.setNonce(nonce);
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,38 @@
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;

import java.io.IOException;

/**
* A response containing the authorization endpoint URL and the appropriate request parameters as URL parameters
* A response object that contains the OpenID Connect Authentication Request as a URL and the state and nonce values that were
* generated for this request.
*/
public class OpenIdConnectPrepareAuthenticationResponse extends ActionResponse {
public class OpenIdConnectPrepareAuthenticationResponse extends ActionResponse implements ToXContentObject {

private String authenticationRequestUrl;
/*
* The oAuth2 state parameter used for CSRF protection.
*/
private String state;
/*
* String value used to associate a Client session with an ID Token, and to mitigate replay attacks.
*/
private String nonce;

public OpenIdConnectPrepareAuthenticationResponse(String authorizationEndpointUrl, String state) {
public OpenIdConnectPrepareAuthenticationResponse(String authorizationEndpointUrl, String state, String nonce) {
this.authenticationRequestUrl = authorizationEndpointUrl;
this.state = state;
this.nonce = nonce;
}

public OpenIdConnectPrepareAuthenticationResponse() {
public OpenIdConnectPrepareAuthenticationResponse(StreamInput in) throws IOException {
super.readFrom(in);
authenticationRequestUrl = in.readString();
state = in.readString();
nonce = in.readString();
}

public String getAuthenticationRequestUrl() {
Expand All @@ -35,19 +50,34 @@ public String getState() {
return state;
}

public String getNonce() {
return nonce;
}

@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
authenticationRequestUrl = in.readString();
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(authenticationRequestUrl);
out.writeString(state);
out.writeString(nonce);
}

public String toString() {
return "{authenticationRequestUrl=" + authenticationRequestUrl + ", state=" + state + "}";
return "{authenticationRequestUrl=" + authenticationRequestUrl + ", state=" + state + ", nonce=" + nonce + "}";
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("authentication_request_url", authenticationRequestUrl);
builder.field("state", state);
builder.field("nonce", nonce);
builder.endObject();
return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static Setting.AffixSetting<String> simpleString(String realmType, String
}

/**
* Create a {@link SecureSetting#secureString secure string} {@link Setting} object for a realm of
* Create a {@link SecureSetting#secureString secure string} {@link Setting} object of a realm of
* with the provided type and setting suffix.
*
* @param realmType The type of the realm, used within the setting prefix
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,11 @@ private OpenIdConnectRealmSettings() {
public static final Setting.AffixSetting<List<String>> RP_REQUESTED_SCOPES = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(TYPE), "rp.requested_scopes",
key -> Setting.listSetting(key, Collections.singletonList("openid"), Function.identity(), Setting.Property.NodeScope));
public static final Setting.AffixSetting<List<String>> RP_ALLOWED_SCOPES = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(TYPE), "rp.allowed_scopes",
key -> Setting.listSetting(key, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope));
public static final Setting.AffixSetting<List<String>> RP_ALLOWED_SIGNATURE_ALGORITHMS = Setting.affixKeySetting(
RealmSettings.realmSettingPrefix(TYPE), "rp.allowed_signature_algorithms",
key -> Setting.listSetting(key, Collections.emptyList(), Function.identity(), Setting.Property.NodeScope));

public static Set<Setting.AffixSetting<?>> getSettings() {
final Set<Setting.AffixSetting<?>> set = Sets.newHashSet(
OP_NAME, RP_CLIENT_ID, RP_REDIRECT_URI, RP_RESPONSE_TYPE, RP_REQUESTED_SCOPES, RP_ALLOWED_SCOPES, RP_CLIENT_SECRET,
RP_ALLOWED_SIGNATURE_ALGORITHMS, OP_AUTHORIZATION_ENDPOINT, OP_TOKEN_ENDPOINT, OP_USERINFO_ENDPOINT, OP_ISSUER);
OP_NAME, RP_CLIENT_ID, RP_REDIRECT_URI, RP_RESPONSE_TYPE, RP_REQUESTED_SCOPES, RP_CLIENT_SECRET,
OP_AUTHORIZATION_ENDPOINT, OP_TOKEN_ENDPOINT, OP_USERINFO_ENDPOINT, OP_ISSUER);
set.addAll(DelegatedAuthorizationSettings.getSettings(TYPE));
set.addAll(RealmSettings.getStandardSettings(TYPE));
return set;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.tasks.Task;
Expand All @@ -27,8 +28,8 @@

import java.util.Map;

public class TransportOpenIdConnectAuthenticateAction extends HandledTransportAction<OpenIdConnectAuthenticateRequest,
OpenIdConnectAuthenticateResponse> {
public class TransportOpenIdConnectAuthenticateAction
extends HandledTransportAction<OpenIdConnectAuthenticateRequest, OpenIdConnectAuthenticateResponse> {

private final ThreadPool threadPool;
private final AuthenticationService authenticationService;
Expand All @@ -38,7 +39,8 @@ public class TransportOpenIdConnectAuthenticateAction extends HandledTransportAc
public TransportOpenIdConnectAuthenticateAction(ThreadPool threadPool, TransportService transportService,
ActionFilters actionFilters, AuthenticationService authenticationService,
TokenService tokenService) {
super(OpenIdConnectAuthenticateAction.NAME, transportService, actionFilters, OpenIdConnectAuthenticateRequest::new);
super(OpenIdConnectAuthenticateAction.NAME, transportService, actionFilters,
(Writeable.Reader<OpenIdConnectAuthenticateRequest>) OpenIdConnectAuthenticateRequest::new);
this.threadPool = threadPool;
this.authenticationService = authenticationService;
this.tokenService = tokenService;
Expand Down
Loading

0 comments on commit 63e9e46

Please sign in to comment.