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