|
| 1 | +/** |
| 2 | + * @description Service class for calling Altinn APIs. |
| 3 | + * |
| 4 | + * @author Frode Hoen <[email protected]> |
| 5 | + * @since 2021-04-07 Created. |
| 6 | + * @author Kenneth Soerensen <[email protected]> |
| 7 | + * @since 2023-11-13 Added token caching. |
| 8 | + * |
| 9 | + * @see https://docs.altinn.studio/api/ |
| 10 | + * @see AltinnCalloutServiceTest |
| 11 | + * |
| 12 | + * @group Altinn |
| 13 | + */ |
1 | 14 | public with sharing class AltinnCalloutService {
|
2 | 15 | private final string API_CONFIG_NAME = 'ALTINN_API'; //API Config name to set when using the ApiController
|
| 16 | + @TestVisible |
3 | 17 | private static final String TEST_SERVICE_TOKEN_RESPONSE =
|
4 | 18 | '{"access_token": "TEST_ACCESS", "expires": ' +
|
5 | 19 | JSON.serialize(Datetime.now().addSeconds(3600)) +
|
@@ -103,43 +117,100 @@ public with sharing class AltinnCalloutService {
|
103 | 117 | return errorRef;
|
104 | 118 | }
|
105 | 119 |
|
| 120 | + /** |
| 121 | + * @description Gets the access token from the cache. |
| 122 | + * If the token is not in the cache, it will be requested from Maskinporten. |
| 123 | + * Note: This method is not the final method for retrieving the access token. |
| 124 | + * |
| 125 | + * @return `String` The access token |
| 126 | + */ |
| 127 | + @TestVisible |
106 | 128 | private string getMaskinportenToken() {
|
107 |
| - String clientDeveloperName = Test.isRunningTest() ? 'salesforce_altinn_test' : 'salesforce_altinn'; |
108 |
| - String clientName = clientDeveloperName + '_tokendata'; |
| 129 | + String clientName = 'altinntokendata'; |
| 130 | + |
| 131 | + LoggerUtility logger = new LoggerUtility(); |
109 | 132 |
|
110 | 133 | Cache.OrgPartition orgPartition = Cache.Org.getPartition('local.tokens');
|
111 |
| - String tokenData = Test.isRunningTest() |
| 134 | + /*String tokenData = Test.isRunningTest() |
112 | 135 | ? CryptoService.encryptString(TEST_SERVICE_TOKEN_RESPONSE)
|
113 |
| - : (String) orgPartition.get(clientName); |
| 136 | + : (String) orgPartition.get(clientName);*/ |
| 137 | + String tokenData = (String) orgPartition.get(clientName); |
114 | 138 |
|
115 | 139 | if (String.isNotBlank(tokenData)) {
|
116 | 140 | String decryptedTokenData = CryptoService.decryptString(tokenData);
|
117 | 141 | Map<String, Object> tokenDataMap = (Map<String, Object>) JSON.deserializeUntyped(decryptedTokenData);
|
| 142 | + Datetime createdAt = (DateTime)JSON.deserialize('"' + tokenDataMap.get('created_at') + '"', DateTime.class); |
| 143 | + System.debug('Token created at: ' + createdAt); |
| 144 | + Datetime expiresAt = createdAt.addSeconds((Integer) tokenDataMap.get('expires_in')); |
| 145 | + Datetime timeNow = Datetime.now(); |
| 146 | + |
| 147 | + if (expiresAt < timeNow) { |
| 148 | + System.debug('Token expired, requesting new token'); |
| 149 | + logger.info('Token expired, requesting new token', null); |
| 150 | + accessToken = this.refreshTokenCache(); |
| 151 | + } |
| 152 | + System.debug('Token not expired, using existing token'); |
| 153 | + logger.info('Token not expired, using existing token', null); |
118 | 154 | accessToken = (String) tokenDataMap.get('access_token');
|
| 155 | + |
119 | 156 | } else {
|
120 |
| - MaskinportenService service = new MaskinportenService(clientDeveloperName); |
| 157 | + |
121 | 158 | if (accessToken == null) {
|
122 |
| - |
123 |
| - MaskinportenService.AuthResponse response = service.doMaskinportenJWTExchange(); |
124 |
| - if (response == null) { |
125 |
| - return null; |
126 |
| - } |
127 |
| - accessToken = response.access_token; |
128 |
| - Integer expiresIn = response.expires_in; |
129 |
| - |
130 |
| - orgPartition.put( |
131 |
| - clientName, |
132 |
| - CryptoService.encryptString(accessToken), |
133 |
| - expiresIn |
134 |
| - ); |
| 159 | + System.debug('No token in cache, requesting new token'); |
| 160 | + logger.info('No token in cache, requesting new token', null); |
| 161 | + accessToken = this.refreshTokenCache(); |
135 | 162 | }
|
136 | 163 | }
|
137 | 164 |
|
138 |
| - |
| 165 | + logger.publish(); |
139 | 166 | return accessToken;
|
140 | 167 | }
|
141 | 168 |
|
| 169 | + /** |
| 170 | + * @description Refreshes the token cache. |
| 171 | + * Note: This method is not the final method for refreshing the token cache. |
| 172 | + * |
| 173 | + * @return `String` The access token |
| 174 | + */ |
| 175 | + @TestVisible |
| 176 | + private String refreshTokenCache() { |
| 177 | + String clientDeveloperName = Test.isRunningTest() ? 'salesforce_altinn_test' : 'salesforce_altinn'; |
| 178 | + String clientName = 'altinntokendata'; |
| 179 | + LoggerUtility logger = new LoggerUtility(); |
| 180 | + |
| 181 | + Cache.OrgPartition orgPartition = Cache.Org.getPartition('local.tokens'); |
| 182 | + MaskinportenService service = new MaskinportenService(clientDeveloperName); |
| 183 | + |
| 184 | + MaskinportenService.AuthResponse testResponse = new MaskinportenService.AuthResponse(); |
| 185 | + testResponse.access_token = 'access_token_test'; |
| 186 | + testResponse.expires_in = 119; |
| 187 | + testResponse.created_at = Datetime.now(); |
| 188 | + testResponse.scope = 'test_scope'; |
142 | 189 |
|
| 190 | + MaskinportenService.AuthResponse response = Test.isRunningTest() |
| 191 | + ? testResponse |
| 192 | + : service.doMaskinportenJWTExchange(); |
| 193 | + |
| 194 | + if (response == null) { |
| 195 | + return null; |
| 196 | + } |
| 197 | + accessToken = response.access_token; |
| 198 | + Integer expiresIn = 300;//response.expires_in; |
| 199 | + |
| 200 | + try { |
| 201 | + orgPartition.put( |
| 202 | + clientName, |
| 203 | + CryptoService.encryptString(JSON.serialize(response)), |
| 204 | + expiresIn |
| 205 | + ); |
| 206 | + } catch (Exception ex) { |
| 207 | + logger.error('Error putting info into cache.' + '\n ' + 'Exception: ' + ex, null); |
| 208 | + System.debug('Error putting info into cache.' + '\n ' + 'Exception: ' + ex); |
| 209 | + } |
| 210 | + |
| 211 | + |
| 212 | + return accessToken; |
| 213 | + } |
143 | 214 |
|
144 | 215 | // RESPONSE WRAPPERS
|
145 | 216 | public class AltinnOrganizationsResponse {
|
|
0 commit comments