001/*-
002 * #%L
003 * Smile CDR - CDR
004 * %%
005 * Copyright (C) 2016 - 2025 Smile CDR, Inc.
006 * %%
007 * All rights reserved.
008 * #L%
009 */
010package ca.cdr.api.security;
011
012import ca.cdr.api.annotations.CdrPublicAPI;
013import ca.cdr.api.log.Logs;
014import ca.cdr.api.model.json.OpenIdTokenResponseJson;
015import ca.cdr.api.model.json.oauth.OpenIdWellKnownOpenIdConfigurationResponseJson;
016import jakarta.annotation.Nonnull;
017import org.apache.http.client.utils.URIBuilder;
018import org.apache.http.message.BasicNameValuePair;
019import org.slf4j.Logger;
020
021import java.net.URISyntaxException;
022import java.time.Instant;
023import java.util.List;
024
025/**
026 * Keeps authentication state properties. After initialization only one of myJwt or mySecret properties
027 * must be valid depending on the authorization flow to be used:
028 * <ul><li>myJwt for private_key_jwt workflow</li>
029 * <li>mySecret for client_secret workflow</li></ul>
030 */
031@CdrPublicAPI
032public class ClientAuthState {
033        private static final Logger ourLog = Logs.getSecurityTroubleshootingLog();
034
035        // safe margin to make sure that token doesn't expire while the request is sent
036        static final long SAFE_EXPIRATION_MARGIN_SECS = 2;
037
038        private final ClientAuthParams myParams;
039
040        /**
041         * Url of the target service which will authenticate the access token
042         */
043        private String myTargetServiceBase;
044
045        /**
046         * Access token, once obtained
047         */
048        private OpenIdTokenResponseJson myAccessToken;
049
050        /** Access token expiration */
051        private long myTokenExpiresInSeconds;
052
053        private OpenIdWellKnownOpenIdConfigurationResponseJson myWellKnownResponse;
054
055        public ClientAuthState(@Nonnull ClientAuthParams theParameters) {
056                myParams = theParameters;
057        }
058
059        public boolean isAccessTokenExpired() {
060                long secondsNow = Instant.now().getEpochSecond();
061
062                ourLog.atDebug()
063                                .setMessage(
064                                                """
065                                
066                                myTokenExpiresAt                        : {}
067                                Now                                     : {}
068                                SAFE_EXPIRATION_MARGIN_SECS             : {}
069                                secondsNow + SAFE_EXPIRATION_MARGIN_SECS: {}
070                                token is {}expired""")
071                                .addArgument(myTokenExpiresInSeconds)
072                                .addArgument(secondsNow)
073                                .addArgument(String.format("%10s", SAFE_EXPIRATION_MARGIN_SECS))
074                                .addArgument(secondsNow + SAFE_EXPIRATION_MARGIN_SECS)
075                                .addArgument(myTokenExpiresInSeconds < secondsNow + SAFE_EXPIRATION_MARGIN_SECS ? "" : "not ")
076                                .log();
077
078                return myTokenExpiresInSeconds < secondsNow + SAFE_EXPIRATION_MARGIN_SECS;
079        }
080
081        public boolean isForceHttpInTokenRequestAudience() {
082                return myParams.isForceHttpInTokenRequestAudience();
083        }
084
085        public String getKeystoreName() {
086                return myParams.getKeystoreName();
087        }
088
089        public OpenIdTokenResponseJson getAccessToken() {
090                return myAccessToken;
091        }
092
093        public void setAccessToken(OpenIdTokenResponseJson theToken) {
094                myAccessToken = theToken;
095        }
096
097        public String getTokenEndpoint() {
098                return myWellKnownResponse == null ? null : myWellKnownResponse.getTokenEndpoint();
099        }
100
101        public String getTargetServiceBase() {
102                return myTargetServiceBase;
103        }
104
105        public void setTargetServiceBase(@Nonnull String theUrl) {
106                String baseUrl = myParams.getBaseUrl() != null ? myParams.getBaseUrl() : theUrl;
107                try {
108                        URIBuilder builder = new URIBuilder(baseUrl);
109                        myTargetServiceBase = builder.setPath("").build().toString();
110                } catch (URISyntaxException e) {
111                        throw new IllegalArgumentException("Invalid URL: " + baseUrl, e);
112                }
113        }
114
115        public String getClientId() {
116                return myParams.getClientId();
117        }
118
119        public void setTokenExpiresInSeconds(long theExpSecs) {
120                myTokenExpiresInSeconds = theExpSecs;
121        }
122
123        public List<BasicNameValuePair> getCustomTokenRequestParams() {
124                return myParams.getCustomTokenRequestParams();
125        }
126
127        public String getScope() {
128                return myParams.getScope();
129        }
130
131        public void setWellKnownConfig(OpenIdWellKnownOpenIdConfigurationResponseJson theWellKnownResponse) {
132                myWellKnownResponse = theWellKnownResponse;
133        }
134
135        public String getClientSecret() {
136                return myParams.getClientSecret();
137        }
138
139        // casting to Object to avoid publishing ITlsComponentConfig
140        public Object getClientTlsConfigObject() {
141                return myParams.getClientTlsConfig();
142        }
143}