Skip to content

Commit

Permalink
Merge pull request #152 from shanggeeth/graalvm
Browse files Browse the repository at this point in the history
Add GraalJS support for conditional authentication functions
  • Loading branch information
shanggeeth authored May 26, 2024
2 parents 38fb3fb + 9fb7673 commit 37f66b6
Show file tree
Hide file tree
Showing 76 changed files with 1,195 additions and 94 deletions.
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">
<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);
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

0 comments on commit 37f66b6

Please sign in to comment.