Skip to content

Commit a28afe4

Browse files
authored
Merge pull request #880 from imesh94/fix/app-scope-restriction
[OB3] Restrict token scopes based on scopes allowed for application
2 parents 9befd07 + 2161085 commit a28afe4

File tree

8 files changed

+190
-11
lines changed

8 files changed

+190
-11
lines changed

open-banking-accelerator/accelerators/ob-is/carbon-home/repository/resources/conf/templates/repository/conf/open-banking.xml.j2

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,22 @@
110110
{% else %}
111111
<ClientTransportCertAsHeaderEnabled>true</ClientTransportCertAsHeaderEnabled>
112112
{% endif %}
113+
{% if open_banking.identity.application_scope_restriction is defined %}
114+
<ApplicationScopeRestriction>
115+
<Enabled>{{ open_banking.identity.application_scope_restriction.enabled | default(false) }}</Enabled>
116+
{% if open_banking.identity.application_scope_restriction.grant_types is defined %}
117+
<RestrictedGrantTypes>
118+
{% for grant_type in open_banking.identity.application_scope_restriction.grant_types %}
119+
<GrantType>{{ grant_type }}</GrantType>
120+
{% endfor %}
121+
</RestrictedGrantTypes>
122+
{% endif %}
123+
</ApplicationScopeRestriction>
124+
{% else %}
125+
<ApplicationScopeRestriction>
126+
<Enabled>false</Enabled>
127+
</ApplicationScopeRestriction>
128+
{% endif %}
113129
{% if open_banking.identity.signing_certificate_kid is defined %}
114130
<SigningCertificateKid>{{open_banking.identity.signing_certificate_kid}}</SigningCertificateKid>
115131
{% endif %}

open-banking-accelerator/components/com.wso2.openbanking.accelerator.identity/src/main/java/com/wso2/openbanking/accelerator/identity/grant/type/handlers/OBAuthorizationCodeGrantHandler.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package com.wso2.openbanking.accelerator.identity.grant.type.handlers;
2020

2121
import com.wso2.openbanking.accelerator.common.exception.OpenBankingException;
22+
import com.wso2.openbanking.accelerator.identity.util.IdentityCommonConstants;
2223
import com.wso2.openbanking.accelerator.identity.util.IdentityCommonUtil;
2324
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
2425
import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO;
@@ -34,8 +35,14 @@ public class OBAuthorizationCodeGrantHandler extends AuthorizationCodeGrantHandl
3435
public OAuth2AccessTokenRespDTO issue(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception {
3536

3637
try {
37-
if (IdentityCommonUtil.getRegulatoryFromSPMetaData(tokReqMsgCtx.getOauth2AccessTokenReqDTO()
38-
.getClientId())) {
38+
String clientId = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId();
39+
if (IdentityCommonUtil.getRegulatoryFromSPMetaData(clientId)) {
40+
// Apply application scope restrictions if enabled
41+
if (IdentityCommonUtil.isAppScopeRestrictionEnabledForGrant(
42+
IdentityCommonConstants.AUTHORIZATION_CODE)) {
43+
tokReqMsgCtx.setScope(IdentityCommonUtil.retainAllowedScopesForApplication(
44+
tokReqMsgCtx.getScope(), clientId));
45+
}
3946
OAuth2AccessTokenRespDTO oAuth2AccessTokenRespDTO = super.issue(tokReqMsgCtx);
4047
executeInitialStep(oAuth2AccessTokenRespDTO, tokReqMsgCtx);
4148
tokReqMsgCtx.setScope(IdentityCommonUtil.removeInternalScopes(tokReqMsgCtx.getScope()));

open-banking-accelerator/components/com.wso2.openbanking.accelerator.identity/src/main/java/com/wso2/openbanking/accelerator/identity/grant/type/handlers/OBClientCredentialsGrantHandler.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package com.wso2.openbanking.accelerator.identity.grant.type.handlers;
2020

2121
import com.wso2.openbanking.accelerator.common.exception.OpenBankingException;
22+
import com.wso2.openbanking.accelerator.identity.util.IdentityCommonConstants;
2223
import com.wso2.openbanking.accelerator.identity.util.IdentityCommonUtil;
2324
import org.wso2.carbon.identity.oauth.common.exception.InvalidOAuthClientException;
2425
import org.wso2.carbon.identity.oauth.dao.OAuthAppDO;
@@ -39,16 +40,21 @@ public class OBClientCredentialsGrantHandler extends ClientCredentialsGrantHandl
3940
public OAuth2AccessTokenRespDTO issue(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception {
4041

4142
try {
42-
if (IdentityCommonUtil.getRegulatoryFromSPMetaData(tokReqMsgCtx.getOauth2AccessTokenReqDTO()
43-
.getClientId())) {
43+
String clientId = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId();
44+
if (IdentityCommonUtil.getRegulatoryFromSPMetaData(clientId)) {
4445
if (IdentityCommonUtil.getDCRModifyResponseConfig() && tokReqMsgCtx.getScope().length > 0 &&
4546
Arrays.asList(tokReqMsgCtx.getScope()).contains(IdentityCommonUtil.getDCRScope())) {
4647
long validityPeriod = 999999999;
47-
OAuthAppDO oAuthAppDO = OAuth2Util
48-
.getAppInformationByClientId(tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId());
48+
OAuthAppDO oAuthAppDO = OAuth2Util.getAppInformationByClientId(clientId);
4949
oAuthAppDO.setApplicationAccessTokenExpiryTime(validityPeriod);
5050
tokReqMsgCtx.setValidityPeriod(validityPeriod);
5151
}
52+
// Apply application scope restrictions if enabled
53+
if (IdentityCommonUtil.isAppScopeRestrictionEnabledForGrant(
54+
IdentityCommonConstants.CLIENT_CREDENTIALS)) {
55+
tokReqMsgCtx.setScope(IdentityCommonUtil.retainAllowedScopesForApplication(
56+
tokReqMsgCtx.getScope(), clientId));
57+
}
5258
OAuth2AccessTokenRespDTO oAuth2AccessTokenRespDTO = super.issue(tokReqMsgCtx);
5359
executeInitialStep(oAuth2AccessTokenRespDTO, tokReqMsgCtx);
5460
tokReqMsgCtx.setScope(IdentityCommonUtil.removeInternalScopes(tokReqMsgCtx.getScope()));

open-banking-accelerator/components/com.wso2.openbanking.accelerator.identity/src/main/java/com/wso2/openbanking/accelerator/identity/grant/type/handlers/OBPasswordGrantHandler.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package com.wso2.openbanking.accelerator.identity.grant.type.handlers;
2020

2121
import com.wso2.openbanking.accelerator.common.exception.OpenBankingException;
22+
import com.wso2.openbanking.accelerator.identity.util.IdentityCommonConstants;
2223
import com.wso2.openbanking.accelerator.identity.util.IdentityCommonUtil;
2324
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception;
2425
import org.wso2.carbon.identity.oauth2.dto.OAuth2AccessTokenRespDTO;
@@ -34,8 +35,13 @@ public class OBPasswordGrantHandler extends PasswordGrantHandler {
3435
public OAuth2AccessTokenRespDTO issue(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception {
3536

3637
try {
37-
if (IdentityCommonUtil.getRegulatoryFromSPMetaData(tokReqMsgCtx.getOauth2AccessTokenReqDTO()
38-
.getClientId())) {
38+
String clientId = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId();
39+
if (IdentityCommonUtil.getRegulatoryFromSPMetaData(clientId)) {
40+
// Apply application scope restrictions if enabled
41+
if (IdentityCommonUtil.isAppScopeRestrictionEnabledForGrant(IdentityCommonConstants.PASSWORD)) {
42+
tokReqMsgCtx.setScope(IdentityCommonUtil.retainAllowedScopesForApplication(
43+
tokReqMsgCtx.getScope(), clientId));
44+
}
3945
OAuth2AccessTokenRespDTO oAuth2AccessTokenRespDTO = super.issue(tokReqMsgCtx);
4046
executeInitialStep(oAuth2AccessTokenRespDTO, tokReqMsgCtx);
4147
tokReqMsgCtx.setScope(IdentityCommonUtil.removeInternalScopes(tokReqMsgCtx.getScope()));

open-banking-accelerator/components/com.wso2.openbanking.accelerator.identity/src/main/java/com/wso2/openbanking/accelerator/identity/grant/type/handlers/OBRefreshGrantHandler.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,13 @@ public class OBRefreshGrantHandler extends RefreshGrantHandler {
4545
public OAuth2AccessTokenRespDTO issue(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception {
4646

4747
try {
48-
if (IdentityCommonUtil.getRegulatoryFromSPMetaData(tokReqMsgCtx.getOauth2AccessTokenReqDTO()
49-
.getClientId())) {
48+
String clientId = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getClientId();
49+
if (IdentityCommonUtil.getRegulatoryFromSPMetaData(clientId)) {
50+
// Apply application scope restrictions if enabled
51+
if (IdentityCommonUtil.isAppScopeRestrictionEnabledForGrant(IdentityCommonConstants.REFRESH_TOKEN)) {
52+
tokReqMsgCtx.setScope(IdentityCommonUtil.retainAllowedScopesForApplication(
53+
tokReqMsgCtx.getScope(), clientId));
54+
}
5055
OAuth2AccessTokenRespDTO oAuth2AccessTokenRespDTO = super.issue(tokReqMsgCtx);
5156
executeInitialStep(oAuth2AccessTokenRespDTO, tokReqMsgCtx);
5257
tokReqMsgCtx.setScope(IdentityCommonUtil.removeInternalScopes(tokReqMsgCtx.getScope()));

open-banking-accelerator/components/com.wso2.openbanking.accelerator.identity/src/main/java/com/wso2/openbanking/accelerator/identity/internal/IdentityExtensionsDataHolder.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import java.security.KeyStore;
5050
import java.util.ArrayList;
5151
import java.util.Arrays;
52+
import java.util.Collections;
5253
import java.util.HashMap;
5354
import java.util.List;
5455
import java.util.Map;
@@ -71,6 +72,7 @@ public class IdentityExtensionsDataHolder {
7172
private Map<String, Object> configurationMap;
7273
private Map<String, Map<String, Object>> dcrRegistrationConfigMap;
7374
private List<OBIdentityFilterValidator> tokenValidators = new ArrayList<>();
75+
private List<String> scopeRestrictedGrantTypes = new ArrayList<>();
7476
private DefaultTokenFilter defaultTokenFilter;
7577
private RegistrationValidator registrationValidator;
7678
private ClaimProvider claimProvider;
@@ -217,6 +219,7 @@ public void setOpenBankingConfigurationService(
217219
.getConfigurations().get("IdentityCache.CacheAccessExpiry"));
218220
setIdentityCacheModifiedExpiry((String) openBankingConfigurationService
219221
.getConfigurations().get("IdentityCache.CacheModifiedExpiry"));
222+
setScopeRestrictedGrantTypes(extractScopeRestrictedGrantTypes());
220223

221224
Map<String, String> authenticationWorkers = openBankingConfigurationService.getAuthenticationWorkers();
222225
authenticationWorkers.forEach((key, value) ->
@@ -237,6 +240,16 @@ public void setTokenFilterValidators() {
237240
}
238241
}
239242

243+
public List<String> getScopeRestrictedGrantTypes() {
244+
245+
return scopeRestrictedGrantTypes;
246+
}
247+
248+
public void setScopeRestrictedGrantTypes(List<String> scopeRestrictedGrantTypes) {
249+
250+
this.scopeRestrictedGrantTypes = scopeRestrictedGrantTypes;
251+
}
252+
240253
private List extractTokenFilterValidators() {
241254

242255
Object validators = configurationMap.get(IdentityCommonConstants.TOKEN_VALIDATORS);
@@ -252,6 +265,26 @@ private List extractTokenFilterValidators() {
252265
}
253266
}
254267

268+
/**
269+
* Get grant types which the token scopes should be restricted based on scopes allowed for the application.
270+
*
271+
* @return List of grant types
272+
*/
273+
public List<String> extractScopeRestrictedGrantTypes() {
274+
275+
Object grantTypes = configurationMap.get(IdentityCommonConstants.APPLICATION_SCOPE_RESTRICTED_GRANT_TYPES);
276+
277+
if (grantTypes instanceof List) {
278+
return (List<String>) grantTypes;
279+
}
280+
281+
if (grantTypes instanceof String) {
282+
return Collections.singletonList((String) grantTypes);
283+
}
284+
285+
return Collections.emptyList();
286+
}
287+
255288
public DefaultTokenFilter getDefaultTokenFilterImpl() {
256289

257290
return defaultTokenFilter;

open-banking-accelerator/components/com.wso2.openbanking.accelerator.identity/src/main/java/com/wso2/openbanking/accelerator/identity/util/IdentityCommonConstants.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ public class IdentityCommonConstants {
7676
"Identity.TokenSubject.RemoveUserStoreDomainFromSubject";
7777
public static final String REMOVE_TENANT_DOMAIN_FROM_SUBJECT =
7878
"Identity.TokenSubject.RemoveTenantDomainFromSubject";
79+
public static final String APPLICATION_SCOPE_RESTRICTION_ENABLED = "Identity.ApplicationScopeRestriction.Enabled";
80+
public static final String APPLICATION_SCOPE_RESTRICTED_GRANT_TYPES =
81+
"Identity.ApplicationScopeRestriction.RestrictedGrantTypes.GrantType";
82+
7983

8084
public static final String AUTH_SERVLET_EXTENSION = "Identity.Extensions.AuthenticationWebApp.ServletExtension";
8185
public static final String CONSENT_ID_CLAIM_NAME = "Identity.ConsentIDClaimName";
@@ -90,6 +94,9 @@ public class IdentityCommonConstants {
9094
public static final String DCR_INTERNAL_SCOPE = "OB_DCR";
9195
public static final String OPENID_SCOPE = "openid";
9296
public static final String CLIENT_CREDENTIALS = "client_credentials";
97+
public static final String AUTHORIZATION_CODE = "authorization_code";
98+
public static final String REFRESH_TOKEN = "refresh_token";
99+
public static final String PASSWORD = "password";
93100
public static final String CARBON_SUPER = "carbon.super";
94101
public static final String REGISTRATION_ACCESS_TOKEN = "registration_access_token";
95102
public static final String REGISTRATION_CLIENT_URI = "registration_client_uri";

open-banking-accelerator/components/com.wso2.openbanking.accelerator.identity/src/main/java/com/wso2/openbanking/accelerator/identity/util/IdentityCommonUtil.java

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,12 @@
7373
import java.util.Base64;
7474
import java.util.Date;
7575
import java.util.HashMap;
76+
import java.util.HashSet;
7677
import java.util.LinkedList;
7778
import java.util.List;
7879
import java.util.Map;
7980
import java.util.Optional;
81+
import java.util.Set;
8082
import java.util.stream.Collectors;
8183

8284
import javax.servlet.http.HttpServletRequest;
@@ -124,7 +126,42 @@ public static String[] removeInternalScopes(String[] scopes) {
124126
}
125127

126128
/**
127-
* Cache regulatory property if exists.
129+
* Filter the requested scopes by removing scopes which are not allowed for the application.
130+
*
131+
* @param scopes Requested scopes
132+
* @param clientId OAuth client ID
133+
* @return filtered scopes by removing the scopes not allowed for the application
134+
*/
135+
public static String[] retainAllowedScopesForApplication(String[] scopes, String clientId)
136+
throws OpenBankingException {
137+
138+
if (scopes == null || scopes.length == 0) {
139+
return scopes;
140+
}
141+
String allowedScopes = getScopeFromSPMetaData(clientId);
142+
143+
if (StringUtils.isBlank(allowedScopes)) {
144+
if (log.isDebugEnabled()) {
145+
log.debug("No allowed scopes configured for application client_id: "
146+
+ clientId.replaceAll("[\r\n]", "") + ". All requested scopes will be removed.");
147+
}
148+
return new String[0];
149+
}
150+
151+
Set<String> allowedScopeSet = new HashSet<>(Arrays.asList(allowedScopes.split(" ")));
152+
String[] filteredScopes = Arrays.stream(scopes).filter(allowedScopeSet::contains).toArray(String[]::new);
153+
154+
if (log.isDebugEnabled()) {
155+
log.debug("Filtered scopes for application client_id: " + clientId.replaceAll("[\r\n]", "")
156+
+ ". Requested scopes: " + Arrays.toString(scopes).replaceAll("[\r\n]", "")
157+
+ " | Filtered scopes: " + Arrays.toString(filteredScopes).replaceAll("[\r\n]", ""));
158+
}
159+
160+
return filteredScopes;
161+
}
162+
163+
/**
164+
* Cache regulatory property if exists
128165
*
129166
* @param clientId ClientId of the application
130167
* @return the regulatory property from cache if exists or from sp metadata
@@ -163,6 +200,41 @@ public static synchronized boolean getRegulatoryFromSPMetaData(String clientId)
163200
}
164201
}
165202

203+
/**
204+
* Get scopes for the application and add to cache.
205+
*
206+
* @param clientId clientId of the application
207+
* @return the scopes from cache if exists or from sp metadata
208+
* @throws OpenBankingException
209+
*/
210+
@Generated(message = "Excluding from code coverage since it requires a cache initialization/service call")
211+
public static synchronized String getScopeFromSPMetaData(String clientId) throws OpenBankingException {
212+
213+
if (StringUtils.isNotEmpty(clientId)) {
214+
215+
if (identityCache == null) {
216+
log.debug("Creating new Identity cache");
217+
identityCache = new IdentityCache();
218+
}
219+
220+
IdentityCacheKey identityCacheKey = IdentityCacheKey.of(clientId
221+
.concat("_").concat(IdentityCommonConstants.SCOPE));
222+
Object scope = null;
223+
224+
scope = identityCache.getFromCacheOrRetrieve(identityCacheKey,
225+
() -> new IdentityCommonHelper().getAppPropertyFromSPMetaData(clientId,
226+
IdentityCommonConstants.SCOPE));
227+
228+
if (scope != null) {
229+
return scope.toString();
230+
} else {
231+
throw new OpenBankingException("Unable to retrieve scope from sp metadata");
232+
}
233+
} else {
234+
throw new OpenBankingException(IdentityCommonConstants.CLIENT_ID_ERROR);
235+
}
236+
}
237+
166238
public static ServiceProviderProperty getServiceProviderProperty(String spPropertyName, String spPropertyValue) {
167239

168240
ServiceProviderProperty serviceProviderProperty = new ServiceProviderProperty();
@@ -468,4 +540,31 @@ public static Date parseStringToDate(String dateString) throws ParseException {
468540
return dateFormat.parse(dateString);
469541
}
470542

543+
/**
544+
* Check whether application scope restriction is enabled for the given grant type.
545+
*
546+
* @param grantType OAuth2 grant type
547+
* @return true if scope restriction is enabled for the given grant type, false otherwise
548+
*/
549+
public static boolean isAppScopeRestrictionEnabledForGrant(String grantType) {
550+
551+
boolean isRestrictionEnabled = Boolean.parseBoolean(String.valueOf(
552+
IdentityExtensionsDataHolder.getInstance().getConfigurationMap().getOrDefault(
553+
IdentityCommonConstants.APPLICATION_SCOPE_RESTRICTION_ENABLED, false)
554+
));
555+
556+
if (!isRestrictionEnabled || StringUtils.isBlank(grantType)) {
557+
return false;
558+
}
559+
560+
boolean isEnabledForGrant = IdentityExtensionsDataHolder.getInstance().getScopeRestrictedGrantTypes().stream()
561+
.anyMatch(configuredGrantType -> configuredGrantType.equalsIgnoreCase(grantType));
562+
563+
if (log.isDebugEnabled()) {
564+
log.debug("Application scope restriction evaluation for grant type: "
565+
+ grantType.replaceAll("[\r\n]", "") + ". Enabled for grant: " + isEnabledForGrant);
566+
}
567+
return isEnabledForGrant;
568+
}
569+
471570
}

0 commit comments

Comments
 (0)