Skip to content

Commit

Permalink
REST API changes for manage-own-api-key privilege (#44936)
Browse files Browse the repository at this point in the history
This commit adds a flag that can be set to `true` if the
API key request (Get or Invalidate) is for the API keys owned
by the currently authenticated user only.
These only interface changes and once the actual cluster privilege 
`manage_own_api_key` is done, we will have another PR to make the
interface work.

The Get API behavior would be:
- when `owner` is set to `true`
  `GET /_security/api_key?id=abcd&owner=true`
        the Rest controller will take care of setting `realm_name` and `username` to the
        values for the authenticated user and only return results if it finds one owned by
        the currently authenticated user.

- when `owner` is set to `false` (default)
  `GET /_security/api_key?id=abcd`
        the Rest controller will assume `realm_name` and `username` to be unspecified
        meaning it will try to search for the API key across users and realms.
        This will fail if the user has only `manage_own_api_key` privilege.

Similarly, for Delete API key behavior:
- when `owner` is set to `true`
   `DELETE /_security/api_key`
   ```
   {
     "id" : "VuaCfGcBCdbkQm-e5aOx",
     "owner": "true"
   }
   ```

   the Rest controller will take care of setting `realm_name` and `username` to the values
   for the authenticated user and only invalidate key if it finds one owned by
   the currently authenticated user.

- when `my_api_keys_only` is set to `false` (default)
   `DELETE /_security/api_key`
   ```
   {
     "id" : "VuaCfGcBCdbkQm-e5aOx",
     "owner": "false"
   }
   ```

    the Rest controller will assume `realm_name` and `username` to be unspecified meaning it will 
    try to search for the API key across users and realms. This will fail if the user has only
    `manage_own_api_key` privilege.

TODO:
- HLRC changes - these will be done in a separate PR
- Actual enforcement of `my_api_keys_only` in a separate PR

Relates #40031
  • Loading branch information
bizybot authored Aug 7, 2019
1 parent 9e61a9c commit 522f005
Show file tree
Hide file tree
Showing 9 changed files with 424 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

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

import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
Expand All @@ -14,6 +15,7 @@
import org.elasticsearch.common.io.stream.StreamOutput;

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

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

Expand All @@ -26,9 +28,10 @@ public final class GetApiKeyRequest extends ActionRequest {
private final String userName;
private final String apiKeyId;
private final String apiKeyName;
private final boolean ownedByAuthenticatedUser;

public GetApiKeyRequest() {
this(null, null, null, null);
this(null, null, null, null, false);
}

public GetApiKeyRequest(StreamInput in) throws IOException {
Expand All @@ -37,14 +40,20 @@ public GetApiKeyRequest(StreamInput in) throws IOException {
userName = in.readOptionalString();
apiKeyId = in.readOptionalString();
apiKeyName = in.readOptionalString();
if (in.getVersion().onOrAfter(Version.V_7_4_0)) {
ownedByAuthenticatedUser = in.readOptionalBoolean();
} else {
ownedByAuthenticatedUser = false;
}
}

public GetApiKeyRequest(@Nullable String realmName, @Nullable String userName, @Nullable String apiKeyId,
@Nullable String apiKeyName) {
@Nullable String apiKeyName, boolean ownedByAuthenticatedUser) {
this.realmName = realmName;
this.userName = userName;
this.apiKeyId = apiKeyId;
this.apiKeyName = apiKeyName;
this.ownedByAuthenticatedUser = ownedByAuthenticatedUser;
}

public String getRealmName() {
Expand All @@ -63,13 +72,17 @@ public String getApiKeyName() {
return apiKeyName;
}

public boolean ownedByAuthenticatedUser() {
return ownedByAuthenticatedUser;
}

/**
* Creates get API key request for given realm name
* @param realmName realm name
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingRealmName(String realmName) {
return new GetApiKeyRequest(realmName, null, null, null);
return new GetApiKeyRequest(realmName, null, null, null, false);
}

/**
Expand All @@ -78,7 +91,7 @@ public static GetApiKeyRequest usingRealmName(String realmName) {
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingUserName(String userName) {
return new GetApiKeyRequest(null, userName, null, null);
return new GetApiKeyRequest(null, userName, null, null, false);
}

/**
Expand All @@ -88,34 +101,38 @@ public static GetApiKeyRequest usingUserName(String userName) {
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingRealmAndUserName(String realmName, String userName) {
return new GetApiKeyRequest(realmName, userName, null, null);
return new GetApiKeyRequest(realmName, userName, null, null, false);
}

/**
* Creates get API key request for given api key id
* @param apiKeyId api key id
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else
* {@code false}
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingApiKeyId(String apiKeyId) {
return new GetApiKeyRequest(null, null, apiKeyId, null);
public static GetApiKeyRequest usingApiKeyId(String apiKeyId, boolean ownedByAuthenticatedUser) {
return new GetApiKeyRequest(null, null, apiKeyId, null, ownedByAuthenticatedUser);
}

/**
* Creates get api key request for given api key name
* @param apiKeyName api key name
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else
* {@code false}
* @return {@link GetApiKeyRequest}
*/
public static GetApiKeyRequest usingApiKeyName(String apiKeyName) {
return new GetApiKeyRequest(null, null, null, apiKeyName);
public static GetApiKeyRequest usingApiKeyName(String apiKeyName, boolean ownedByAuthenticatedUser) {
return new GetApiKeyRequest(null, null, null, apiKeyName, ownedByAuthenticatedUser);
}

@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(apiKeyId) == false
&& Strings.hasText(apiKeyName) == false) {
validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified",
validationException);
&& Strings.hasText(apiKeyName) == false && ownedByAuthenticatedUser == false) {
validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified if " +
"[owner] flag is false", validationException);
}
if (Strings.hasText(apiKeyId) || Strings.hasText(apiKeyName)) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
Expand All @@ -124,6 +141,13 @@ public ActionRequestValidationException validate() {
validationException);
}
}
if (ownedByAuthenticatedUser) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
validationException = addValidationError(
"neither username nor realm-name may be specified when retrieving owned API keys",
validationException);
}
}
if (Strings.hasText(apiKeyId) && Strings.hasText(apiKeyName)) {
validationException = addValidationError("only one of [api key id, api key name] can be specified", validationException);
}
Expand All @@ -137,6 +161,29 @@ public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalString(userName);
out.writeOptionalString(apiKeyId);
out.writeOptionalString(apiKeyName);
if (out.getVersion().onOrAfter(Version.V_7_4_0)) {
out.writeOptionalBoolean(ownedByAuthenticatedUser);
}
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
GetApiKeyRequest that = (GetApiKeyRequest) o;
return ownedByAuthenticatedUser == that.ownedByAuthenticatedUser &&
Objects.equals(realmName, that.realmName) &&
Objects.equals(userName, that.userName) &&
Objects.equals(apiKeyId, that.apiKeyId) &&
Objects.equals(apiKeyName, that.apiKeyName);
}

@Override
public int hashCode() {
return Objects.hash(realmName, userName, apiKeyId, apiKeyName, ownedByAuthenticatedUser);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

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

import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.common.Nullable;
Expand All @@ -14,6 +15,7 @@
import org.elasticsearch.common.io.stream.StreamOutput;

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

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

Expand All @@ -26,9 +28,10 @@ public final class InvalidateApiKeyRequest extends ActionRequest {
private final String userName;
private final String id;
private final String name;
private final boolean ownedByAuthenticatedUser;

public InvalidateApiKeyRequest() {
this(null, null, null, null);
this(null, null, null, null, false);
}

public InvalidateApiKeyRequest(StreamInput in) throws IOException {
Expand All @@ -37,14 +40,20 @@ public InvalidateApiKeyRequest(StreamInput in) throws IOException {
userName = in.readOptionalString();
id = in.readOptionalString();
name = in.readOptionalString();
if (in.getVersion().onOrAfter(Version.V_7_4_0)) {
ownedByAuthenticatedUser = in.readOptionalBoolean();
} else {
ownedByAuthenticatedUser = false;
}
}

public InvalidateApiKeyRequest(@Nullable String realmName, @Nullable String userName, @Nullable String id,
@Nullable String name) {
@Nullable String name, boolean ownedByAuthenticatedUser) {
this.realmName = realmName;
this.userName = userName;
this.id = id;
this.name = name;
this.ownedByAuthenticatedUser = ownedByAuthenticatedUser;
}

public String getRealmName() {
Expand All @@ -63,65 +72,85 @@ public String getName() {
return name;
}

public boolean ownedByAuthenticatedUser() {
return ownedByAuthenticatedUser;
}

/**
* Creates invalidate api key request for given realm name
*
* @param realmName realm name
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingRealmName(String realmName) {
return new InvalidateApiKeyRequest(realmName, null, null, null);
return new InvalidateApiKeyRequest(realmName, null, null, null, false);
}

/**
* Creates invalidate API key request for given user name
*
* @param userName user name
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingUserName(String userName) {
return new InvalidateApiKeyRequest(null, userName, null, null);
return new InvalidateApiKeyRequest(null, userName, null, null, false);
}

/**
* Creates invalidate API key request for given realm and user name
*
* @param realmName realm name
* @param userName user name
* @param userName user name
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingRealmAndUserName(String realmName, String userName) {
return new InvalidateApiKeyRequest(realmName, userName, null, null);
return new InvalidateApiKeyRequest(realmName, userName, null, null, false);
}

/**
* Creates invalidate API key request for given api key id
*
* @param id api key id
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else
* {@code false}
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingApiKeyId(String id) {
return new InvalidateApiKeyRequest(null, null, id, null);
public static InvalidateApiKeyRequest usingApiKeyId(String id, boolean ownedByAuthenticatedUser) {
return new InvalidateApiKeyRequest(null, null, id, null, ownedByAuthenticatedUser);
}

/**
* Creates invalidate api key request for given api key name
*
* @param name api key name
* @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else
* {@code false}
* @return {@link InvalidateApiKeyRequest}
*/
public static InvalidateApiKeyRequest usingApiKeyName(String name) {
return new InvalidateApiKeyRequest(null, null, null, name);
public static InvalidateApiKeyRequest usingApiKeyName(String name, boolean ownedByAuthenticatedUser) {
return new InvalidateApiKeyRequest(null, null, null, name, ownedByAuthenticatedUser);
}

@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(id) == false
&& Strings.hasText(name) == false) {
validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified",
validationException);
&& Strings.hasText(name) == false && ownedByAuthenticatedUser == false) {
validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified if " +
"[owner] flag is false", validationException);
}
if (Strings.hasText(id) || Strings.hasText(name)) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
validationException = addValidationError(
"username or realm name must not be specified when the api key id or api key name is specified",
validationException);
"username or realm name must not be specified when the api key id or api key name is specified",
validationException);
}
}
if (ownedByAuthenticatedUser) {
if (Strings.hasText(realmName) || Strings.hasText(userName)) {
validationException = addValidationError(
"neither username nor realm-name may be specified when invalidating owned API keys",
validationException);
}
}
if (Strings.hasText(id) && Strings.hasText(name)) {
Expand All @@ -137,5 +166,29 @@ public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalString(userName);
out.writeOptionalString(id);
out.writeOptionalString(name);
if (out.getVersion().onOrAfter(Version.V_7_4_0)) {
out.writeOptionalBoolean(ownedByAuthenticatedUser);
}
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
InvalidateApiKeyRequest that = (InvalidateApiKeyRequest) o;
return ownedByAuthenticatedUser == that.ownedByAuthenticatedUser &&
Objects.equals(realmName, that.realmName) &&
Objects.equals(userName, that.userName) &&
Objects.equals(id, that.id) &&
Objects.equals(name, that.name);
}

@Override
public int hashCode() {
return Objects.hash(realmName, userName, id, name, ownedByAuthenticatedUser);
}
}
Loading

0 comments on commit 522f005

Please sign in to comment.