Skip to content

Commit df84426

Browse files
ZhongpinWangsap-cloud-sdk-bot[bot]marikaner
authored
fix: Avoid caching jwt if it needs to be forwarded (#6007)
* Add forwarded auth token after caching if needed & REFACTOR * lint * Add RCA * chore: improve RCA * Improve RCA * fix: lint * refactor: remove let * refactor: return early * refactor: align code styles * chore: adapt jsdocs * chore: remove debug comments * chore: remove TODO * fix: lint * Changes from lint:fix * chore: changeset * Apply suggestion from @marikaner --------- Co-authored-by: sap-cloud-sdk-bot[bot] <274190970+sap-cloud-sdk-bot[bot]@users.noreply.github.com> Co-authored-by: Marika Marszalkowski <[email protected]>
1 parent 07f5dff commit df84426

7 files changed

Lines changed: 162 additions & 117 deletions

File tree

.changeset/mean-flies-lick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sap-cloud-sdk/connectivity': patch
3+
---
4+
5+
[Fixed Issue] Avoid caching JWT if `forwardAuthToken` is set to `true`.

packages/connectivity/src/scp-cf/destination/destination-from-env.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
proxyStrategy
1010
} from './http-proxy-util';
1111
import { isHttpDestination } from './destination-service-types';
12-
import { setForwardedAuthTokenIfNeeded } from './forward-auth-token';
12+
import { addForwardedAuthTokenIfNeeded } from './forward-auth-token';
1313
import type { Destination } from './destination-service-types';
1414
import type { DestinationFetchOptions } from './destination-accessor-types';
1515

@@ -103,18 +103,21 @@ export function searchEnvVariablesForDestination(
103103

104104
if (getDestinationsEnvVariable()) {
105105
try {
106-
const destination = getDestinationFromEnvByName(options.destinationName);
106+
let destination = getDestinationFromEnvByName(options.destinationName);
107107
if (destination) {
108108
logger.info(
109109
`Successfully retrieved destination '${options.destinationName}' from environment variable.`
110110
);
111111

112-
setForwardedAuthTokenIfNeeded(destination, options.jwt);
112+
destination = addForwardedAuthTokenIfNeeded(destination, options.jwt);
113113

114-
return isHttpDestination(destination) &&
114+
if (
115+
isHttpDestination(destination) &&
115116
['internet', 'private-link'].includes(proxyStrategy(destination))
116-
? addProxyConfigurationInternet(destination)
117-
: destination;
117+
) {
118+
destination = addProxyConfigurationInternet(destination);
119+
}
120+
return destination;
118121
}
119122
} catch (error) {
120123
logger.error(

packages/connectivity/src/scp-cf/destination/destination-from-registration.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
proxyStrategy
1313
} from './http-proxy-util';
1414
import { registerDestinationCache } from './register-destination-cache';
15-
import { setForwardedAuthTokenIfNeeded } from './forward-auth-token';
15+
import { addForwardedAuthTokenIfNeeded } from './forward-auth-token';
1616
import type { Destination } from './destination-service-types';
1717
import type { IsolationStrategy } from './destination-cache';
1818
import type { DestinationFetchOptions } from './destination-accessor-types';
@@ -90,7 +90,7 @@ export type DestinationWithName = Destination & { name: string };
9090
export async function searchRegisteredDestination(
9191
options: DestinationFetchOptions
9292
): Promise<Destination | null> {
93-
const destination =
93+
let destination =
9494
await registerDestinationCache.destination.retrieveDestinationFromCache(
9595
getJwtForCaching(options),
9696
options.destinationName,
@@ -108,12 +108,15 @@ export async function searchRegisteredDestination(
108108
`Successfully retrieved destination '${options.destinationName}' from registered destinations.`
109109
);
110110

111-
setForwardedAuthTokenIfNeeded(destination, options.jwt);
111+
destination = addForwardedAuthTokenIfNeeded(destination, options.jwt);
112112

113-
return isHttpDestination(destination) &&
113+
if (
114+
isHttpDestination(destination) &&
114115
['internet', 'private-link'].includes(proxyStrategy(destination))
115-
? addProxyConfigurationInternet(destination)
116-
: destination;
116+
) {
117+
destination = addProxyConfigurationInternet(destination);
118+
}
119+
return destination;
117120
}
118121

119122
/**

packages/connectivity/src/scp-cf/destination/destination-from-service.ts

Lines changed: 116 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
addProxyConfigurationInternet,
3434
proxyStrategy
3535
} from './http-proxy-util';
36-
import { setForwardedAuthTokenIfNeeded } from './forward-auth-token';
36+
import { addForwardedAuthTokenIfNeeded } from './forward-auth-token';
3737
import type { SubscriberToken } from './get-subscriber-token';
3838
import type { Destination } from './destination-service-types';
3939
import type { AuthAndExchangeTokens } from './destination-service';
@@ -89,80 +89,63 @@ export class DestinationFromServiceRetriever {
8989
public static async getDestinationFromDestinationService(
9090
options: DestinationFetchOptions
9191
): Promise<Destination | null> {
92-
// TODO: This is currently always skipped for tokens issued by XSUAA
93-
// in the XSUAA case no exchange takes place
94-
if (shouldExchangeToken(options) && options.jwt) {
95-
// Exchange the IAS token to a XSUAA token using the destination service credentials
96-
options.jwt = await jwtBearerToken(options.jwt, 'destination');
92+
// Exchange the IAS token to a XSUAA token using the destination service credentials
93+
if (shouldExchangeToken(options)) {
94+
options.jwt = await jwtBearerToken(options.jwt!, 'destination');
9795
}
9896

99-
const subscriberToken = await getSubscriberToken(options);
100-
const providerToken = await getProviderServiceToken(options);
101-
102-
const da = new DestinationFromServiceRetriever(
97+
// Create retriever with subscriber and provider tokens
98+
const retriever = new DestinationFromServiceRetriever(
10399
options,
104-
subscriberToken,
105-
providerToken
100+
await getSubscriberToken(options),
101+
await getProviderServiceToken(options)
106102
);
107103

108-
const destinationResult =
109-
await da.searchDestinationWithSelectionStrategyAndCache();
110-
if (!destinationResult) {
104+
// Search destination with selection strategy and cache
105+
const destinationSearchResult =
106+
await retriever.searchDestinationWithSelectionStrategyAndCache();
107+
108+
// Immediately return null if no destination found
109+
if (!destinationSearchResult) {
111110
return null;
112111
}
113112

114-
let { destination } = destinationResult;
115-
116-
setForwardedAuthTokenIfNeeded(destination, options.jwt);
117-
118-
if (destinationResult.fromCache) {
119-
return da.addProxyConfiguration(destination);
120-
}
113+
const { origin, fromCache } = destinationSearchResult;
114+
let { destination } = destinationSearchResult;
121115

122-
if (!destination.forwardAuthToken) {
123-
if (
124-
destination.authentication === 'OAuth2UserTokenExchange' ||
125-
destination.authentication === 'OAuth2JWTBearer' ||
126-
destination.authentication === 'SAMLAssertion' ||
127-
(destination.authentication === 'OAuth2SAMLBearerAssertion' &&
128-
!da.usesSystemUser(destination))
129-
) {
130-
destination =
131-
await da.fetchDestinationWithUserExchangeFlows(destinationResult);
132-
}
116+
if (!fromCache) {
117+
/* Destination NOT from cache */
133118

134-
if (destination.authentication === 'PrincipalPropagation') {
135-
if (!this.isUserJwt(da.subscriberToken)) {
136-
DestinationFromServiceRetriever.throwUserTokenMissing(destination);
137-
}
138-
}
119+
// Fetch and add auth token if needed.
120+
// Needed means `forwardAuthToken` is `false`
121+
// AND authentication is one of the supported types
122+
destination = await retriever.fetchAndAddAuthTokenIfNeeded(
123+
destination,
124+
origin
125+
);
139126

140-
if (
141-
destination.authentication === 'OAuth2Password' ||
142-
destination.authentication === 'ClientCertificateAuthentication' ||
143-
destination.authentication === 'OAuth2ClientCredentials' ||
144-
da.usesSystemUser(destination)
145-
) {
146-
destination =
147-
await da.fetchDestinationWithNonUserExchangeFlows(destinationResult);
148-
}
127+
// Add trust store configuration if needed.
128+
// Needed means `TrustStoreLocation` is defined
129+
destination = await retriever.addTrustStoreConfigurationIfNeeded(
130+
destination,
131+
origin
132+
);
149133

150-
if (destination.authentication === 'OAuth2RefreshToken') {
151-
destination =
152-
await da.fetchDestinationWithRefreshTokenFlow(destinationResult);
153-
}
134+
// Cache the destination
135+
await retriever.cacheDestination(destination, origin);
154136
}
155137

156-
const withTrustStore = await da.addTrustStoreConfiguration(
157-
destination,
158-
destinationResult.origin
159-
);
160-
await da.updateDestinationCache(withTrustStore, destinationResult.origin);
138+
// Add auth token based on the given `options.jwt` if needed
139+
// Needed means `forwardAuthToken` is `true`
140+
destination = addForwardedAuthTokenIfNeeded(destination, options.jwt);
141+
142+
// Add proxy configuration based on the proxy strategy
143+
destination = await retriever.addProxyConfiguration(destination);
161144

162-
return da.addProxyConfiguration(withTrustStore);
145+
return destination;
163146
}
164147

165-
private static throwUserTokenMissing(destination) {
148+
private static throwUserTokenMissing(destination: Destination) {
166149
throw Error(
167150
`No user token (JWT) has been provided. This is strictly necessary for '${destination.authentication}'.`
168151
);
@@ -247,9 +230,9 @@ export class DestinationFromServiceRetriever {
247230
}
248231

249232
private async getAuthTokenForOAuth2ClientCredentials(
250-
destinationResult: DestinationSearchResult
233+
destination: Destination,
234+
origin: DestinationOrigin
251235
): Promise<AuthAndExchangeTokens> {
252-
const { destination, origin } = destinationResult;
253236
// This covers the x-tenant case https://api.sap.com/api/SAP_CP_CF_Connectivity_Destination/resource
254237
const exchangeTenant = this.getExchangeTenant(destination);
255238
const authHeaderJwt =
@@ -285,9 +268,9 @@ Possible alternatives for such technical user authentication are BasicAuthentica
285268
}
286269

287270
private async getAuthTokenForOAuth2UserBasedTokenExchanges(
288-
destinationResult: DestinationSearchResult
271+
destination: Destination,
272+
origin: DestinationOrigin
289273
): Promise<AuthAndExchangeTokens> {
290-
const { destination, origin } = destinationResult;
291274
const { destinationName } = this.options;
292275
if (!DestinationFromServiceRetriever.isUserJwt(this.subscriberToken)) {
293276
throw DestinationFromServiceRetriever.throwUserTokenMissing(destination);
@@ -341,9 +324,9 @@ Possible alternatives for such technical user authentication are BasicAuthentica
341324
}
342325

343326
private async getAuthTokenForOAuth2RefreshToken(
344-
destinationResult: DestinationSearchResult
327+
destination: Destination,
328+
origin: DestinationOrigin
345329
): Promise<AuthAndExchangeTokens> {
346-
const { destination, origin } = destinationResult;
347330
const { refreshToken } = this.options;
348331
if (!refreshToken) {
349332
throw Error(
@@ -361,14 +344,18 @@ Possible alternatives for such technical user authentication are BasicAuthentica
361344
* @internal
362345
* This method calls the 'find destination by name' endpoint of the destination service using a client credentials grant.
363346
* For the find by name endpoint, the destination service will take care of OAuth flows and include the token in the destination.
364-
* @param destinationResult - Result of the getDestinations call for which the exchange flow is triggered.
347+
* @param destination - The destination for which the token should be fetched.
348+
* @param origin - The origin of the destination, either 'subscriber' or 'provider'.
365349
* @returns Destination containing the auth token.
366350
*/
367351
private async fetchDestinationWithNonUserExchangeFlows(
368-
destinationResult: DestinationSearchResult
352+
destination: Destination,
353+
origin: DestinationOrigin
369354
): Promise<Destination> {
370-
const token =
371-
await this.getAuthTokenForOAuth2ClientCredentials(destinationResult);
355+
const token = await this.getAuthTokenForOAuth2ClientCredentials(
356+
destination,
357+
origin
358+
);
372359

373360
return fetchDestinationWithTokenRetrieval(
374361
getDestinationServiceCredentials().uri,
@@ -378,12 +365,13 @@ Possible alternatives for such technical user authentication are BasicAuthentica
378365
}
379366

380367
private async fetchDestinationWithUserExchangeFlows(
381-
destinationResult: DestinationSearchResult
368+
destination: Destination,
369+
origin: DestinationOrigin
382370
): Promise<Destination> {
383-
const token =
384-
await this.getAuthTokenForOAuth2UserBasedTokenExchanges(
385-
destinationResult
386-
);
371+
const token = await this.getAuthTokenForOAuth2UserBasedTokenExchanges(
372+
destination,
373+
origin
374+
);
387375

388376
return fetchDestinationWithTokenRetrieval(
389377
getDestinationServiceCredentials().uri,
@@ -393,10 +381,13 @@ Possible alternatives for such technical user authentication are BasicAuthentica
393381
}
394382

395383
private async fetchDestinationWithRefreshTokenFlow(
396-
destinationResult: DestinationSearchResult
384+
destination: Destination,
385+
origin: DestinationOrigin
397386
): Promise<Destination> {
398-
const token =
399-
await this.getAuthTokenForOAuth2RefreshToken(destinationResult);
387+
const token = await this.getAuthTokenForOAuth2RefreshToken(
388+
destination,
389+
origin
390+
);
400391

401392
return fetchDestinationWithTokenRetrieval(
402393
getDestinationServiceCredentials().uri,
@@ -405,6 +396,47 @@ Possible alternatives for such technical user authentication are BasicAuthentica
405396
);
406397
}
407398

399+
private async fetchAndAddAuthTokenIfNeeded(
400+
destination: Destination,
401+
origin: DestinationOrigin
402+
): Promise<Destination> {
403+
const { forwardAuthToken, authentication } = destination;
404+
if (forwardAuthToken) {
405+
return destination;
406+
}
407+
408+
if (
409+
authentication === 'OAuth2UserTokenExchange' ||
410+
authentication === 'OAuth2JWTBearer' ||
411+
authentication === 'SAMLAssertion' ||
412+
(authentication === 'OAuth2SAMLBearerAssertion' &&
413+
!this.usesSystemUser(destination))
414+
) {
415+
return this.fetchDestinationWithUserExchangeFlows(destination, origin);
416+
}
417+
418+
if (
419+
authentication === 'OAuth2Password' ||
420+
authentication === 'ClientCertificateAuthentication' ||
421+
authentication === 'OAuth2ClientCredentials' ||
422+
this.usesSystemUser(destination)
423+
) {
424+
return this.fetchDestinationWithNonUserExchangeFlows(destination, origin);
425+
}
426+
427+
if (authentication === 'OAuth2RefreshToken') {
428+
return this.fetchDestinationWithRefreshTokenFlow(destination, origin);
429+
}
430+
431+
if (authentication === 'PrincipalPropagation') {
432+
if (!DestinationFromServiceRetriever.isUserJwt(this.subscriberToken)) {
433+
DestinationFromServiceRetriever.throwUserTokenMissing(destination);
434+
}
435+
}
436+
437+
return destination;
438+
}
439+
408440
private async addProxyConfiguration(
409441
destination: Destination
410442
): Promise<Destination> {
@@ -429,10 +461,10 @@ Possible alternatives for such technical user authentication are BasicAuthentica
429461
}
430462
}
431463

432-
private async updateDestinationCache(
464+
private async cacheDestination(
433465
destination: Destination,
434466
destinationOrigin: DestinationOrigin
435-
) {
467+
): Promise<void> {
436468
if (!this.options.useCache) {
437469
return;
438470
}
@@ -577,11 +609,11 @@ Possible alternatives for such technical user authentication are BasicAuthentica
577609
);
578610
}
579611

580-
private async addTrustStoreConfiguration(
612+
private async addTrustStoreConfigurationIfNeeded(
581613
destination: Destination,
582614
origin: DestinationOrigin
583615
): Promise<Destination> {
584-
const originalProperties = destination.originalProperties;
616+
const { originalProperties } = destination;
585617
const trustStoreLocation =
586618
originalProperties?.TrustStoreLocation ||
587619
originalProperties?.destinationConfiguration?.TrustStoreLocation;
@@ -593,7 +625,10 @@ Possible alternatives for such technical user authentication are BasicAuthentica
593625
: this.subscriberToken!.serviceJwt!.encoded,
594626
trustStoreLocation
595627
);
596-
destination.trustStoreCertificate = trustStoreCertificate;
628+
return {
629+
...destination,
630+
trustStoreCertificate
631+
};
597632
}
598633
return destination;
599634
}

0 commit comments

Comments
 (0)