Skip to content

Commit

Permalink
User Profile: Activate user profile API (#82400)
Browse files Browse the repository at this point in the history
This PR adds the initial REST endpoint to activate user profile.

The API takes a grant type request similar to GrantApiKey. The request
currently does not allow other content other than grant related fields. This
means all information is extracted from the Authentication object. In future,
we will support more fields in the request body so that extra information, e.g.
displayName, can be specified by the calling application.

Since authentication object has not been updated for domain, for now, the
profile creation and update do not record domain related information either.
Similarly, when updating existing profile, it searches the document using realm
name and not considering domain.
  • Loading branch information
ywangd committed Jan 17, 2022
1 parent 22b7ed7 commit 3c131e6
Show file tree
Hide file tree
Showing 27 changed files with 1,007 additions and 234 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.security.action;

import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.xpack.core.security.authc.AuthenticationToken;
import org.elasticsearch.xpack.core.security.authc.support.BearerToken;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;

import java.io.IOException;

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

/**
* Fields related to the end user authentication
*/
public class Grant implements Writeable {
public static final String PASSWORD_GRANT_TYPE = "password";
public static final String ACCESS_TOKEN_GRANT_TYPE = "access_token";

private String type;
private String username;
private SecureString password;
private SecureString accessToken;

public Grant() {}

public Grant(StreamInput in) throws IOException {
this.type = in.readString();
this.username = in.readOptionalString();
this.password = in.readOptionalSecureString();
this.accessToken = in.readOptionalSecureString();
}

public void writeTo(StreamOutput out) throws IOException {
out.writeString(type);
out.writeOptionalString(username);
out.writeOptionalSecureString(password);
out.writeOptionalSecureString(accessToken);
}

public String getType() {
return type;
}

public String getUsername() {
return username;
}

public SecureString getPassword() {
return password;
}

public SecureString getAccessToken() {
return accessToken;
}

public void setType(String type) {
this.type = type;
}

public void setUsername(String username) {
this.username = username;
}

public void setPassword(SecureString password) {
this.password = password;
}

public void setAccessToken(SecureString accessToken) {
this.accessToken = accessToken;
}

public AuthenticationToken getAuthenticationToken() {
assert validate(null) == null : "grant is invalid";
return switch (type) {
case PASSWORD_GRANT_TYPE -> new UsernamePasswordToken(username, password);
case ACCESS_TOKEN_GRANT_TYPE -> new BearerToken(accessToken);
default -> null;
};
}

public ActionRequestValidationException validate(ActionRequestValidationException validationException) {
if (type == null) {
validationException = addValidationError("[grant_type] is required", validationException);
} else if (type.equals(PASSWORD_GRANT_TYPE)) {
validationException = validateRequiredField("username", username, validationException);
validationException = validateRequiredField("password", password, validationException);
validationException = validateUnsupportedField("access_token", accessToken, validationException);
} else if (type.equals(ACCESS_TOKEN_GRANT_TYPE)) {
validationException = validateRequiredField("access_token", accessToken, validationException);
validationException = validateUnsupportedField("username", username, validationException);
validationException = validateUnsupportedField("password", password, validationException);
} else {
validationException = addValidationError("grant_type [" + type + "] is not supported", validationException);
}
return validationException;
}

private ActionRequestValidationException validateRequiredField(
String fieldName,
CharSequence fieldValue,
ActionRequestValidationException validationException
) {
if (fieldValue == null || fieldValue.length() == 0) {
return addValidationError("[" + fieldName + "] is required for grant_type [" + type + "]", validationException);
}
return validationException;
}

private ActionRequestValidationException validateUnsupportedField(
String fieldName,
CharSequence fieldValue,
ActionRequestValidationException validationException
) {
if (fieldValue != null && fieldValue.length() > 0) {
return addValidationError("[" + fieldName + "] is not supported for grant_type [" + type + "]", validationException);
}
return validationException;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,105 +7,36 @@

package org.elasticsearch.xpack.core.security.action;

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.SecureString;

import java.io.IOException;
import java.util.Objects;

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

/**
* Request class used for the creation of an API key on behalf of another user.
* Logically this is similar to {@link CreateApiKeyRequest}, but is for cases when the user that has permission to call this action
* is different to the user for whom the API key should be created
*/
public final class GrantApiKeyRequest extends ActionRequest {

public static final String PASSWORD_GRANT_TYPE = "password";
public static final String ACCESS_TOKEN_GRANT_TYPE = "access_token";

/**
* Fields related to the end user authentication
*/
public static class Grant implements Writeable {
private String type;
private String username;
private SecureString password;
private SecureString accessToken;

public Grant() {}

public Grant(StreamInput in) throws IOException {
this.type = in.readString();
this.username = in.readOptionalString();
this.password = in.readOptionalSecureString();
this.accessToken = in.readOptionalSecureString();
}

public void writeTo(StreamOutput out) throws IOException {
out.writeString(type);
out.writeOptionalString(username);
out.writeOptionalSecureString(password);
out.writeOptionalSecureString(accessToken);
}

public String getType() {
return type;
}

public String getUsername() {
return username;
}

public SecureString getPassword() {
return password;
}

public SecureString getAccessToken() {
return accessToken;
}

public void setType(String type) {
this.type = type;
}
public final class GrantApiKeyRequest extends GrantRequest {

public void setUsername(String username) {
this.username = username;
}

public void setPassword(SecureString password) {
this.password = password;
}

public void setAccessToken(SecureString accessToken) {
this.accessToken = accessToken;
}
}

private final Grant grant;
private CreateApiKeyRequest apiKey;

public GrantApiKeyRequest() {
this.grant = new Grant();
super();
this.apiKey = new CreateApiKeyRequest();
}

public GrantApiKeyRequest(StreamInput in) throws IOException {
super(in);
this.grant = new Grant(in);
this.apiKey = new CreateApiKeyRequest(in);
}

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

Expand All @@ -117,10 +48,6 @@ public void setRefreshPolicy(WriteRequest.RefreshPolicy refreshPolicy) {
apiKey.setRefreshPolicy(refreshPolicy);
}

public Grant getGrant() {
return grant;
}

public CreateApiKeyRequest getApiKeyRequest() {
return apiKey;
}
Expand All @@ -132,41 +59,6 @@ public void setApiKeyRequest(CreateApiKeyRequest apiKeyRequest) {
@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = apiKey.validate();
if (grant.type == null) {
validationException = addValidationError("[grant_type] is required", validationException);
} else if (grant.type.equals(PASSWORD_GRANT_TYPE)) {
validationException = validateRequiredField("username", grant.username, validationException);
validationException = validateRequiredField("password", grant.password, validationException);
validationException = validateUnsupportedField("access_token", grant.accessToken, validationException);
} else if (grant.type.equals(ACCESS_TOKEN_GRANT_TYPE)) {
validationException = validateRequiredField("access_token", grant.accessToken, validationException);
validationException = validateUnsupportedField("username", grant.username, validationException);
validationException = validateUnsupportedField("password", grant.password, validationException);
} else {
validationException = addValidationError("grant_type [" + grant.type + "] is not supported", validationException);
}
return validationException;
}

private ActionRequestValidationException validateRequiredField(
String fieldName,
CharSequence fieldValue,
ActionRequestValidationException validationException
) {
if (fieldValue == null || fieldValue.length() == 0) {
return addValidationError("[" + fieldName + "] is required for grant_type [" + grant.type + "]", validationException);
}
return validationException;
}

private ActionRequestValidationException validateUnsupportedField(
String fieldName,
CharSequence fieldValue,
ActionRequestValidationException validationException
) {
if (fieldValue != null && fieldValue.length() > 0) {
return addValidationError("[" + fieldName + "] is not supported for grant_type [" + grant.type + "]", validationException);
}
return validationException;
return grant.validate(validationException);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.security.action;

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

import java.io.IOException;

public abstract class GrantRequest extends ActionRequest {
protected final Grant grant;

public GrantRequest() {
this.grant = new Grant();
}

public GrantRequest(StreamInput in) throws IOException {
super(in);
this.grant = new Grant(in);
}

public Grant getGrant() {
return grant;
}

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

@Override
public ActionRequestValidationException validate() {
return grant.validate(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.security.action.profile;

import org.elasticsearch.action.ActionType;

public class ActivateProfileAction extends ActionType<ActivateProfileResponse> {

public static final String NAME = "cluster:admin/xpack/security/profile/activate";
public static final ActivateProfileAction INSTANCE = new ActivateProfileAction();

public ActivateProfileAction() {
super(NAME, ActivateProfileResponse::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.security.action.profile;

import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.core.security.action.GrantRequest;

import java.io.IOException;

public class ActivateProfileRequest extends GrantRequest {

public ActivateProfileRequest() {
super();
}

public ActivateProfileRequest(StreamInput in) throws IOException {
super(in);
}

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

@Override
public ActionRequestValidationException validate() {
return super.validate();
}
}

0 comments on commit 3c131e6

Please sign in to comment.