Skip to content

Commit

Permalink
Expose the invalidation_time field in Get/Query ApiKey APIs (#102472)
Browse files Browse the repository at this point in the history
  • Loading branch information
jfreden authored and timgrein committed Nov 30, 2023
1 parent 85b2a37 commit 59aecf0
Show file tree
Hide file tree
Showing 12 changed files with 295 additions and 87 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/102472.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 102472
summary: Expose the `invalidation` field in Get/Query `ApiKey` APIs
area: Security
type: enhancement
issues: [ ]
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ static TransportVersion def(int id) {
public static final TransportVersion GRANT_API_KEY_CLIENT_AUTHENTICATION_ADDED = def(8_545_00_0);
public static final TransportVersion PIT_WITH_INDEX_FILTER = def(8_546_00_0);
public static final TransportVersion NODE_INFO_VERSION_AS_STRING = def(8_547_00_0);
public static final TransportVersion GET_API_KEY_INVALIDATION_TIME_ADDED = def(8_548_00_0);
/*
* STOP! READ THIS FIRST! No, really,
* ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public String value() {
private final Instant creation;
private final Instant expiration;
private final boolean invalidated;
private final Instant invalidation;
private final String username;
private final String realm;
private final Map<String, Object> metadata;
Expand All @@ -107,6 +108,7 @@ public ApiKey(
Instant creation,
Instant expiration,
boolean invalidated,
@Nullable Instant invalidation,
String username,
String realm,
@Nullable Map<String, Object> metadata,
Expand All @@ -120,6 +122,7 @@ public ApiKey(
creation,
expiration,
invalidated,
invalidation,
username,
realm,
metadata,
Expand All @@ -135,6 +138,7 @@ private ApiKey(
Instant creation,
Instant expiration,
boolean invalidated,
Instant invalidation,
String username,
String realm,
@Nullable Map<String, Object> metadata,
Expand All @@ -150,6 +154,7 @@ private ApiKey(
this.creation = Instant.ofEpochMilli(creation.toEpochMilli());
this.expiration = (expiration != null) ? Instant.ofEpochMilli(expiration.toEpochMilli()) : null;
this.invalidated = invalidated;
this.invalidation = (invalidation != null) ? Instant.ofEpochMilli(invalidation.toEpochMilli()) : null;
this.username = username;
this.realm = realm;
this.metadata = metadata == null ? Map.of() : metadata;
Expand Down Expand Up @@ -177,6 +182,12 @@ public ApiKey(StreamInput in) throws IOException {
this.creation = in.readInstant();
this.expiration = in.readOptionalInstant();
this.invalidated = in.readBoolean();
if (in.getTransportVersion().onOrAfter(TransportVersions.GET_API_KEY_INVALIDATION_TIME_ADDED)) {
this.invalidation = in.readOptionalInstant();
} else {
this.invalidation = null;
}

this.username = in.readString();
this.realm = in.readString();
if (in.getTransportVersion().onOrAfter(TransportVersions.V_8_0_0)) {
Expand Down Expand Up @@ -218,6 +229,10 @@ public boolean isInvalidated() {
return invalidated;
}

public Instant getInvalidation() {
return invalidation;
}

public String getUsername() {
return username;
}
Expand Down Expand Up @@ -252,10 +267,11 @@ public XContentBuilder innerToXContent(XContentBuilder builder, Params params) t
if (expiration != null) {
builder.field("expiration", expiration.toEpochMilli());
}
builder.field("invalidated", invalidated)
.field("username", username)
.field("realm", realm)
.field("metadata", (metadata == null ? Map.of() : metadata));
builder.field("invalidated", invalidated);
if (invalidation != null) {
builder.field("invalidation", invalidation.toEpochMilli());
}
builder.field("username", username).field("realm", realm).field("metadata", (metadata == null ? Map.of() : metadata));
if (roleDescriptors != null) {
builder.startObject("role_descriptors");
for (var roleDescriptor : roleDescriptors) {
Expand Down Expand Up @@ -321,6 +337,9 @@ public void writeTo(StreamOutput out) throws IOException {
out.writeInstant(creation);
out.writeOptionalInstant(expiration);
out.writeBoolean(invalidated);
if (out.getTransportVersion().onOrAfter(TransportVersions.GET_API_KEY_INVALIDATION_TIME_ADDED)) {
out.writeOptionalInstant(invalidation);
}
out.writeString(username);
out.writeString(realm);
if (out.getTransportVersion().onOrAfter(TransportVersions.V_8_0_0)) {
Expand All @@ -334,7 +353,20 @@ public void writeTo(StreamOutput out) throws IOException {

@Override
public int hashCode() {
return Objects.hash(name, id, type, creation, expiration, invalidated, username, realm, metadata, roleDescriptors, limitedBy);
return Objects.hash(
name,
id,
type,
creation,
expiration,
invalidated,
invalidation,
username,
realm,
metadata,
roleDescriptors,
limitedBy
);
}

@Override
Expand All @@ -355,6 +387,7 @@ public boolean equals(Object obj) {
&& Objects.equals(creation, other.creation)
&& Objects.equals(expiration, other.expiration)
&& Objects.equals(invalidated, other.invalidated)
&& Objects.equals(invalidation, other.invalidation)
&& Objects.equals(username, other.username)
&& Objects.equals(realm, other.realm)
&& Objects.equals(metadata, other.metadata)
Expand All @@ -371,11 +404,12 @@ public boolean equals(Object obj) {
Instant.ofEpochMilli((Long) args[3]),
(args[4] == null) ? null : Instant.ofEpochMilli((Long) args[4]),
(Boolean) args[5],
(String) args[6],
(args[6] == null) ? null : Instant.ofEpochMilli((Long) args[6]),
(String) args[7],
(args[8] == null) ? null : (Map<String, Object>) args[8],
(List<RoleDescriptor>) args[9],
(RoleDescriptorsIntersection) args[10]
(String) args[8],
(args[9] == null) ? null : (Map<String, Object>) args[9],
(List<RoleDescriptor>) args[10],
(RoleDescriptorsIntersection) args[11]
);
});
static {
Expand All @@ -385,6 +419,7 @@ public boolean equals(Object obj) {
PARSER.declareLong(constructorArg(), new ParseField("creation"));
PARSER.declareLong(optionalConstructorArg(), new ParseField("expiration"));
PARSER.declareBoolean(constructorArg(), new ParseField("invalidated"));
PARSER.declareLong(optionalConstructorArg(), new ParseField("invalidation"));
PARSER.declareString(constructorArg(), new ParseField("username"));
PARSER.declareString(constructorArg(), new ParseField("realm"));
PARSER.declareObject(optionalConstructorArg(), (p, c) -> p.map(), new ParseField("metadata"));
Expand Down Expand Up @@ -418,6 +453,8 @@ public String toString() {
+ expiration
+ ", invalidated="
+ invalidated
+ ", invalidation="
+ invalidation
+ ", username="
+ username
+ ", realm="
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.apikey;

import org.elasticsearch.TransportVersions;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.core.XPackClientPlugin;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.elasticsearch.xpack.core.security.action.apikey.ApiKeyTests.randomApiKeyInstance;
import static org.hamcrest.Matchers.nullValue;

public class ApiKeySerializationTests extends AbstractWireSerializingTestCase<ApiKey> {

public void testSerializationBackwardsCompatibility() throws IOException {
ApiKey testInstance = createTestInstance();
ApiKey deserializedInstance = copyInstance(testInstance, TransportVersions.V_8_500_064);
try {
// Transport is on a version before invalidation was introduced, so should always be null
assertThat(deserializedInstance.getInvalidation(), nullValue());
} finally {
dispose(deserializedInstance);
}
}

@Override
protected ApiKey createTestInstance() {
return randomApiKeyInstance();
}

@Override
protected ApiKey mutateInstance(ApiKey instance) throws IOException {
ApiKey copyOfInstance = copyInstance(instance);
// Metadata in test instance is mutable, so mutate it instead of the copy (immutable metadata) to make sure they differ
Object metadataNumberValue = instance.getMetadata().getOrDefault("number", Integer.toString(randomInt()));
instance.getMetadata().put("number", Integer.parseInt(metadataNumberValue.toString()) + randomInt());
return copyOfInstance;
}

@Override
protected Writeable.Reader<ApiKey> instanceReader() {
return ApiKey::new;
}

@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(new XPackClientPlugin().getNamedWriteables());
}

public static Map<String, Object> randomMetadata() {
Map<String, Object> randomMetadata = randomFrom(
Map.of(
"application",
randomAlphaOfLength(5),
"number",
1,
"numbers",
List.of(1, 3, 5),
"environment",
Map.of("os", "linux", "level", 42, "category", "trusted")
),
Map.of(randomAlphaOfLengthBetween(3, 8), randomAlphaOfLengthBetween(3, 8)),
Map.of(),
null
);

// Make metadata mutable for testing purposes
return randomMetadata == null ? new HashMap<>() : new HashMap<>(randomMetadata);
}
}

0 comments on commit 59aecf0

Please sign in to comment.