Skip to content

Commit 3481fda

Browse files
committed
feat(auth): add support for MCSP V2 authentication
This commit adds the MCSPV2Authenticator implementation. This authenticator will invoke the MCSP V2 POST /api/2.0/{scopeCollectionType}/{scopeId}/apikeys/token operation to obtain an access token for a user-supplied apikey. Signed-off-by: Phil Adams <[email protected]>
1 parent bb2d72b commit 3481fda

14 files changed

+1416
-10
lines changed

Authentication.md

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ The java-sdk-core project supports the following types of authentication:
77
- Container Authentication
88
- VPC Instance Authentication
99
- Cloud Pak for Data Authentication
10-
- Multi-Cloud Saas Platform (MCSP) Authentication
10+
- Multi-Cloud Saas Platform (MCSP) V1 Authentication
11+
- Multi-Cloud Saas Platform (MCSP) V2 Authentication
1112
- No Authentication (for testing)
1213

1314
The SDK user configures the appropriate type of authentication for use with service instances.
@@ -575,11 +576,11 @@ ExampleService service = ExampleService.newInstance("example_service");
575576
```
576577

577578

578-
## Multi-Cloud Saas Platform (MCSP) Authentication
579+
## Multi-Cloud Saas Platform (MCSP) V1 Authentication
579580
The `MCSPAuthenticator` can be used in scenarios where an application needs to
580581
interact with an IBM Cloud service that has been deployed to a non-IBM Cloud environment (e.g. AWS).
581-
It accepts a user-supplied apikey and performs the necessary interactions with the
582-
Multi-Cloud Saas Platform token service to obtain a suitable MCSP access token (a bearer token)
582+
It accepts a user-supplied apikey and invokes the Multi-Cloud Saas Platform token service's
583+
`POST /siusermgr/api/1.0/apikeys/token` operation to obtain a suitable MCSP access token (a bearer token)
583584
for the specified apikey.
584585
The authenticator will also obtain a new bearer token when the current token expires.
585586
The bearer token is then added to each outbound request in the `Authorization` header in the
@@ -643,6 +644,106 @@ ExampleService service = ExampleService.newInstance("example_service");
643644
```
644645

645646

647+
## Multi-Cloud Saas Platform (MCSP) V2 Authentication
648+
The `MCSPV2Authenticator` can be used in scenarios where an application needs to
649+
interact with an IBM Cloud service that has been deployed to a non-IBM Cloud environment (e.g. AWS).
650+
It accepts a user-supplied apikey and invokes the Multi-Cloud Saas Platform token service's
651+
`POST /api/2.0/{scopeCollectionType}/{scopeId}/apikeys/token` operation to obtain a suitable MCSP access token (a bearer token)
652+
for the specified apikey.
653+
The authenticator will also obtain a new bearer token when the current token expires.
654+
The bearer token is then added to each outbound request in the `Authorization` header in the
655+
form:
656+
```
657+
Authorization: Bearer <bearer-token>
658+
```
659+
660+
### Properties
661+
662+
- apikey: (required) the apikey to be used to obtain an MCSP access token.
663+
664+
- url: (required) The URL representing the MCSP token service endpoint's base URL string. Do not include the
665+
operation path (e.g. `/siusermgr/api/1.0/apikeys/token`) as part of this property's value.
666+
667+
- scopeCollectionType: (required) The scope collection type of item(s).
668+
The valid values are: `accounts`, `subscriptions`, `services`, `products`, `externalservices`
669+
670+
- scopeId: (required) The scope identifier of item(s).
671+
672+
- includeBuiltinActions: (optional) A flag to include builtin actions in the `actions` claim in the MCSP token (default: false).
673+
674+
- includeCustomActions: (optional) A flag to include custom actions in the `actions` claim in the MCSP token (default: false).
675+
676+
- includeRoles: (optional) A flag to include the `roles` claim in the MCSP token (default: true).
677+
678+
- prefixRoles: (optional) A flag to add a prefix with the scope level where
679+
the role is defined in the `roles` claim (default: false).
680+
681+
- callerExtClaim: (optional) A map containing keys and values to be injected into the returned access token
682+
as the `callerExt` claim. The keys used in this map must be enabled in the apikey by setting the
683+
`callerExtClaimNames` property when the apikey is created.
684+
685+
- disableSSLVerification: (optional) A flag that indicates whether verification of the server's SSL
686+
certificate should be disabled or not. The default value is `false`.
687+
688+
- headers: (optional) A set of key/value pairs that will be sent as HTTP headers in requests
689+
made to the MCSP token service.
690+
691+
### Usage Notes
692+
- When constructing an MCSPV2Authenticator instance, the apikey, url, scopeCollectionType, and scopeId properties are required.
693+
694+
- If you specify the callerExtClaim map, the keys used in the map must have been previously enabled in the apikey
695+
by setting the `callerExtClaimNames` property when you created the apikey.
696+
The entries contained in this map will appear in the `callerExt` field (claim) of the returned access token.
697+
698+
- The authenticator will invoke the token server's `POST /api/2.0/{scopeCollectionType}/{scopeId}/apikeys/token` operation to
699+
exchange the apikey for an MCSP access token (the bearer token).
700+
701+
### Programming example
702+
```java
703+
import com.ibm.cloud.sdk.core.security.MCSPV2Authenticator;
704+
import <sdk_base_package>.ExampleService.v1.ExampleService;
705+
...
706+
Map<String, String> callerExtClaim = Map.of("productID", "prod-123");
707+
708+
// Create the authenticator.
709+
MCSPV2Authenticator authenticator = new MCSPV2Authenticator.Builder()
710+
.apikey("myapikey")
711+
.url("https://example.mcspv2.token-exchange.com")
712+
.scopeCollectionType("accounts")
713+
.scopeId("global_account")
714+
.includeBuiltinActions(true)
715+
.callerExtClaim(callerExtClaim)
716+
.build();
717+
718+
// Create the service instance.
719+
ExampleService service = new ExampleService(ExampleService.DEFAULT_SERVICE_NAME, authenticator);
720+
721+
// 'service' can now be used to invoke operations.
722+
```
723+
724+
### Configuration example
725+
External configuration:
726+
```
727+
export EXAMPLE_SERVICE_AUTH_TYPE=mcspv2
728+
export EXAMPLE_SERVICE_APIKEY=myapikey
729+
export EXAMPLE_SERVICE_AUTH_URL=https://example.mcspv2.token-exchange.com
730+
export EXAMPLE_SCOPE_COLLECTION_TYPE=accounts
731+
export EXAMPLE_SCOPE_ID=global_account
732+
export EXAMPLE_INCLUDE_BUILTIN_ACTIONS=true
733+
export EXAMPLE_CALLER_EXT_CLAIM={"productID":"prod-123"}
734+
```
735+
Application code:
736+
```java
737+
import <sdk_base_package>.ExampleService.v1.ExampleService;
738+
...
739+
740+
// Create the service instance.
741+
ExampleService service = ExampleService.newInstance("example_service");
742+
743+
// 'service' can now be used to invoke operations.
744+
```
745+
746+
646747
## No Auth Authentication
647748
The `NoAuthAuthenticator` is a placeholder authenticator which performs no actual authentication function.
648749
It can be used in situations where authentication needs to be bypassed, perhaps while developing

debug-logging.properties

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#
2-
# Copyright 2024 IBM Corporation.
2+
# Copyright 2024, 2025 IBM Corporation.
33
# SPDX-License-Identifier: Apache2.0
44
#
55

66
# This file contains a java.util.logging configuration that enables debug
77
# logging (level = FINE) in the Java SDK core library.
88
#
9-
# To use this file, you can add the "-Djava.util.logging.config.file=logging.properties"
9+
# To use this file, you can add the "-Djava.util.logging.config.file=debug-logging.properties"
1010
# option to your java command line.
1111
# For more information on java.util.logging, please see:
1212
# https://docs.oracle.com/en/java/javase/11/core/java-logging-overview.html

src/main/java/com/ibm/cloud/sdk/core/security/Authenticator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public interface Authenticator {
3434
String AUTHTYPE_CONTAINER = "container";
3535
String AUTHTYPE_VPC = "vpc";
3636
String AUTHTYPE_MCSP = "mcsp";
37+
String AUTHTYPE_MCSPV2 = "mcspv2";
3738

3839
/**
3940
* Constants which define the names of external config propreties (credential file, environment variable, etc.).
@@ -59,6 +60,13 @@ public interface Authenticator {
5960
String PROPNAME_IAM_PROFILE_ID = "IAM_PROFILE_ID";
6061
String PROPNAME_IAM_PROFILE_NAME = "IAM_PROFILE_NAME";
6162
String PROPNAME_IAM_ACCOUNT_ID = "IAM_ACCOUNT_ID";
63+
String PROPNAME_SCOPE_COLLECTION_TYPE = "SCOPE_COLLECTION_TYPE";
64+
String PROPNAME_SCOPE_ID = "SCOPE_ID";
65+
String PROPNAME_INCLUDE_BUILTIN_ACTIONS = "INCLUDE_BUILTIN_ACTIONS";
66+
String PROPNAME_INCLUDE_CUSTOM_ACTIONS = "INCLUDE_CUSTOM_ACTIONS";
67+
String PROPNAME_INCLUDE_ROLES = "INCLUDE_ROLES";
68+
String PROPNAME_PREFIX_ROLES = "PREFIX_ROLES";
69+
String PROPNAME_CALLER_EXT_CLAIM = "CALLER_EXT_CLAIM";
6270

6371
/**
6472
* Validates the current set of configuration information in the Authenticator.

src/main/java/com/ibm/cloud/sdk/core/security/AuthenticatorBase.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class AuthenticatorBase {
3535
"The %s property must be a valid integer but was %s.";
3636
public static final String ERRORMSG_ACCOUNTID_PROP_ERROR =
3737
"iamAccountId must be specified if and only if iamProfileName is specified";
38+
public static final String ERRORMSG_PROP_INVALID_BOOL =
39+
"The %s property must be a valid boolean but was '%s'. Valid values are 'true' and 'false'.";
3840

3941
/**
4042
* Returns a "Basic" Authorization header value for the specified username and password.

src/main/java/com/ibm/cloud/sdk/core/security/ConfigBasedAuthenticatorFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ protected static Authenticator createAuthenticator(Map<String, String> props) {
109109
authenticator = VpcInstanceAuthenticator.fromConfiguration(props);
110110
} else if (authType.equalsIgnoreCase(Authenticator.AUTHTYPE_MCSP)) {
111111
authenticator = MCSPAuthenticator.fromConfiguration(props);
112+
} else if (authType.equalsIgnoreCase(Authenticator.AUTHTYPE_MCSPV2)) {
113+
authenticator = MCSPV2Authenticator.fromConfiguration(props);
112114
} else if (authType.equalsIgnoreCase(Authenticator.AUTHTYPE_NOAUTH)) {
113115
authenticator = new NoAuthAuthenticator(props);
114116
} else {

src/main/java/com/ibm/cloud/sdk/core/security/MCSPAuthenticator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* (C) Copyright IBM Corp. 2023, 2024.
2+
* (C) Copyright IBM Corp. 2023, 2025.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
55
* the License. You may obtain a copy of the License at
@@ -34,7 +34,7 @@
3434
*/
3535
public class MCSPAuthenticator extends TokenRequestBasedAuthenticator<MCSPToken, MCSPTokenResponse>
3636
implements Authenticator {
37-
private static final Logger LOG = Logger.getLogger(ContainerAuthenticator.class.getName());
37+
private static final Logger LOG = Logger.getLogger(MCSPAuthenticator.class.getName());
3838
private static final String OPERATION_PATH = "/siusermgr/api/1.0/apikeys/token";
3939

4040
// Properties specific to an MCSP authenticator.

src/main/java/com/ibm/cloud/sdk/core/security/MCSPToken.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,33 @@ public MCSPToken(MCSPTokenResponse response) {
7676
}
7777
}
7878

79+
/**
80+
* This ctor will extract the access token from the specified MCSPV2TokenResponse instance,
81+
* and compute the refresh time as "80% of the timeToLive added to the issued-at time".
82+
* This means that we'll trigger the acquisition of a new token shortly before it is set to expire.
83+
* @param response the MCSPTokenResponse instance
84+
*/
85+
public MCSPToken(MCSPV2TokenResponse response) {
86+
super();
87+
this.accessToken = response.getToken();
88+
89+
// To compute the expiration time, we'll need to crack open the accessToken value
90+
// which is a JWT (Json Web Token) instance.
91+
JsonWebToken jwt = new JsonWebToken(this.accessToken);
92+
93+
Long iat = jwt.getPayload().getIssuedAt();
94+
Long exp = jwt.getPayload().getExpiresAt();
95+
96+
if (iat != null && exp != null) {
97+
long ttl = exp - iat;
98+
99+
this.expirationTime = exp;
100+
this.refreshTime = iat + (long) (0.8 * ttl);
101+
} else {
102+
throw new RuntimeException("Properties 'iat' and 'exp' MUST be present within the encoded access token");
103+
}
104+
}
105+
79106
/**
80107
* Returns true iff this object does not hold a valid access token or has one which has crossed our refresh
81108
* time. This method also updates the refresh time if it determines the token needs to be refreshed to

0 commit comments

Comments
 (0)