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
44package com .oracle .wls .exporter ;
55
66import java .io .IOException ;
77import java .io .PrintStream ;
8+ import java .time .OffsetDateTime ;
9+ import java .util .ArrayList ;
10+ import java .util .Collections ;
11+ import java .util .HashMap ;
812import java .util .List ;
13+ import java .util .Map ;
14+ import java .util .Optional ;
15+ import java .util .stream .Collectors ;
916
1017import com .oracle .wls .exporter .domain .MBeanSelector ;
1118import 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 ;
1322import static java .net .HttpURLConnection .HTTP_FORBIDDEN ;
1423import static java .net .HttpURLConnection .HTTP_INTERNAL_ERROR ;
1524import static java .net .HttpURLConnection .HTTP_UNAUTHORIZED ;
1928 * and the WLS RESTful Management services, thus using that service's security.
2029 */
2130public 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 ();
0 commit comments