Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
Copyright (c) 2019, 2022, Oracle and/or its affiliates.
Copyright (c) 2019, 2023, Oracle and/or its affiliates.
Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
-->
<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">
Expand Down Expand Up @@ -109,14 +109,26 @@
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>${maven-war-plugin-version}</version>
<executions>
<execution>
<id>notices</id>
<phase>package</phase>
<goals>
<goal>war</goal>
</goals>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warSourceDirectory>${configuration.directory}</warSourceDirectory>
<webResources>
<resource>
<directory>${project.basedir}/../src/main/notices</directory>
</resource>
</webResources>
</configuration>
</execution>
</executions>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warSourceDirectory>${configuration.directory}</warSourceDirectory>
<webResources>
<resource>
<directory>${project.basedir}/../src/main/notices</directory>
</resource>
</webResources>
</configuration>
</plugin>
<plugin>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
// Copyright (c) 2017, 2022, Oracle and/or its affiliates.
// Copyright (c) 2017, 2023, Oracle and/or its affiliates.
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.

package com.oracle.wls.exporter;

import java.io.IOException;
import java.io.PrintStream;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import com.oracle.wls.exporter.domain.MBeanSelector;
import com.oracle.wls.exporter.domain.QueryType;

import static com.oracle.wls.exporter.WebAppConstants.AUTHENTICATION_CHALLENGE_HEADER;
import static com.oracle.wls.exporter.WebAppConstants.COOKIE_HEADER;
import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
Expand All @@ -19,10 +28,26 @@
* and the WLS RESTful Management services, thus using that service's security.
*/
public abstract class AuthenticatedCall {

/**
* The length of time in seconds after which a cookie is deemed to have expired.
*/
static final long COOKIE_LIFETIME_SECONDS = 1000L;

/**
* A map of authentication credentials to lists of cookies. The cookies will be sent on any request with
* the specified credentials.
*/
private static final Map<String,List<Cookie>> COOKIES = new HashMap<>();

private final WebClientFactory webClientFactory;
private final InvocationContext context;
private final UrlBuilder urlBuilder;

// For unit testing only
static void clearCookies() {
COOKIES.clear();
}

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

setAuthentication(webClient);
webClient.setAuthentication(context.getAuthenticationHeader());
manageCookies(webClient);
return webClient;
}

protected void setAuthentication(WebClient webClient) {
webClient.setAuthentication(context.getAuthenticationHeader());
}
private void manageCookies(WebClient webClient) {
synchronized (COOKIES) {
getCookies(context.getAuthenticationHeader()).forEach(c -> webClient.addHeader(COOKIE_HEADER, c));
webClient.onSetCookieReceivedDo(this::handleNewCookie);
webClient.onSetCookieReceivedDo(c -> webClient.addHeader(COOKIE_HEADER, c));
}
}

public List<String> getCookies(String credentials) {
final OffsetDateTime now = SystemClock.now();
final List<Cookie> cookieList = getCookieList(credentials);
cookieList.removeIf(c -> c.isExpiredAt(now));

return cookieList.stream().map(Cookie::getValue).collect(Collectors.toList());
}

private List<Cookie> getCookieList(String credentials) {
return Optional.ofNullable(COOKIES.get(credentials)).orElse(Collections.emptyList());
}

void handleNewCookie(String cookieHeader) {
if (context.getAuthenticationHeader() == null) return;

final Cookie cookie = new Cookie(cookieHeader);
COOKIES
.computeIfAbsent(context.getAuthenticationHeader(), h -> new ArrayList<>())
.add(cookie);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You would need to synchronize around the add too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, ignore... This is called under the other synchronized block.

}

private static class Cookie {
private final String value;
private final OffsetDateTime expirationTime = SystemClock.now().plusSeconds(COOKIE_LIFETIME_SECONDS);

Cookie(String cookieHeader) {
this.value = trimParameters(cookieHeader);
}

String getValue() {
return value;
}

boolean isExpiredAt(OffsetDateTime now) {
return now.isAfter(expirationTime);
}

private String trimParameters(String cookieHeader) {
if (!cookieHeader.contains(";"))
return cookieHeader;
else
return cookieHeader.substring(0, cookieHeader.indexOf(';'));
}
}

/**
* Performs a servlet action, wrapping it with authentication handling.
Expand All @@ -71,7 +146,7 @@ public void doWithAuthentication() throws IOException {
} catch (ForbiddenException e) {
context.sendError(HTTP_FORBIDDEN, "Not authorized");
} catch (AuthenticationChallengeException e) {
context.setResponseHeader("WWW-Authenticate", e.getChallenge());
context.setResponseHeader(AUTHENTICATION_CHALLENGE_HEADER, e.getChallenge());
context.sendError(HTTP_UNAUTHORIZED, "Authentication required");
} catch (ServerErrorException e) {
final int status = e.getStatus();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) 2023, Oracle and/or its affiliates.
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.

package com.oracle.wls.exporter;

import java.time.OffsetDateTime;

/**
* A wrapper for the system clock that facilitates unit testing of time.
*/
public abstract class SystemClock {

// Leave as non-final; unit tests may replace this value
@SuppressWarnings({"FieldMayBeFinal", "CanBeFinal"})
private static SystemClock delegate = new SystemClock() {
@Override
public OffsetDateTime getCurrentTime() {
return OffsetDateTime.now();
}
};

/**
* Returns the current time.
*
* @return a time instance
*/
public static OffsetDateTime now() {
return delegate.getCurrentTime();
}

/**
* Returns the delegate's current time.
*
* @return a time instance
*/
public abstract OffsetDateTime getCurrentTime();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2017, 2021, Oracle and/or its affiliates.
// Copyright (c) 2017, 2023, Oracle and/or its affiliates.
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.

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

/** The header sent by a web server to require authentication. **/
String AUTHENTICATION_CHALLENGE_HEADER = "WWW-Authenticate";

/** The header used by a web server to define a new cookie. **/
String SET_COOKIE_HEADER = "Set-Cookie";

/** The header used by a web client to send its authentication credentials. **/
String AUTHENTICATION_HEADER = "Authorization";

/** The header used by a web client to pass a created cookie to a server. **/
String COOKIE_HEADER = "Cookie";

/** The header used by a web client to specify the content type of its data. **/
String CONTENT_TYPE_HEADER = "Content-Type";

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

// The possible values for the effect
String DEFAULT_ACTION = WebAppConstants.REPLACE_ACTION;
String REPLACE_ACTION = "replace";
String APPEND_ACTION = "append";
String DEFAULT_ACTION = REPLACE_ACTION;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright (c) 2017, 2021, Oracle and/or its affiliates.
// Copyright (c) 2017, 2023, Oracle and/or its affiliates.
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.

package com.oracle.wls.exporter;

import java.io.IOException;
import java.util.function.Consumer;

/**
* An interface for an object responsible for sending requests from exporter web application to
Expand Down Expand Up @@ -72,4 +73,16 @@ public interface WebClient {
* Returns true if this client has been marked to retry the last request.
*/
boolean isRetryNeeded();

/**
* Defines a handler to be called when a Set-Cookie header is found in a reply from the server.
* @param setCookieHandler the handler to call with the value of the header
*/
void onSetCookieReceivedDo(Consumer<String> setCookieHandler);


interface Response {
String getBody() throws IOException;

}
}
Loading