Do not require after in audit events endpoint

Closes gh-11605
pull/11674/head
Andy Wilkinson 7 years ago
parent c233125f1d
commit 849baa4c02

@ -25,14 +25,11 @@ include::{snippets}auditevents/filtered/http-response.adoc[]
=== Query Parameters === Query Parameters
The endpoint uses query parameters to limit the events that it returns. The following The endpoint uses query parameters to limit the events that it returns. The following
table shows the supported query parameters: table shows the supported query parameters:
[cols="2,4"] [cols="2,4"]
include::{snippets}auditevents/filtered/request-parameters.adoc[] include::{snippets}auditevents/filtered/request-parameters.adoc[]
The `after` parameter is required. You can also use one or both of the `principal` and
`type` parameters to further limit the results.
[[audit-events-retrieving-response-structure]] [[audit-events-retrieving-response-structure]]
@ -42,4 +39,4 @@ The response contains details of all of the audit events that matched the query.
following table describes the structure of the response: following table describes the structure of the response:
[cols="2,1,3"] [cols="2,1,3"]
include::{snippets}auditevents/after/response-fields.adoc[] include::{snippets}auditevents/all/response-fields.adoc[]

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,8 +18,6 @@ package org.springframework.boot.actuate.autoconfigure.audit;
import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint; import org.springframework.boot.actuate.audit.AuditEventsEndpoint;
import org.springframework.boot.actuate.audit.AuditEventsEndpointWebExtension;
import org.springframework.boot.actuate.audit.AuditEventsJmxEndpointExtension;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.logging.LoggersEndpoint; import org.springframework.boot.actuate.logging.LoggersEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -50,22 +48,4 @@ public class AuditEventsEndpointAutoConfiguration {
return new AuditEventsEndpoint(auditEventRepository); return new AuditEventsEndpoint(auditEventRepository);
} }
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(AuditEventsEndpoint.class)
public AuditEventsJmxEndpointExtension auditEventsJmxEndpointExtension(
AuditEventsEndpoint auditEventsEndpoint) {
return new AuditEventsJmxEndpointExtension(auditEventsEndpoint);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnEnabledEndpoint
@ConditionalOnBean(AuditEventsEndpoint.class)
public AuditEventsEndpointWebExtension auditEventsWebEndpointExtension(
AuditEventsEndpoint auditEventsEndpoint) {
return new AuditEventsEndpointWebExtension(auditEventsEndpoint);
}
} }

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2017 the original author or authors. * Copyright 2012-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,8 +19,6 @@ package org.springframework.boot.actuate.autoconfigure.audit;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint; import org.springframework.boot.actuate.audit.AuditEventsEndpoint;
import org.springframework.boot.actuate.audit.AuditEventsEndpointWebExtension;
import org.springframework.boot.actuate.audit.AuditEventsJmxEndpointExtension;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@ -46,25 +44,11 @@ public class AuditEventsEndpointAutoConfigurationTests {
} }
@Test @Test
public void runShouldHaveJmxExtensionBean() { public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpoint() {
this.contextRunner.run((context) -> assertThat(context)
.hasSingleBean(AuditEventsJmxEndpointExtension.class));
}
@Test
public void runShouldHaveWebExtensionBean() {
this.contextRunner.run((context) -> assertThat(context)
.hasSingleBean(AuditEventsEndpointWebExtension.class));
}
@Test
public void runWhenEnabledPropertyIsFalseShouldNotHaveEndpointOrExtensionBean() {
this.contextRunner this.contextRunner
.withPropertyValues("management.endpoint.auditevents.enabled:false") .withPropertyValues("management.endpoint.auditevents.enabled:false")
.run((context) -> assertThat(context) .run((context) -> assertThat(context)
.doesNotHaveBean(AuditEventsEndpoint.class) .doesNotHaveBean(AuditEventsEndpoint.class));
.doesNotHaveBean(AuditEventsJmxEndpointExtension.class)
.doesNotHaveBean(AuditEventsEndpointWebExtension.class));
} }
} }

@ -26,7 +26,6 @@ import org.junit.Test;
import org.springframework.boot.actuate.audit.AuditEvent; import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository; import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint; import org.springframework.boot.actuate.audit.AuditEventsEndpoint;
import org.springframework.boot.actuate.audit.AuditEventsEndpointWebExtension;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -55,13 +54,13 @@ public class AuditEventsEndpointDocumentationTests
private AuditEventRepository repository; private AuditEventRepository repository;
@Test @Test
public void allAuditEventsAfter() throws Exception { public void allAuditEvents() throws Exception {
String queryTimestamp = "2017-11-07T09:37Z"; String queryTimestamp = "2017-11-07T09:37Z";
given(this.repository.find(any(), any(), any())).willReturn( given(this.repository.find(any(), any(), any())).willReturn(
Arrays.asList(new AuditEvent("alice", "logout", Collections.emptyMap()))); Arrays.asList(new AuditEvent("alice", "logout", Collections.emptyMap())));
this.mockMvc.perform(get("/actuator/auditevents").param("after", queryTimestamp)) this.mockMvc.perform(get("/actuator/auditevents").param("after", queryTimestamp))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andDo(document("auditevents/after", responseFields( .andDo(document("auditevents/all", responseFields(
fieldWithPath("events").description("An array of audit events."), fieldWithPath("events").description("An array of audit events."),
fieldWithPath("events.[].timestamp") fieldWithPath("events.[].timestamp")
.description("The timestamp of when the event occurred."), .description("The timestamp of when the event occurred."),
@ -85,7 +84,7 @@ public class AuditEventsEndpointDocumentationTests
requestParameters( requestParameters(
parameterWithName("after").description( parameterWithName("after").description(
"Restricts the events to those that occurred " "Restricts the events to those that occurred "
+ "after the given time. Required."), + "after the given time. Optional."),
parameterWithName("principal").description( parameterWithName("principal").description(
"Restricts the events to those with the given " "Restricts the events to those with the given "
+ "principal. Optional."), + "principal. Optional."),
@ -104,12 +103,6 @@ public class AuditEventsEndpointDocumentationTests
return new AuditEventsEndpoint(repository); return new AuditEventsEndpoint(repository);
} }
@Bean
public AuditEventsEndpointWebExtension adAuditEventsWebEndpointExtension(
AuditEventsEndpoint delegate) {
return new AuditEventsEndpointWebExtension(delegate);
}
} }
} }

@ -21,6 +21,7 @@ import java.util.List;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -40,8 +41,8 @@ public class AuditEventsEndpoint {
} }
@ReadOperation @ReadOperation
public AuditEventsDescriptor events(String principal, OffsetDateTime after, public AuditEventsDescriptor events(@Nullable String principal,
String type) { @Nullable OffsetDateTime after, @Nullable String type) {
return new AuditEventsDescriptor(this.auditEventRepository.find(principal, return new AuditEventsDescriptor(this.auditEventRepository.find(principal,
after == null ? null : after.toInstant(), type)); after == null ? null : after.toInstant(), type));
} }

@ -1,49 +0,0 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.audit;
import java.time.OffsetDateTime;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint.AuditEventsDescriptor;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension;
import org.springframework.lang.Nullable;
/**
* {@link EndpointWebExtension} for the {@link AuditEventsEndpoint}.
*
* @author Vedran Pavic
* @since 2.0.0
*/
@EndpointWebExtension(endpoint = AuditEventsEndpoint.class)
public class AuditEventsEndpointWebExtension {
private final AuditEventsEndpoint delegate;
public AuditEventsEndpointWebExtension(AuditEventsEndpoint delegate) {
this.delegate = delegate;
}
@ReadOperation
public WebEndpointResponse<AuditEventsDescriptor> events(@Nullable String principal,
OffsetDateTime after, @Nullable String type) {
AuditEventsDescriptor auditEvents = this.delegate.events(principal, after, type);
return new WebEndpointResponse<>(auditEvents);
}
}

@ -1,52 +0,0 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.audit;
import java.time.OffsetDateTime;
import org.springframework.boot.actuate.audit.AuditEventsEndpoint.AuditEventsDescriptor;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.jmx.annotation.EndpointJmxExtension;
/**
* JMX-specific extension of the {@link AuditEventsEndpoint}.
*
* @author Vedran Pavic
* @author Andy Wilkinson
* @since 2.0.0
*/
@EndpointJmxExtension(endpoint = AuditEventsEndpoint.class)
public class AuditEventsJmxEndpointExtension {
private final AuditEventsEndpoint delegate;
public AuditEventsJmxEndpointExtension(AuditEventsEndpoint delegate) {
this.delegate = delegate;
}
@ReadOperation
public AuditEventsDescriptor eventsAfter(OffsetDateTime after) {
return this.delegate.events(null, after, null);
}
@ReadOperation
public AuditEventsDescriptor eventsWithPrincipalAndAfter(String principal,
OffsetDateTime after) {
return this.delegate.events(principal, after, null);
}
}

@ -41,13 +41,16 @@ public class AuditEventsEndpointWebIntegrationTests {
private static WebTestClient client; private static WebTestClient client;
@Test @Test
public void eventsWithoutParams() { public void allEvents() {
client.get().uri((builder) -> builder.path("/actuator/auditevents").build()) client.get().uri((builder) -> builder.path("/actuator/auditevents").build())
.exchange().expectStatus().isBadRequest(); .exchange().expectStatus().isOk().expectBody()
.jsonPath("events.[*].principal")
.isEqualTo(new JSONArray().appendElement("admin").appendElement("admin")
.appendElement("user"));
} }
@Test @Test
public void eventsWithDateAfter() { public void eventsAfter() {
client.get() client.get()
.uri((builder) -> builder.path("/actuator/auditevents") .uri((builder) -> builder.path("/actuator/auditevents")
.queryParam("after", "2016-11-01T13:00:00%2B00:00").build()) .queryParam("after", "2016-11-01T13:00:00%2B00:00").build())
@ -56,10 +59,9 @@ public class AuditEventsEndpointWebIntegrationTests {
} }
@Test @Test
public void eventsWithPrincipalAndDateAfter() { public void eventsWithPrincipal() {
client.get() client.get()
.uri((builder) -> builder.path("/actuator/auditevents") .uri((builder) -> builder.path("/actuator/auditevents")
.queryParam("after", "2016-11-01T10:00:00%2B00:00")
.queryParam("principal", "user").build()) .queryParam("principal", "user").build())
.exchange().expectStatus().isOk().expectBody() .exchange().expectStatus().isOk().expectBody()
.jsonPath("events.[*].principal") .jsonPath("events.[*].principal")
@ -67,12 +69,10 @@ public class AuditEventsEndpointWebIntegrationTests {
} }
@Test @Test
public void eventsWithPrincipalDateAfterAndType() { public void eventsWithType() {
client.get() client.get()
.uri((builder) -> builder.path("/actuator/auditevents") .uri((builder) -> builder.path("/actuator/auditevents")
.queryParam("after", "2016-11-01T10:00:00%2B00:00") .queryParam("type", "logout").build())
.queryParam("principal", "admin").queryParam("type", "logout")
.build())
.exchange().expectStatus().isOk().expectBody() .exchange().expectStatus().isOk().expectBody()
.jsonPath("events.[*].principal") .jsonPath("events.[*].principal")
.isEqualTo(new JSONArray().appendElement("admin")) .isEqualTo(new JSONArray().appendElement("admin"))
@ -97,12 +97,6 @@ public class AuditEventsEndpointWebIntegrationTests {
return new AuditEventsEndpoint(auditEventsRepository()); return new AuditEventsEndpoint(auditEventsRepository());
} }
@Bean
public AuditEventsEndpointWebExtension auditEventsEndpointWebExtension(
AuditEventsEndpoint auditEventsEndpoint) {
return new AuditEventsEndpointWebExtension(auditEventsEndpoint);
}
private AuditEvent createEvent(String instant, String principal, String type) { private AuditEvent createEvent(String instant, String principal, String type) {
return new AuditEvent(Instant.parse(instant), principal, type, return new AuditEvent(Instant.parse(instant), principal, type,
Collections.emptyMap()); Collections.emptyMap());

@ -1,63 +0,0 @@
/*
* Copyright 2012-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.actuate.audit;
import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AuditEventsJmxEndpointExtension}.
*
* @author Andy Wilkinson
*/
public class AuditEventsJmxEndpointExtensionTests {
private final AuditEventRepository repository = mock(AuditEventRepository.class);
private final AuditEventsJmxEndpointExtension extension = new AuditEventsJmxEndpointExtension(
new AuditEventsEndpoint(this.repository));
private final AuditEvent event = new AuditEvent("principal", "type",
Collections.singletonMap("a", "alpha"));
@Test
public void eventsCreatedAfter() {
OffsetDateTime now = OffsetDateTime.now();
given(this.repository.find(null, now.toInstant(), null))
.willReturn(Collections.singletonList(this.event));
List<AuditEvent> result = this.extension.eventsAfter(now).getEvents();
assertThat(result).isEqualTo(Collections.singletonList(this.event));
}
@Test
public void eventsWithPrincipalAndDateAfter() {
OffsetDateTime now = OffsetDateTime.now();
given(this.repository.find("Joan", now.toInstant(), null))
.willReturn(Collections.singletonList(this.event));
List<AuditEvent> result = this.extension.eventsWithPrincipalAndAfter("Joan", now)
.getEvents();
assertThat(result).isEqualTo(Collections.singletonList(this.event));
}
}
Loading…
Cancel
Save