Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GraalJS support for conditional authentication functions #152

Merged
merged 18 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,18 @@
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
</suiteXmlFiles>
<classpathDependencyExcludes>
<classpathDependencyExclude>org.ops4j.pax.logging</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.util.EntityUtils;
import org.graalvm.polyglot.HostAccess;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
Expand All @@ -41,6 +42,7 @@
import java.net.SocketTimeoutException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

Expand All @@ -60,14 +62,23 @@ public class CallAnalyticsFunctionImpl extends AbstractAnalyticsFunction impleme
private static final String PARAM_INPUT_STREAM = "InputStream";

@Override
@HostAccess.Export
public void callAnalytics(Map<String, String> metadata,
Map<String, Object> payloadData, Map<String, Object> eventHandlers) {

/*
* Here, we need to clone the parameters since, even though we're accessing the parameters as Map objects,
* these may be instances of child classes of Map class (Script Engine specific implementations).
* When the AsyncProcess is executed, the objects will not be available if the relevant Script Engine is closed.
* Eg: Polyglot Map (Map implementation from GraalJS) will be unavailable when the Polyglot Context is closed.
*/
Map<String, String> metadataMap = new HashMap<>(metadata);
Map<String, Object> payloadDataMap = new HashMap<>(payloadData);
AsyncProcess asyncProcess = new AsyncProcess((authenticationContext, asyncReturn) -> {

String appName = metadata.get(PARAM_APP_NAME);
String inputStream = metadata.get(PARAM_INPUT_STREAM);
String receiverUrl = metadata.get(PARAM_EP_URL);
String appName = metadataMap.get(PARAM_APP_NAME);
String inputStream = metadataMap.get(PARAM_INPUT_STREAM);
String receiverUrl = metadataMap.get(PARAM_EP_URL);
String targetPath;
try {
if (appName != null && inputStream != null) {
Expand All @@ -92,7 +103,7 @@ public void callAnalytics(Map<String, String> metadata,

JSONObject jsonObject = new JSONObject();
JSONObject event = new JSONObject();
for (Map.Entry<String, Object> dataElements : payloadData.entrySet()) {
for (Map.Entry<String, Object> dataElements : payloadDataMap.entrySet()) {
event.put(dataElements.getKey(), dataElements.getValue());
}
jsonObject.put("event", event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.graalvm.polyglot.HostAccess;
import org.json.simple.JSONObject;
import org.wso2.carbon.identity.application.authentication.framework.config.model.graph.js.JsAuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.FrameworkException;
Expand All @@ -34,6 +35,7 @@

import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import static org.apache.http.HttpHeaders.CONTENT_TYPE;
Expand All @@ -48,12 +50,23 @@ public class PublishToAnalyticsFunctionImpl extends AbstractAnalyticsFunction im
private static final String PARAM_INPUT_STREAM = "InputStream";

@Override
@HostAccess.Export
public void publishToAnalytics(Map<String, String> metadata, Map<String, Object> payloadData,
JsAuthenticationContext context) {

String appName = metadata.get(PARAM_APP_NAME);
String inputStream = metadata.get(PARAM_INPUT_STREAM);
String targetPath = metadata.get(PARAM_EP_URL);
/*
* Here, we need to clone the parameters since, even though we're accessing the parameters as Map objects,
* these may be instances of child classes of Map class (Script Engine specific implementations).
* When the AsyncProcess is executed, the objects will not be available if the relevant Script Engine is closed.
* Eg: Polyglot Map (Map implementation from GraalJS) will be unavailable when the Polyglot Context is closed.
*/
Map<String, String> metadataMap = new HashMap<>(metadata);
Map<String, Object> payloadDataMap = new HashMap<>(payloadData);
String contextIdentifier = context.getWrapped().getContextIdentifier();

String appName = metadataMap.get(PARAM_APP_NAME);
String inputStream = metadataMap.get(PARAM_INPUT_STREAM);
String targetPath = metadataMap.get(PARAM_EP_URL);
String epUrl = null;
try {
if (appName != null && inputStream != null) {
Expand All @@ -64,7 +77,7 @@ public void publishToAnalytics(Map<String, String> metadata, Map<String, Object>
LOG.error("Target path cannot be found.");
return;
}
String tenantDomain = context.getContext().getTenantDomain();
String tenantDomain = context.getWrapped().getTenantDomain();
String targetHostUrl = CommonUtils.getConnectorConfig(AnalyticsEngineConfigImpl.RECEIVER, tenantDomain);
if (targetHostUrl == null) {
LOG.error("Target host cannot be found.");
Expand All @@ -78,7 +91,7 @@ public void publishToAnalytics(Map<String, String> metadata, Map<String, Object>

JSONObject jsonObject = new JSONObject();
JSONObject event = new JSONObject();
for (Map.Entry<String, Object> dataElements : payloadData.entrySet()) {
for (Map.Entry<String, Object> dataElements : payloadDataMap.entrySet()) {
event.put(dataElements.getKey(), dataElements.getValue());
}
jsonObject.put("event", event);
Expand All @@ -105,11 +118,11 @@ public void completed(final HttpResponse response) {
if (responseCode == 200) {
if (LOG.isDebugEnabled()) {
LOG.debug("Successfully published data to the analytics for session data key: " +
context.getContext().getContextIdentifier());
contextIdentifier);
}
} else {
LOG.error("Error while publishing data to analytics engine for session data key: " +
context.getContext().getContextIdentifier() + ". Request completed successfully. " +
contextIdentifier + ". Request completed successfully. " +
"But response code was not 200");
}
}
Expand All @@ -118,25 +131,25 @@ public void completed(final HttpResponse response) {
public void failed(final Exception ex) {

LOG.error("Error while publishing data to analytics engine for session data key: " +
context.getContext().getContextIdentifier() + ". Request failed with: " + ex);
contextIdentifier + ". Request failed with: " + ex);
}

@Override
public void cancelled() {

LOG.error("Error while publishing data to analytics engine for session data key: " +
context.getContext().getContextIdentifier() + ". Request canceled.");
contextIdentifier + ". Request canceled.");
}
});
}

} catch (IOException e) {
LOG.error("Error while calling analytics engine for tenant: " + context.getContext().getTenantDomain(), e);
LOG.error("Error while calling analytics engine for tenant: " + context.getWrapped().getTenantDomain(), e);
} catch (IdentityEventException e) {
LOG.error("Error while preparing authentication information for tenant: " + context.getContext()
LOG.error("Error while preparing authentication information for tenant: " + context.getWrapped()
.getTenantDomain(), e);
} catch (FrameworkException e) {
LOG.error("Error while building client to invoke analytics engine for tenant: " + context.getContext()
LOG.error("Error while building client to invoke analytics engine for tenant: " + context.getWrapped()
.getTenantDomain(), e);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,6 @@ public class CallAnalyticsFunctionImplTest extends JsSequenceHandlerAbstractTest
@BeforeMethod
protected void setUp() throws Exception {

super.setUp();

CarbonConstants.ENABLE_LEGACY_AUTHZ_RUNTIME = true;
sequenceHandlerRunner.registerJsFunction("callAnalytics", new CallAnalyticsFunctionImpl());
UserRealm userRealm = realmService.getTenantUserRealm(-1234);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!--
~ Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com).
~
~ WSO2 LLC. licenses this file to you under the Apache License,
~ Version 2.0 (the "License"); you may not use this file except
~ in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name="Identity-connector-analytics-functions-validation-test-suite">
<test name="call-analytics-nashorn" parallel="false">
<parameter name="scriptEngine" value="nashorn"/>
<classes>
<class name="org.wso2.carbon.identity.conditional.auth.functions.analytics.CallAnalyticsFunctionImplTest"/>
</classes>
</test>
<test name="call-analytics-graaljs" parallel="false">
shanggeeth marked this conversation as resolved.
Show resolved Hide resolved
<parameter name="scriptEngine" value="graaljs"/>
<classes>
<class name="org.wso2.carbon.identity.conditional.auth.functions.analytics.CallAnalyticsFunctionImplTest"/>
</classes>
</test>
</suite>
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,18 @@
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
</suiteXmlFiles>
<classpathDependencyExcludes>
<classpathDependencyExclude>org.ops4j.pax.logging</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.graalvm.polyglot.HostAccess;
import org.json.simple.JSONObject;
import org.wso2.carbon.identity.application.authentication.framework.AsyncProcess;
import org.wso2.carbon.identity.application.authentication.framework.AsyncReturn;
Expand Down Expand Up @@ -63,6 +64,7 @@
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
Expand Down Expand Up @@ -122,13 +124,22 @@ public CallChoreoFunctionImpl() {
}

@Override
@HostAccess.Export
public void callChoreo(Map<String, String> connectionMetaData, Map<String, Object> payloadData,
Map<String, Object> eventHandlers) {

/*
* Here, we need to clone the parameters since, even though we're accessing the parameters as Map objects,
* these may be instances of child classes of Map class (Script Engine specific implementations).
* When the AsyncProcess is executed, the objects will not be available if the relevant Script Engine is closed.
* Eg: Polyglot Map (Map implementation from GraalJS) will be unavailable when the Polyglot Context is closed.
*/
Map<String, String> connectionMetaDataMap = new HashMap<>(connectionMetaData);
shanggeeth marked this conversation as resolved.
Show resolved Hide resolved
Map<String, Object> payloadDataMap = new HashMap<>(payloadData);
AsyncProcess asyncProcess = new AsyncProcess((authenticationContext, asyncReturn) -> {
LOG.info("Starting the callChoreo function for session data key: " +
authenticationContext.getContextIdentifier());
String epUrl = connectionMetaData.get(URL_VARIABLE_NAME);
String epUrl = connectionMetaDataMap.get(URL_VARIABLE_NAME);
try {
if (!isValidChoreoDomain(epUrl)) {
LOG.error("Provided Url does not contain a configured choreo domain. Invalid Url: " + epUrl);
Expand All @@ -138,7 +149,7 @@ public void callChoreo(Map<String, String> connectionMetaData, Map<String, Objec

String tenantDomain = authenticationContext.getTenantDomain();
AccessTokenRequestHelper accessTokenRequestHelper = new AccessTokenRequestHelper(
connectionMetaData, asyncReturn, authenticationContext, payloadData);
connectionMetaDataMap, asyncReturn, authenticationContext, payloadDataMap);
String accessToken = choreoAccessTokenCache.getValueFromCache(accessTokenRequestHelper.getConsumerKey(),
tenantDomain);
if (StringUtils.isNotEmpty(accessToken) && !isTokenExpired(accessToken)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,6 @@ public class CallChoreoFunctionImplTest extends JsSequenceHandlerAbstractTest {
@BeforeMethod
protected void setUp() throws Exception {

super.setUp();

CarbonConstants.ENABLE_LEGACY_AUTHZ_RUNTIME = true;
sequenceHandlerRunner.registerJsFunction("callChoreo", new CallChoreoFunctionImpl());
UserRealm userRealm = realmService.getTenantUserRealm(-1234);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!--
~ Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com).
~
~ WSO2 LLC. licenses this file to you under the Apache License,
~ Version 2.0 (the "License"); you may not use this file except
~ in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing,
~ software distributed under the License is distributed on an
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
~ KIND, either express or implied. See the License for the
~ specific language governing permissions and limitations
~ under the License.
-->

<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name="Identity-connector-choreo-functions-validation-test-suite">
<test name="call-choreo-nashorn" parallel="false">
<parameter name="scriptEngine" value="nashorn"/>
<classes>
<class name="org.wso2.carbon.identity.conditional.auth.functions.choreo.CallChoreoFunctionImplTest"/>
</classes>
</test>
<test name="call-choreo-graaljs" parallel="false">
<parameter name="scriptEngine" value="graaljs"/>
<classes>
<class name="org.wso2.carbon.identity.conditional.auth.functions.choreo.CallChoreoFunctionImplTest"/>
</classes>
</test>
</suite>
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,18 @@
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
</suiteXmlFiles>
<classpathDependencyExcludes>
<classpathDependencyExclude>org.ops4j.pax.logging</classpathDependencyExclude>
</classpathDependencyExcludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.util.EntityUtils;
import org.graalvm.polyglot.HostAccess;
import org.json.JSONException;
import org.json.JSONObject;
import org.wso2.carbon.identity.application.authentication.framework.AsyncProcess;
Expand Down Expand Up @@ -66,8 +67,10 @@ public CallElasticFunctionImpl() {
}

@Override
@HostAccess.Export
public void callElastic(Map<String, String> params, Map<String, Object> eventHandlers) {

Map<String, String> paramsMap = new HashMap<>(params);
AsyncProcess asyncProcess = new AsyncProcess((authenticationContext, asyncReturn) -> {

try {
Expand All @@ -78,13 +81,13 @@ public void callElastic(Map<String, String> params, Map<String, Object> eventHan
throw new FrameworkException("Elasticsearch host cannot be found.");
}

HttpPost request = new HttpPost(elasticConfigProvider.getElasticSearchUrl(targetHostUrl,params));
HttpPost request = new HttpPost(elasticConfigProvider.getElasticSearchUrl(targetHostUrl,paramsMap));

request.setHeader(ACCEPT, TYPE_APPLICATION_JSON);
request.setHeader(CONTENT_TYPE, TYPE_APPLICATION_JSON);
handleAuthentication(request, authenticationContext.getTenantDomain());

String query = elasticConfigProvider.getQuery(params);
String query = elasticConfigProvider.getQuery(paramsMap);
request.setEntity(new StringEntity(query, StandardCharsets.UTF_8));

String[] targetHostUrls = targetHostUrl.split(";");
Expand Down
Loading
Loading