Skip to content

Commit 5677472

Browse files
authored
Manage wls-management-services sessions in exporter (#239)
1 parent 2ba0ff8 commit 5677472

File tree

18 files changed

+605
-113
lines changed

18 files changed

+605
-113
lines changed

pom.xml

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!--
2-
Copyright (c) 2019, 2022, Oracle and/or its affiliates.
2+
Copyright (c) 2019, 2023, Oracle and/or its affiliates.
33
Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
44
-->
55
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
@@ -109,14 +109,26 @@
109109
<plugin>
110110
<artifactId>maven-war-plugin</artifactId>
111111
<version>${maven-war-plugin-version}</version>
112+
<executions>
113+
<execution>
114+
<id>notices</id>
115+
<phase>package</phase>
116+
<goals>
117+
<goal>war</goal>
118+
</goals>
119+
<configuration>
120+
<failOnMissingWebXml>false</failOnMissingWebXml>
121+
<warSourceDirectory>${configuration.directory}</warSourceDirectory>
122+
<webResources>
123+
<resource>
124+
<directory>${project.basedir}/../src/main/notices</directory>
125+
</resource>
126+
</webResources>
127+
</configuration>
128+
</execution>
129+
</executions>
112130
<configuration>
113131
<failOnMissingWebXml>false</failOnMissingWebXml>
114-
<warSourceDirectory>${configuration.directory}</warSourceDirectory>
115-
<webResources>
116-
<resource>
117-
<directory>${project.basedir}/../src/main/notices</directory>
118-
</resource>
119-
</webResources>
120132
</configuration>
121133
</plugin>
122134
<plugin>

wls-exporter-core/src/main/java/com/oracle/wls/exporter/AuthenticatedCall.java

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
1-
// Copyright (c) 2017, 2022, Oracle and/or its affiliates.
1+
// Copyright (c) 2017, 2023, Oracle and/or its affiliates.
22
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
33

44
package com.oracle.wls.exporter;
55

66
import java.io.IOException;
77
import java.io.PrintStream;
8+
import java.time.OffsetDateTime;
9+
import java.util.ArrayList;
10+
import java.util.Collections;
11+
import java.util.HashMap;
812
import java.util.List;
13+
import java.util.Map;
14+
import java.util.Optional;
15+
import java.util.stream.Collectors;
916

1017
import com.oracle.wls.exporter.domain.MBeanSelector;
1118
import com.oracle.wls.exporter.domain.QueryType;
1219

20+
import static com.oracle.wls.exporter.WebAppConstants.AUTHENTICATION_CHALLENGE_HEADER;
21+
import static com.oracle.wls.exporter.WebAppConstants.COOKIE_HEADER;
1322
import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
1423
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
1524
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
@@ -19,10 +28,26 @@
1928
* and the WLS RESTful Management services, thus using that service's security.
2029
*/
2130
public abstract class AuthenticatedCall {
31+
32+
/**
33+
* The length of time in seconds after which a cookie is deemed to have expired.
34+
*/
35+
static final long COOKIE_LIFETIME_SECONDS = 1000L;
36+
37+
/**
38+
* A map of authentication credentials to lists of cookies. The cookies will be sent on any request with
39+
* the specified credentials.
40+
*/
41+
private static final Map<String,List<Cookie>> COOKIES = new HashMap<>();
42+
2243
private final WebClientFactory webClientFactory;
2344
private final InvocationContext context;
2445
private final UrlBuilder urlBuilder;
2546

47+
// For unit testing only
48+
static void clearCookies() {
49+
COOKIES.clear();
50+
}
2651

2752
protected AuthenticatedCall(WebClientFactory webClientFactory, InvocationContext context) {
2853
this.webClientFactory = webClientFactory;
@@ -46,13 +71,63 @@ public WebClient createWebClient() {
4671
final WebClient webClient = webClientFactory.createClient();
4772
webClient.addHeader("X-Requested-By", "rest-exporter");
4873

49-
setAuthentication(webClient);
74+
webClient.setAuthentication(context.getAuthenticationHeader());
75+
manageCookies(webClient);
5076
return webClient;
5177
}
5278

53-
protected void setAuthentication(WebClient webClient) {
54-
webClient.setAuthentication(context.getAuthenticationHeader());
55-
}
79+
private void manageCookies(WebClient webClient) {
80+
synchronized (COOKIES) {
81+
getCookies(context.getAuthenticationHeader()).forEach(c -> webClient.addHeader(COOKIE_HEADER, c));
82+
webClient.onSetCookieReceivedDo(this::handleNewCookie);
83+
webClient.onSetCookieReceivedDo(c -> webClient.addHeader(COOKIE_HEADER, c));
84+
}
85+
}
86+
87+
public List<String> getCookies(String credentials) {
88+
final OffsetDateTime now = SystemClock.now();
89+
final List<Cookie> cookieList = getCookieList(credentials);
90+
cookieList.removeIf(c -> c.isExpiredAt(now));
91+
92+
return cookieList.stream().map(Cookie::getValue).collect(Collectors.toList());
93+
}
94+
95+
private List<Cookie> getCookieList(String credentials) {
96+
return Optional.ofNullable(COOKIES.get(credentials)).orElse(Collections.emptyList());
97+
}
98+
99+
void handleNewCookie(String cookieHeader) {
100+
if (context.getAuthenticationHeader() == null) return;
101+
102+
final Cookie cookie = new Cookie(cookieHeader);
103+
COOKIES
104+
.computeIfAbsent(context.getAuthenticationHeader(), h -> new ArrayList<>())
105+
.add(cookie);
106+
}
107+
108+
private static class Cookie {
109+
private final String value;
110+
private final OffsetDateTime expirationTime = SystemClock.now().plusSeconds(COOKIE_LIFETIME_SECONDS);
111+
112+
Cookie(String cookieHeader) {
113+
this.value = trimParameters(cookieHeader);
114+
}
115+
116+
String getValue() {
117+
return value;
118+
}
119+
120+
boolean isExpiredAt(OffsetDateTime now) {
121+
return now.isAfter(expirationTime);
122+
}
123+
124+
private String trimParameters(String cookieHeader) {
125+
if (!cookieHeader.contains(";"))
126+
return cookieHeader;
127+
else
128+
return cookieHeader.substring(0, cookieHeader.indexOf(';'));
129+
}
130+
}
56131

57132
/**
58133
* Performs a servlet action, wrapping it with authentication handling.
@@ -71,7 +146,7 @@ public void doWithAuthentication() throws IOException {
71146
} catch (ForbiddenException e) {
72147
context.sendError(HTTP_FORBIDDEN, "Not authorized");
73148
} catch (AuthenticationChallengeException e) {
74-
context.setResponseHeader("WWW-Authenticate", e.getChallenge());
149+
context.setResponseHeader(AUTHENTICATION_CHALLENGE_HEADER, e.getChallenge());
75150
context.sendError(HTTP_UNAUTHORIZED, "Authentication required");
76151
} catch (ServerErrorException e) {
77152
final int status = e.getStatus();
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright (c) 2023, Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
4+
package com.oracle.wls.exporter;
5+
6+
import java.time.OffsetDateTime;
7+
8+
/**
9+
* A wrapper for the system clock that facilitates unit testing of time.
10+
*/
11+
public abstract class SystemClock {
12+
13+
// Leave as non-final; unit tests may replace this value
14+
@SuppressWarnings({"FieldMayBeFinal", "CanBeFinal"})
15+
private static SystemClock delegate = new SystemClock() {
16+
@Override
17+
public OffsetDateTime getCurrentTime() {
18+
return OffsetDateTime.now();
19+
}
20+
};
21+
22+
/**
23+
* Returns the current time.
24+
*
25+
* @return a time instance
26+
*/
27+
public static OffsetDateTime now() {
28+
return delegate.getCurrentTime();
29+
}
30+
31+
/**
32+
* Returns the delegate's current time.
33+
*
34+
* @return a time instance
35+
*/
36+
public abstract OffsetDateTime getCurrentTime();
37+
}

wls-exporter-core/src/main/java/com/oracle/wls/exporter/WebAppConstants.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2017, 2021, Oracle and/or its affiliates.
1+
// Copyright (c) 2017, 2023, Oracle and/or its affiliates.
22
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
33

44
package com.oracle.wls.exporter;
@@ -25,17 +25,26 @@ public interface WebAppConstants {
2525
String MESSAGES_PAGE = "messages";
2626
String LOG_PAGE = "log";
2727

28+
/** The header sent by a web server to require authentication. **/
29+
String AUTHENTICATION_CHALLENGE_HEADER = "WWW-Authenticate";
30+
31+
/** The header used by a web server to define a new cookie. **/
32+
String SET_COOKIE_HEADER = "Set-Cookie";
33+
2834
/** The header used by a web client to send its authentication credentials. **/
2935
String AUTHENTICATION_HEADER = "Authorization";
3036

37+
/** The header used by a web client to pass a created cookie to a server. **/
38+
String COOKIE_HEADER = "Cookie";
39+
3140
/** The header used by a web client to specify the content type of its data. **/
3241
String CONTENT_TYPE_HEADER = "Content-Type";
3342

3443
// The field which defines the configuration update action
3544
String EFFECT_OPTION = "effect";
3645

3746
// The possible values for the effect
38-
String DEFAULT_ACTION = WebAppConstants.REPLACE_ACTION;
3947
String REPLACE_ACTION = "replace";
4048
String APPEND_ACTION = "append";
49+
String DEFAULT_ACTION = REPLACE_ACTION;
4150
}

wls-exporter-core/src/main/java/com/oracle/wls/exporter/WebClient.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// Copyright (c) 2017, 2021, Oracle and/or its affiliates.
1+
// Copyright (c) 2017, 2023, Oracle and/or its affiliates.
22
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
33

44
package com.oracle.wls.exporter;
55

66
import java.io.IOException;
7+
import java.util.function.Consumer;
78

89
/**
910
* An interface for an object responsible for sending requests from exporter web application to
@@ -72,4 +73,16 @@ public interface WebClient {
7273
* Returns true if this client has been marked to retry the last request.
7374
*/
7475
boolean isRetryNeeded();
76+
77+
/**
78+
* Defines a handler to be called when a Set-Cookie header is found in a reply from the server.
79+
* @param setCookieHandler the handler to call with the value of the header
80+
*/
81+
void onSetCookieReceivedDo(Consumer<String> setCookieHandler);
82+
83+
84+
interface Response {
85+
String getBody() throws IOException;
86+
87+
}
7588
}

0 commit comments

Comments
 (0)