Skip to content

Commit 3c19ffa

Browse files
authored
feat: Remove XSUAA binding dependency from destination retrieval flow (#5879)
1 parent 06a0574 commit 3c19ffa

22 files changed

+82
-443
lines changed

.changeset/early-rocks-dip.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': minor
3+
---
4+
5+
[Compatibility Note] The `getDestinationFromDestinationService()` function no longer verifies the incoming XSUAA JWT against the application's bound XSUAA instance. Consequently, the `cacheVerificationKeys` option is now deprecated and has no effect.

.changeset/early-rocks-work.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': minor
3+
---
4+
5+
[Improvement] Remove dependency on XSUAA service binding while retrieving destinations using `getDestinationFromDestinationService()` and `getAllDestinationsFromDestinationService()` functions.

packages/connectivity/src/scp-cf/destination/destination-accessor-authentication-type.spec.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import {
1111
} from '../../../../../test-resources/test/test-util/token-accessor-mocks';
1212
import {
1313
mockFetchDestinationCalls,
14-
mockFetchDestinationCallsNotFound,
15-
mockVerifyJwt
14+
mockFetchDestinationCallsNotFound
1615
} from '../../../../../test-resources/test/test-util/destination-service-mocks';
1716
import {
1817
onlyIssuerServiceToken,
@@ -48,7 +47,6 @@ import {
4847
describe('authentication types', () => {
4948
beforeEach(() => {
5049
mockServiceBindings();
51-
mockVerifyJwt();
5250
mockServiceToken();
5351
});
5452

@@ -412,7 +410,6 @@ describe('authentication types', () => {
412410

413411
it('returns a destination without authTokens if its authenticationType is Basic', async () => {
414412
mockServiceBindings();
415-
mockVerifyJwt();
416413
const serviceTokenSpy = mockServiceToken();
417414

418415
const httpMocks = mockFetchDestinationCalls(basicMultipleResponse[0], {
@@ -500,7 +497,6 @@ describe('authentication types', () => {
500497

501498
it('works for onPremise Basic and issuer JWT', async () => {
502499
mockServiceBindings();
503-
mockVerifyJwt();
504500
mockServiceToken();
505501

506502
const httpMocks = mockFetchDestinationCalls(

packages/connectivity/src/scp-cf/destination/destination-accessor-failure-cases.spec.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ import {
1414
mockFetchDestinationCalls,
1515
mockFetchDestinationCallsNotFound,
1616
mockInstanceDestinationsCall,
17-
mockSubaccountDestinationsCall,
18-
mockVerifyJwt
17+
mockSubaccountDestinationsCall
1918
} from '../../../../../test-resources/test/test-util/destination-service-mocks';
2019
import {
2120
basicMultipleResponse,
@@ -33,8 +32,6 @@ describe('Failure cases', () => {
3332
xsuaa: [xsuaaBindingMock]
3433
});
3534

36-
mockVerifyJwt();
37-
3835
await expect(
3936
getDestination({
4037
destinationName,
@@ -48,8 +45,6 @@ describe('Failure cases', () => {
4845

4946
it('throws an error when the provide userJwt is invalid', async () => {
5047
mockServiceBindings();
51-
mockVerifyJwt();
52-
5348
await expect(
5449
getDestination({
5550
destinationName,
@@ -63,9 +58,7 @@ describe('Failure cases', () => {
6358

6459
it('throws an error if the subaccount/instance destinations call fails', async () => {
6560
mockServiceBindings();
66-
mockVerifyJwt();
6761
mockServiceToken();
68-
6962
const httpMocks = [
7063
mockInstanceDestinationsCall(
7164
{
@@ -96,7 +89,6 @@ describe('Failure cases', () => {
9689

9790
it('returns an error if the single destination call fails for OAuth2SAMLBearerAssertion destinations', async () => {
9891
mockServiceBindings();
99-
mockVerifyJwt();
10092
mockServiceToken();
10193
mockJwtBearerToken();
10294

@@ -130,7 +122,6 @@ describe('Failure cases', () => {
130122

131123
it('returns null if no destinations are found', async () => {
132124
mockServiceBindings();
133-
mockVerifyJwt();
134125
mockServiceToken();
135126

136127
const httpMocks = [
@@ -152,9 +143,7 @@ describe('Failure cases', () => {
152143

153144
it('should throw an error when neither userJwt nor SystemUser are defined', async () => {
154145
mockServiceBindings();
155-
mockVerifyJwt();
156146
mockServiceToken();
157-
158147
const [httpMock] = mockFetchDestinationCalls(oauthMultipleResponse[0], {
159148
mockWithTokenRetrievalCall: false
160149
});

packages/connectivity/src/scp-cf/destination/destination-accessor-provider-subscriber-lookup.spec.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import {
44
mockFetchDestinationCalls,
55
mockFetchDestinationCallsNotFound,
66
mockInstanceDestinationsCall,
7-
mockSubaccountDestinationsCall,
8-
mockVerifyJwt
7+
mockSubaccountDestinationsCall
98
} from '../../../../../test-resources/test/test-util/destination-service-mocks';
109
import {
1110
destinationServiceUri,
@@ -26,7 +25,7 @@ import {
2625
subscriberUserToken
2726
} from '../../../../../test-resources/test/test-util/mocked-access-tokens';
2827
import { mockServiceToken } from '../../../../../test-resources/test/test-util/token-accessor-mocks';
29-
import * as identityService from '../identity-service';
28+
import * as tokenAccessor from '../token-accessor';
3029
import { decodeJwt } from '../jwt';
3130
import { parseDestination } from './destination';
3231
import {
@@ -166,7 +165,6 @@ function assertMockUsed(mock: nock.Scope, used: boolean) {
166165
describe('JWT type and selection strategies', () => {
167166
beforeEach(() => {
168167
mockServiceBindings();
169-
mockVerifyJwt();
170168
mockServiceToken();
171169
});
172170

@@ -256,7 +254,7 @@ describe('JWT type and selection strategies', () => {
256254
mockServiceToken();
257255

258256
jest
259-
.spyOn(identityService, 'exchangeToken')
257+
.spyOn(tokenAccessor, 'jwtBearerToken')
260258
.mockImplementationOnce(() => Promise.resolve(subscriberUserToken));
261259

262260
mockFetchDestinationCalls(samlAssertionMultipleResponse[0], {
@@ -334,7 +332,6 @@ describe('JWT type and selection strategies', () => {
334332
describe('call getAllDestinations with and without subscriber token', () => {
335333
beforeEach(() => {
336334
mockServiceBindings();
337-
mockVerifyJwt();
338335
mockServiceToken();
339336
destinationServiceCache.clear();
340337
});

packages/connectivity/src/scp-cf/destination/destination-accessor-proxy-configuration.spec.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
} from '../../../../../test-resources/test/test-util/token-accessor-mocks';
1212
import {
1313
mockCertificateCall,
14-
mockVerifyJwt,
1514
mockFetchDestinationCalls
1615
} from '../../../../../test-resources/test/test-util/destination-service-mocks';
1716
import {
@@ -34,7 +33,6 @@ import type { Destination } from './destination-service-types';
3433
describe('proxy configuration', () => {
3534
beforeEach(() => {
3635
mockServiceBindings();
37-
mockVerifyJwt();
3836
mockServiceToken();
3937
});
4038

@@ -88,7 +86,6 @@ describe('proxy configuration', () => {
8886
describe('get destination with PrivateLink proxy type', () => {
8987
beforeEach(() => {
9088
mockServiceBindings();
91-
mockVerifyJwt();
9289
mockServiceToken();
9390
mockJwtBearerToken();
9491

@@ -171,7 +168,6 @@ describe('truststore configuration', () => {
171168
};
172169
mockCertificateCall('my-cert.pem', providerServiceToken, 'subaccount');
173170
mockServiceBindings();
174-
mockVerifyJwt();
175171
mockServiceToken();
176172
mockJwtBearerToken();
177173

packages/connectivity/src/scp-cf/destination/destination-accessor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { createLogger, ErrorWithCause } from '@sap-cloud-sdk/util';
2-
import { exchangeToken, shouldExchangeToken } from '../identity-service';
2+
import { shouldExchangeToken } from '../identity-service';
33
import { getDestinationServiceCredentials } from '../environment-accessor';
44
import { getSubdomain } from '../jwt';
5+
import { jwtBearerToken } from '../token-accessor';
56
import { sanitizeDestination, toDestinationNameUrl } from './destination';
67
import { searchEnvVariablesForDestination } from './destination-from-env';
78
import { searchServiceBindingForDestination } from './destination-from-vcap';
@@ -130,7 +131,8 @@ export async function getAllDestinationsFromDestinationService(
130131
'Attempting to retrieve all destinations from destination service.'
131132
);
132133
if (shouldExchangeToken(options) && options.jwt) {
133-
options.jwt = await exchangeToken(options.jwt);
134+
// Exchange the IAS token to a XSUAA token using the destination service credentials
135+
options.jwt = await jwtBearerToken(options.jwt, 'destination');
134136
}
135137

136138
const token =

packages/connectivity/src/scp-cf/destination/destination-cache.spec.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import nock from 'nock';
33
import { decodeJwt } from '../jwt';
44
import {
55
mockFetchDestinationCalls,
6-
mockFetchDestinationCallsNotFound,
7-
mockVerifyJwt
6+
mockFetchDestinationCallsNotFound
87
} from '../../../../../test-resources/test/test-util/destination-service-mocks';
98
import {
109
connectivityProxyConfigMock,
@@ -120,7 +119,6 @@ describe('destination cache', () => {
120119

121120
describe('caching', () => {
122121
beforeEach(() => {
123-
mockVerifyJwt();
124122
mockServiceBindings();
125123
mockServiceToken();
126124

@@ -234,7 +232,6 @@ describe('destination cache', () => {
234232
});
235233

236234
it('caches only subscriber if selection strategy always subscriber', async () => {
237-
mockVerifyJwt();
238235
await getDestination({
239236
destinationName: 'SubscriberDest',
240237
jwt: subscriberUserToken,
@@ -251,7 +248,6 @@ describe('destination cache', () => {
251248
});
252249

253250
it('caches nothing if the destination is not found', async () => {
254-
mockVerifyJwt();
255251
mockFetchDestinationCallsNotFound('ANY', {
256252
serviceToken: subscriberServiceToken,
257253
mockWithTokenRetrievalCall: false
@@ -273,7 +269,6 @@ describe('destination cache', () => {
273269
});
274270

275271
it('caches only the found destination not other ones received from the service', async () => {
276-
mockVerifyJwt();
277272
await getDestination({
278273
destinationName: 'SubscriberDest2',
279274
jwt: subscriberUserToken,
@@ -305,7 +300,6 @@ describe('destination cache', () => {
305300
beforeEach(() => {
306301
mockServiceBindings({ xsuaaBinding: false });
307302
mockServiceToken();
308-
mockVerifyJwt();
309303
});
310304

311305
const destName = destinationOne.name!;
@@ -414,7 +408,6 @@ describe('destination cache', () => {
414408
describe('caching of destinations with special information (e.g. authTokens, certificates)', () => {
415409
it('destinations with certificates are cached ', async () => {
416410
mockServiceBindings();
417-
mockVerifyJwt();
418411
mockServiceToken();
419412
mockJwtBearerToken();
420413

@@ -456,7 +449,6 @@ describe('destination cache', () => {
456449

457450
it('destinations with authTokens are cached correctly', async () => {
458451
mockServiceBindings();
459-
mockVerifyJwt();
460452
mockServiceToken();
461453
mockJwtBearerToken();
462454

@@ -507,7 +499,6 @@ describe('destination cache', () => {
507499

508500
it('destinations with proxy configuration are cached correctly', async () => {
509501
mockServiceBindings();
510-
mockVerifyJwt();
511502
mockServiceToken();
512503
mockJwtBearerToken();
513504

@@ -568,8 +559,6 @@ describe('destination cache', () => {
568559
it('retrieves subscriber cached destination', async () => {
569560
mockServiceBindings();
570561
mockServiceToken();
571-
mockVerifyJwt();
572-
573562
const authType = 'NoAuthentication' as AuthenticationType;
574563
const subscriberDest = {
575564
URL: 'https://subscriber.example',
@@ -597,7 +586,6 @@ describe('destination cache', () => {
597586

598587
it('retrieves provider cached destination', async () => {
599588
mockServiceBindings();
600-
mockVerifyJwt();
601589
mockServiceToken();
602590

603591
const authType = 'NoAuthentication' as AuthenticationType;
@@ -627,7 +615,6 @@ describe('destination cache', () => {
627615

628616
it('should return cached provider destination from cache after checking for remote subscriber destination when subscriberFirst is specified and destinations are tenant isolated', async () => {
629617
mockServiceBindings();
630-
mockVerifyJwt();
631618
mockServiceToken();
632619

633620
const authType = 'NoAuthentication' as AuthenticationType;

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
mockServiceBindings,
33
mockServiceToken,
44
mockFetchDestinationCalls,
5-
mockVerifyJwt,
65
providerServiceToken
76
} from '../../../../../test-resources/test/test-util';
87
import { getDestinationFromDestinationService } from './destination-from-service';
@@ -11,7 +10,6 @@ describe('getDestinationFromDestinationService', () => {
1110
beforeEach(() => {
1211
jest.clearAllMocks();
1312
mockServiceBindings();
14-
mockVerifyJwt();
1513
mockServiceToken();
1614
});
1715

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
getDestinationServiceCredentials,
55
getServiceBinding
66
} from '../environment-accessor';
7-
import { exchangeToken, shouldExchangeToken } from '../identity-service';
7+
import { shouldExchangeToken } from '../identity-service';
88
import { getSubdomain, isXsuaaToken } from '../jwt';
99
import { isIdenticalTenant } from '../tenant';
1010
import { jwtBearerToken } from '../token-accessor';
@@ -90,10 +90,10 @@ export class DestinationFromServiceRetriever {
9090
options: DestinationFetchOptions
9191
): Promise<Destination | null> {
9292
// TODO: This is currently always skipped for tokens issued by XSUAA
93-
// in the XSUAA case no exchange takes place, but instead the JWT is verified
94-
// in the future we should just let it verify here, but skip it later (get-subscriber-token)
93+
// in the XSUAA case no exchange takes place
9594
if (shouldExchangeToken(options) && options.jwt) {
96-
options.jwt = await exchangeToken(options.jwt);
95+
// Exchange the IAS token to a XSUAA token using the destination service credentials
96+
options.jwt = await jwtBearerToken(options.jwt, 'destination');
9797
}
9898

9999
const subscriberToken = await getSubscriberToken(options);

0 commit comments

Comments
 (0)