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.OpenIdTokenResponse;
015import ca.cdr.api.model.json.oauth.OpenIdWellKnownOpenIdConfigurationResponse;
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 OpenIdTokenResponse myAccessToken;
049
050        /** Access token expiration */
051        private long myTokenExpiresInSeconds;
052
053        private OpenIdWellKnownOpenIdConfigurationResponse 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 OpenIdTokenResponse getAccessToken() {
090                return myAccessToken;
091        }
092
093        public void setAccessToken(OpenIdTokenResponse 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 url) {
106                try {
107                        URIBuilder builder = new URIBuilder(url);
108                        myTargetServiceBase = builder.setPath("").build().toString();
109                } catch (URISyntaxException e) {
110                        throw new IllegalArgumentException("Invalid URL: " + url, e);
111                }
112        }
113
114        public String getClientId() {
115                return myParams.getClientId();
116        }
117
118        public void setTokenExpiresInSeconds(long theExpSecs) {
119                myTokenExpiresInSeconds = theExpSecs;
120        }
121
122        public List<BasicNameValuePair> getCustomTokenRequestParams() {
123                return myParams.getCustomTokenRequestParams();
124        }
125
126        public String getScope() {
127                return myParams.getScope();
128        }
129
130        public void setWellKnownConfig(OpenIdWellKnownOpenIdConfigurationResponse theWellKnownResponse) {
131                myWellKnownResponse = theWellKnownResponse;
132        }
133
134        public String getClientSecret() {
135                return myParams.getClientSecret();
136        }
137
138        // casting to Object to avoid publishing ITlsComponentConfig
139        public Object getClientTlsConfigObject() {
140                return myParams.getClientTlsConfig();
141        }
142}