Skip to content

Commit

Permalink
Merge pull request #183 from hasithakn/ciba-weblink-authenticator
Browse files Browse the repository at this point in the history
[OB3] CIBA auth weblink authenticator
  • Loading branch information
amjadhifthikar authored Nov 7, 2024
2 parents 2866605 + 30cd106 commit eed4823
Show file tree
Hide file tree
Showing 29 changed files with 1,889 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,47 @@
</ServletExtension>
{% endif %}
</CIBAAuthenticationEndpointWebApp>
<CIBA>
<AuthWebLink>
{% if open_banking.identity.ciba.auth_web_link.redirect_endpoint is defined %}
<AuthenticationRedirectEndpoint>{{open_banking.identity.ciba.auth_web_link.redirect_endpoint}}</AuthenticationRedirectEndpoint>
{% else %}
<AuthenticationRedirectEndpoint>${carbon.protocol}://${carbon.host}:${carbon.management.port}/authenticationendpoint/ciba.jsp</AuthenticationRedirectEndpoint>
{% endif %}
<AllowedAuthURLParams>
{% if open_banking.identity.ciba.auth_web_link.allowed_auth_url_parameters is defined %}
{% for value in open_banking.identity.ciba.auth_web_link.allowed_auth_url_parameters %}
<Value>{{value}}</Value>
{% endfor %}
{% else %}
<Value>client_id</Value>
<Value>scope</Value>
<Value>response_type</Value>
<Value>nonce</Value>
<Value>redirect_uri</Value>
<Value>binding_message</Value>
{% endif %}
</AllowedAuthURLParams>
{% if open_banking.identity.ciba.auth_web_link.notification_provider is defined %}
<NotificationProvider>{{open_banking.identity.ciba.auth_web_link.notification_provider}}</NotificationProvider>
{% else %}
<NotificationProvider>com.wso2.openbanking.accelerator.consent.extensions.ciba.authenticator.weblink.notification.provider.SMSNotificationProvider</NotificationProvider>
{% endif %}

<SMS>
{% if open_banking.identity.ciba.auth_web_link.sms_notification.sms_url is defined %}
<SMSUrl>{{open_banking.identity.ciba.auth_web_link.sms_notification.sms_url}}</SMSUrl>
{% else %}
<SMSUrl>${carbon.protocol}://${carbon.host}:${carbon.management.port}/sample/sms</SMSUrl>
{% endif %}
<Headers>
{% for header in open_banking.identity.ciba.auth_web_link.sms_notification.header %}
<Header name="{{header.name}}" value="{{header.value}}"/>
{% endfor %}
</Headers>
</SMS>
</AuthWebLink>
</CIBA>
<AuthenticationWebApp>
{% if open_banking.identity.authentication_webapp.servlet_extension is defined %}
<ServletExtension>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,15 @@

package com.wso2.openbanking.accelerator.ciba;

import com.wso2.openbanking.accelerator.common.constant.OpenBankingConstants;
import com.wso2.openbanking.accelerator.common.exception.ConsentManagementException;
import com.wso2.openbanking.accelerator.common.util.Generated;
import com.wso2.openbanking.accelerator.consent.mgt.dao.models.DetailedConsentResource;
import com.wso2.openbanking.accelerator.identity.internal.IdentityExtensionsDataHolder;
import net.minidev.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.oauth2.RequestObjectException;
import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters;
import org.wso2.carbon.identity.openidconnect.CIBARequestObjectValidatorImpl;
Expand All @@ -34,6 +40,8 @@
*/
public class OBCIBARequestObjectValidationExtension extends CIBARequestObjectValidatorImpl {

private static final Log log = LogFactory.getLog(OBCIBARequestObjectValidationExtension.class);

/**
* Validations related to clientId, response type, exp, redirect URL, mandatory params,
* issuer, audience are done. Called after signature validation.
Expand All @@ -56,10 +64,32 @@ public boolean validateRequestObject(RequestObject initialRequestObject, OAuth2P
if (StringUtils.isEmpty(intent.getAsString(CIBAConstants.VALUE_TAG))) {
throw new RequestObjectException(CIBAConstants.INVALID_REQUEST, CIBAConstants.EMPTY_CONTENT_ERROR);
}

if (!isAuthorizableConsent(intent.getAsString("value"))) {
throw new RequestObjectException(OAuth2ErrorCodes.INVALID_REQUEST,
"Consent is not in authorizable state");
}
return validateIAMConstraints(initialRequestObject, oAuth2Parameters);
}

private boolean isAuthorizableConsent(String consentId) throws RequestObjectException {
try {
DetailedConsentResource detailedConsent = IdentityExtensionsDataHolder.getInstance()
.getConsentCoreService().getDetailedConsent(consentId);
if (log.isDebugEnabled()) {
log.debug(String.format("Consent status for consent_id %s is %s",
detailedConsent.getConsentID(), detailedConsent.getCurrentStatus()));
}
return OpenBankingConstants.AWAITING_AUTHORISATION_STATUS.equalsIgnoreCase(
detailedConsent.getCurrentStatus()) ||
OpenBankingConstants.AWAITING_FURTHER_AUTHORISATION_STATUS
.equalsIgnoreCase(detailedConsent.getCurrentStatus());
} catch (ConsentManagementException e) {
log.error("Error occurred while fetching consent_id", e);
throw new RequestObjectException(OAuth2ErrorCodes.INVALID_REQUEST,
"Error occurred while fetching consent_id", e);
}
}

/**
* Validate IAM related logic.
* @param requestObject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,53 @@
package com.wso2.openbanking.accelerator.ciba;

import com.nimbusds.jwt.JWTClaimsSet;
import com.wso2.openbanking.accelerator.common.exception.ConsentManagementException;
import com.wso2.openbanking.accelerator.consent.mgt.dao.models.DetailedConsentResource;
import com.wso2.openbanking.accelerator.consent.mgt.service.impl.ConsentCoreServiceImpl;
import com.wso2.openbanking.accelerator.identity.internal.IdentityExtensionsDataHolder;
import net.minidev.json.JSONObject;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.testng.PowerMockTestCase;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.wso2.carbon.identity.oauth2.RequestObjectException;
import org.wso2.carbon.identity.oauth2.model.OAuth2Parameters;
import org.wso2.carbon.identity.openidconnect.model.RequestObject;

import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test class for OBCIBARequestObjectValidationExtension.
*/
@PowerMockIgnore("jdk.internal.reflect.*")
@PrepareForTest({JWTClaimsSet.class, OAuth2Parameters.class, RequestObject.class, JSONObject.class})
@PrepareForTest({JWTClaimsSet.class, OAuth2Parameters.class, RequestObject.class, JSONObject.class,
IdentityExtensionsDataHolder.class})
public class OBCIBARequestObjectValidationExtensionTest extends PowerMockTestCase {

private final String dummyString = "dummyString";
private static ConsentCoreServiceImpl consentCoreServiceMock;

@BeforeClass
public void initTest() {

consentCoreServiceMock = PowerMockito.mock(ConsentCoreServiceImpl.class);
}

@BeforeMethod
private void mockStaticClasses() throws ConsentManagementException {

PowerMockito.mockStatic(IdentityExtensionsDataHolder.class);
IdentityExtensionsDataHolder mock = PowerMockito.mock(IdentityExtensionsDataHolder.class);
PowerMockito.when(IdentityExtensionsDataHolder.getInstance()).thenReturn(mock);
PowerMockito.when(IdentityExtensionsDataHolder.getInstance().getConsentCoreService())
.thenReturn(consentCoreServiceMock);
}

@Test(expectedExceptions = RequestObjectException.class, description = "Empty intent key")
public void validateRequestObjectInvalidIntentKeyTest() throws Exception {
Expand All @@ -61,6 +88,30 @@ public void validateRequestObjectInvalidIntentKeyTest() throws Exception {

}

@Test(expectedExceptions = RequestObjectException.class, description = "Consent is not in authorizable state")
public void validateRequestObjectInvalidConsentIdTest() throws Exception {

OBCIBARequestObjectValidationExtensionMock obcibaRequestObjectValidationExtensionMock =
new OBCIBARequestObjectValidationExtensionMock();

JSONObject intent = mock(JSONObject.class);

RequestObject requestObject = mock(RequestObject.class);
OAuth2Parameters oAuth2Parameters = mock(OAuth2Parameters.class);
JWTClaimsSet claimsSet = Mockito.mock(JWTClaimsSet.class);

Mockito.when(requestObject.getClaimsSet()).thenReturn(claimsSet);
Mockito.when(claimsSet.getJSONObjectClaim(Mockito.anyString())).thenReturn(intent);
when(intent.getAsString("value")).thenReturn(dummyString);

DetailedConsentResource consentResourceMock = mock(DetailedConsentResource.class);
doReturn("authorised").when(consentResourceMock).getCurrentStatus();
doReturn(consentResourceMock).when(consentCoreServiceMock).getDetailedConsent(anyString());

obcibaRequestObjectValidationExtensionMock.validateRequestObject(requestObject, oAuth2Parameters);

}

@Test(description = "success scenario")
public void validateRequestObjectValidObjectTest() throws Exception {

Expand All @@ -78,6 +129,10 @@ public void validateRequestObjectValidObjectTest() throws Exception {

when(intent.getAsString("value")).thenReturn(dummyString);

DetailedConsentResource consentResourceMock = mock(DetailedConsentResource.class);
doReturn("awaitingAuthorisation").when(consentResourceMock).getCurrentStatus();
doReturn(consentResourceMock).when(consentCoreServiceMock).getDetailedConsent(anyString());

obcibaRequestObjectValidationExtensionMock.validateRequestObject(requestObject, oAuth2Parameters);

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1489,4 +1489,97 @@ public boolean isNbfClaimMandatory() {
getConfigElementFromKey(OpenBankingConstants.MANDATE_NBF_CLAIM)).trim());
}

/**
* Method to get the CIBA authentication redirect endpoint configuration.
*
* @return ciba redirect endpoint
*/
public String getCibaAuthenticationRedirectEndpoint() {
return getConfigElementFromKey(OpenBankingConstants.CIBA_AUTHENTICATION_REDIRECT_ENDPOINT) == null ?
"" : ((String) getConfigElementFromKey(
OpenBankingConstants.CIBA_AUTHENTICATION_REDIRECT_ENDPOINT)).trim();
}

/**
* Method to get the CIBA web link allowed parameters.
*
* @return list of allowed parameters
*/
public List<String> getCibaWebLinkAllowedParams() {

List<String> allowedParamsList = new ArrayList<>();
Object configElementFromKey = getConfigElementFromKey(OpenBankingConstants.CIBA_WEB_LINK_ALLOWED_PARAMETERS);
if (configElementFromKey instanceof List) {
allowedParamsList = (List<String>) configElementFromKey;
} else {
allowedParamsList.add(configElementFromKey.toString());
}
return allowedParamsList;
}

/**
* Method to get the CIBA notification Provider
*
* @return CIBA notification Provider
*/
public String getCibaWebLinkNotificationProvider() {

return getConfigElementFromKey(OpenBankingConstants.CIBA_NOTIFICATION_PROVIDER) == null ?
"" : ((String) getConfigElementFromKey(OpenBankingConstants.CIBA_NOTIFICATION_PROVIDER)).trim();
}

/**
* Method to get the CIBA SMS notification service URL
*
* @return sms service URL
*/
public String getCibaWebLinkSMSNotificationServiceURL() {

return getConfigElementFromKey(OpenBankingConstants.CIBA_WEB_LINK_NOTIFICATION_SMS_SERVICE_URL) == null ?
"" : ((String) getConfigElementFromKey(
OpenBankingConstants.CIBA_WEB_LINK_NOTIFICATION_SMS_SERVICE_URL)).trim();
}

/**
* Method to get the CIBA web link SMS notification request headers
*
* @return A map of header name and values
*/
public Map<String, String> getCibaWebLinkSMSNotificationRequestHeaders() {

Map<String, String> headersMap = new HashMap<>();
OMElement identityElement = rootElement.getFirstChildWithName(
new QName(OpenBankingConstants.OB_CONFIG_QNAME, OpenBankingConstants.IDENTITY_CONFIG_TAG));

if (identityElement != null) {
OMElement cibaElement = identityElement.getFirstChildWithName(
new QName(OpenBankingConstants.OB_CONFIG_QNAME, OpenBankingConstants.CIBA_CONFIG_TAG));

if (cibaElement != null) {
OMElement authWebLinkElement = cibaElement.getFirstChildWithName(
new QName(OpenBankingConstants.OB_CONFIG_QNAME, OpenBankingConstants.AUTH_WEB_LINK_CONFIG_TAG));

if (authWebLinkElement != null) {
OMElement smsElement = authWebLinkElement.getFirstChildWithName(
new QName(OpenBankingConstants.OB_CONFIG_QNAME, OpenBankingConstants.SMS_CONFIG_TAG));

if (smsElement != null) {
OMElement headersElement = smsElement.getFirstChildWithName(
new QName(OpenBankingConstants.OB_CONFIG_QNAME,
OpenBankingConstants.HEADERS_CONFIG_TAG));
if (headersElement != null) {
Iterator headerElements = headersElement.getChildElements();
while (headerElements.hasNext()) {
OMElement headerElement = (OMElement) headerElements.next();
String headerName = headerElement.getAttributeValue(new QName("name"));
String headerValue = headerElement.getAttributeValue(new QName("value"));
headersMap.put(headerName, headerValue);
}
}
}
}
}
}
return headersMap;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -270,5 +270,33 @@ public class OpenBankingConstants {
public static final String DOT_SEPARATOR = ".";
public static final String MANDATE_NBF_CLAIM = "Identity.RequestObject.MandateNBF";

// CIBA Constants
public static final String CIBA_AUTHENTICATION_REDIRECT_ENDPOINT =
"Identity.CIBA.AuthWebLink.AuthenticationRedirectEndpoint";
public static final String CIBA_WEB_LINK_ALLOWED_PARAMETERS =
"Identity.CIBA.AuthWebLink.AllowedAuthURLParams.Value";
public static final String CIBA_NOTIFICATION_PROVIDER = "Identity.CIBA.AuthWebLink.NotificationProvider";
public static final String AUTH_REQ_ID = "auth_req_id";
public static final String CIBA_WEB_AUTH_LINK_PARAM = "ciba_web_auth_link";
public static final String CIBA_AUTH_CODE_RESPONSE_TYPE = "cibaAuthCode";

// CIBA SMS Constants
public static final String CIBA_WEB_LINK_NOTIFICATION_SMS_SERVICE_URL =
"Identity.CIBA.AuthWebLink.SMS.SMSUrl";
public static final String IDENTITY_CONFIG_TAG = "Identity";
public static final String CIBA_CONFIG_TAG = "CIBA";
public static final String AUTH_WEB_LINK_CONFIG_TAG = "AuthWebLink";
public static final String SMS_CONFIG_TAG = "SMS";
public static final String HEADERS_CONFIG_TAG = "Headers";

// Accelerator default consent statuses
public static final String AUTHORISED_STATUS = "authorised";
public static final String REJECTED_STATUS = "rejected";
public static final String AWAITING_AUTHORISATION_STATUS = "awaitingAuthorisation";
public static final String AWAITING_FURTHER_AUTHORISATION_STATUS = "awaitingFurtherAuthorisation";
public static final String CREATED_AUTHORISATION_RESOURCE_STATE = "created";
public static final String MULTI_AUTH_AUTHORISATION_TYPE = "multi-authorization";
public static final String CARBON_SUPER_TENANT_DOMAIN = "@carbon.super";

}

Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static String getCarbonHome() {
@Generated(message = "Ignoring because ServerConfiguration cannot be mocked")
public static String getCarbonPort() {

int offset = Integer.parseInt(ServerConfiguration.getInstance().getFirstProperty("Offset"));
int offset = Integer.parseInt(ServerConfiguration.getInstance().getFirstProperty("Ports.Offset"));
return String.valueOf(9443 + offset);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,4 +476,32 @@ public void testNbfClaimMandatory() {
boolean nbfClaimMandatory = openBankingConfigParser.isNbfClaimMandatory();
Assert.assertTrue(nbfClaimMandatory);
}

@Test(priority = 37)
public void testCibaWebLinkConfigs() {
String dummyConfigFile = absolutePathForTestResources + "/open-banking.xml";
OpenBankingConfigParser openBankingConfigParser = OpenBankingConfigParser.getInstance(dummyConfigFile);

List<String> cibaWebLinkAllowedParams = openBankingConfigParser.getCibaWebLinkAllowedParams();
Assert.assertEquals(cibaWebLinkAllowedParams.size(), 6);
Assert.assertEquals(cibaWebLinkAllowedParams.get(0), "client_id");
Assert.assertEquals(openBankingConfigParser.getCibaWebLinkNotificationProvider(),
"com.wso2.openbanking.accelerator.consent.extensions.ciba.authenticator.weblink." +
"notification.provider.SMSNotificationProvider");
Assert.assertEquals(openBankingConfigParser.getCibaAuthenticationRedirectEndpoint(),
"https://localhost:9446/authenticationendpoint/ciba.jsp");
}

@Test(priority = 38)
public void testCibaWebLinkSMSConfigs() {
String dummyConfigFile = absolutePathForTestResources + "/open-banking.xml";
OpenBankingConfigParser openBankingConfigParser = OpenBankingConfigParser.getInstance(dummyConfigFile);

String cibaWebLinkAllowedParams = openBankingConfigParser.getCibaWebLinkSMSNotificationServiceURL();
Assert.assertEquals(cibaWebLinkAllowedParams, "https://localhost:9446/sample/sms");
Map<String, String> headerMap = openBankingConfigParser.getCibaWebLinkSMSNotificationRequestHeaders();
Assert.assertEquals(headerMap.get("Authorization"), "abc");
Assert.assertEquals(headerMap.get("Accept"), "application/json");
}

}
Loading

0 comments on commit eed4823

Please sign in to comment.