Skip to content

Commit 0df3cea

Browse files
committed
Modified platform cache handling for Altinn token.
1 parent fd85e0c commit 0df3cea

File tree

5 files changed

+165
-23
lines changed

5 files changed

+165
-23
lines changed

.vscode/extensions.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
"aaron-bond.better-comments",
55
"ChakrounAnas.turbo-console-log",
66
"dbaeumer.vscode-eslint",
7-
"donjayamanne.githistory",
87
"eamodio.gitlens",
98
"hnw.vscode-auto-open-markdown-preview",
109
"IBM.output-colorizer",

force-app/altinn/classes/AltinnCalloutService.cls

Lines changed: 90 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
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+
*/
114
public with sharing class AltinnCalloutService {
215
private final string API_CONFIG_NAME = 'ALTINN_API'; //API Config name to set when using the ApiController
16+
@TestVisible
317
private static final String TEST_SERVICE_TOKEN_RESPONSE =
418
'{"access_token": "TEST_ACCESS", "expires": ' +
519
JSON.serialize(Datetime.now().addSeconds(3600)) +
@@ -103,43 +117,100 @@ public with sharing class AltinnCalloutService {
103117
return errorRef;
104118
}
105119

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
106128
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();
109132

110133
Cache.OrgPartition orgPartition = Cache.Org.getPartition('local.tokens');
111-
String tokenData = Test.isRunningTest()
134+
/*String tokenData = Test.isRunningTest()
112135
? CryptoService.encryptString(TEST_SERVICE_TOKEN_RESPONSE)
113-
: (String) orgPartition.get(clientName);
136+
: (String) orgPartition.get(clientName);*/
137+
String tokenData = (String) orgPartition.get(clientName);
114138

115139
if (String.isNotBlank(tokenData)) {
116140
String decryptedTokenData = CryptoService.decryptString(tokenData);
117141
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);
118154
accessToken = (String) tokenDataMap.get('access_token');
155+
119156
} else {
120-
MaskinportenService service = new MaskinportenService(clientDeveloperName);
157+
121158
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();
135162
}
136163
}
137164

138-
165+
logger.publish();
139166
return accessToken;
140167
}
141168

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';
142189

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+
}
143214

144215
// RESPONSE WRAPPERS
145216
public class AltinnOrganizationsResponse {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3-
<apiVersion>51.0</apiVersion>
3+
<apiVersion>59.0</apiVersion>
44
<status>Active</status>
55
</ApexClass>

force-app/altinn/classes/AltinnCalloutServiceTest.cls

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,72 @@ private with sharing class AltinnCalloutServiceTest {
7777
System.assert(resp.errorMessage.contains('Kunne ikke hente rettigheter fra Altinn.'), 'Expect error message');
7878
System.assertEquals(null, resp.rights, 'Expect no rights');
7979
}
80+
81+
@IsTest
82+
static void testGetMaskinportenTokenMethodPlatformCachePositive(){
83+
84+
Cache.OrgPartition orgPartition = Cache.Org.getPartition('local.tokens');
85+
AltinnCalloutService altinnCalloutService = new AltinnCalloutService();
86+
87+
System.Test.startTest();
88+
/*orgPartition.put(
89+
'altinntokendata',
90+
'abcdefghijklmnopqrstuv=',
91+
300
92+
);*/
93+
94+
String accessToken = altinnCalloutService.getMaskinportenToken();
95+
System.debug('accessToken: ' + accessToken);
96+
String accessToken2 = altinnCalloutService.getMaskinportenToken();
97+
System.debug('accessToken2: ' + accessToken2);
98+
99+
MaskinportenService.AuthResponse testResponse = new MaskinportenService.AuthResponse();
100+
testResponse.access_token = 'access_token_test';
101+
testResponse.expires_in = 119;
102+
testResponse.created_at = Datetime.now().addSeconds(-210);//.format('yyyy-MM-dd HH:mm:ss');
103+
testResponse.scope = 'test_scope';
104+
105+
orgPartition.put(
106+
'altinntokendata',
107+
CryptoService.encryptString(JSON.serialize(testResponse)),
108+
300
109+
);
110+
111+
String accessToken3 = altinnCalloutService.getMaskinportenToken();
112+
System.debug('accessToken3: ' + accessToken3);
113+
String accessToken4 = altinnCalloutService.getMaskinportenToken();
114+
System.debug('accessToken4: ' + accessToken4);
115+
116+
testResponse.access_token = 'access_token_test';
117+
testResponse.expires_in = 119;
118+
testResponse.created_at = Datetime.now().addSeconds(-210);//.format('yyyy-MM-dd HH:mm:ss');
119+
testResponse.scope = 'test_scope';
120+
121+
orgPartition.put(
122+
'altinntokendata',
123+
CryptoService.encryptString(JSON.serialize(testResponse)),
124+
300
125+
);
126+
127+
128+
String accessToken5 = altinnCalloutService.getMaskinportenToken();
129+
System.debug('accessToken5: ' + accessToken5);
130+
System.Test.stopTest();
131+
132+
String tokenData = (String) orgPartition.get('altinntokendata');
133+
134+
String decryptedTokenData = CryptoService.decryptString(tokenData);
135+
Map<String, Object> tokenDataMap = (Map<String, Object>) JSON.deserializeUntyped(decryptedTokenData);
136+
accessToken = (String) tokenDataMap.get('access_token');
137+
Integer expiresIn = (Integer) tokenDataMap.get('expires_in');
138+
String createdAt = (String) tokenDataMap.get('created_at');
139+
String scope = (String) tokenDataMap.get('scope');
140+
141+
System.debug('accessToken: ' + accessToken);
142+
System.debug('expiresIn: ' + expiresIn);
143+
System.debug('createdAt: ' + createdAt);
144+
System.debug('scope: ' + scope);
145+
System.Assert.areEqual('access_token_test', accessToken, 'Access token should be the same');
146+
147+
}
80148
}

force-app/krr-integration/classes/MaskinportenService.cls

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,16 @@ public class MaskinportenService {
3232

3333
// Do callout, parse body, and return parsed response
3434
HttpResponse res = new Http().send(req);
35-
return (AuthResponse) Json.deserialize(res.getBody(), AuthResponse.class);
35+
AuthResponse authResponse = (AuthResponse) Json.deserialize(res.getBody(), AuthResponse.class);
36+
authResponse.created_at = Datetime.now();
37+
38+
return authResponse;
3639
}
3740

3841
public class AuthResponse {
3942
public String access_token;
4043
public Integer expires_in;
41-
String scope;
44+
public Datetime created_at;
45+
public String scope;
4246
}
4347
}

0 commit comments

Comments
 (0)