diff --git a/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/pom.xml b/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/pom.xml index 674cd1b501..9bab0bcf9f 100644 --- a/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/pom.xml +++ b/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/pom.xml @@ -111,6 +111,46 @@ org.wso2.carbon.analytics-common org.wso2.carbon.event.output.adapter.core + + org.wso2.orbit.com.google.http-client + google-http-client + + + org.wso2.orbit.com.google.auth-library-oauth2-http + google-auth-library-oauth2-http + + + org.wso2.orbit.io.opencensus + opencensus + + + io.opencensus + opencensus-api + + + io.opencensus + opencensus-contrib-http-util + + + org.wso2.orbit.io.grpc + grpc-context + + + com.google.http-client + google-http-client-gson + + + com.google.guava + failureaccess + + + com.google.guava + guava + + + org.wso2.carbon + org.wso2.carbon.utils + @@ -137,12 +177,27 @@ com.google.gson, org.osgi.framework.*;version="${imp.package.version.osgi.framework}", org.osgi.service.*;version="${imp.package.version.osgi.service}", + org.wso2.carbon.utils.*, io.entgra.device.mgt.core.device.mgt.common.operation.mgt, io.entgra.device.mgt.core.device.mgt.common.push.notification, org.apache.commons.logging, io.entgra.device.mgt.core.device.mgt.common.*, - io.entgra.device.mgt.core.device.mgt.core.service + io.entgra.device.mgt.core.device.mgt.core.service, + io.entgra.device.mgt.core.device.mgt.core.config.*, + io.entgra.device.mgt.core.device.mgt.core.config.push.notification.*, + io.entgra.device.mgt.core.device.mgt.extensions.logger.spi, + io.entgra.device.mgt.core.notification.logger.*, + com.google.auth.oauth2.* + + google-auth-library-oauth2-http;scope=compile|runtime, + google-http-client;scope=compile|runtime, + grpc-context;scope=compile|runtime, + guava;scope=compile|runtime, + opencensus;scope=compile|runtime, + failureaccess;scope=compile|runtime + + true diff --git a/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/src/main/java/io/entgra/device/mgt/core/device/mgt/extensions/push/notification/provider/fcm/FCMBasedPushNotificationProvider.java b/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/src/main/java/io/entgra/device/mgt/core/device/mgt/extensions/push/notification/provider/fcm/FCMBasedPushNotificationProvider.java index 1dbbacd720..2f849a4366 100644 --- a/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/src/main/java/io/entgra/device/mgt/core/device/mgt/extensions/push/notification/provider/fcm/FCMBasedPushNotificationProvider.java +++ b/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/src/main/java/io/entgra/device/mgt/core/device/mgt/extensions/push/notification/provider/fcm/FCMBasedPushNotificationProvider.java @@ -32,7 +32,9 @@ public class FCMBasedPushNotificationProvider implements PushNotificationProvide @Override public NotificationStrategy getNotificationStrategy(PushNotificationConfig config) { - return new FCMNotificationStrategy(config); + FCMNotificationStrategy fcmNotificationStrategy = new FCMNotificationStrategy(config); + fcmNotificationStrategy.init(); + return fcmNotificationStrategy; } } diff --git a/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/src/main/java/io/entgra/device/mgt/core/device/mgt/extensions/push/notification/provider/fcm/FCMNotificationStrategy.java b/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/src/main/java/io/entgra/device/mgt/core/device/mgt/extensions/push/notification/provider/fcm/FCMNotificationStrategy.java index c6100aa418..39bd1ba7e5 100644 --- a/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/src/main/java/io/entgra/device/mgt/core/device/mgt/extensions/push/notification/provider/fcm/FCMNotificationStrategy.java +++ b/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/src/main/java/io/entgra/device/mgt/core/device/mgt/extensions/push/notification/provider/fcm/FCMNotificationStrategy.java @@ -17,9 +17,8 @@ */ package io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; +import io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm.util.FCMUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import io.entgra.device.mgt.core.device.mgt.common.Device; @@ -39,14 +38,13 @@ import java.util.List; public class FCMNotificationStrategy implements NotificationStrategy { private static final Log log = LogFactory.getLog(FCMNotificationStrategy.class); - private static final String NOTIFIER_TYPE_FCM = "FCM"; private static final String FCM_TOKEN = "FCM_TOKEN"; - private static final String FCM_ENDPOINT = "https://fcm.googleapis.com/fcm/send"; private static final String FCM_API_KEY = "fcmAPIKey"; - private static final int TIME_TO_LIVE = 2419199; // 1 second less that 28 days + private static final int TIME_TO_LIVE = 2419199; // 1 second less than 28 days private static final int HTTP_STATUS_CODE_OK = 200; private final PushNotificationConfig config; + private static final String FCM_ENDPOINT_KEY = "FCM_SERVER_ENDPOINT"; public FCMNotificationStrategy(PushNotificationConfig config) { this.config = config; @@ -64,12 +62,14 @@ public class FCMNotificationStrategy implements NotificationStrategy { Device device = FCMDataHolder.getInstance().getDeviceManagementProviderService() .getDeviceWithTypeProperties(ctx.getDeviceId()); if(device.getProperties() != null && getFCMToken(device.getProperties()) != null) { - this.sendWakeUpCall(ctx.getOperation().getCode(), device); + FCMUtil.getInstance().getDefaultApplication().refresh(); + sendWakeUpCall(FCMUtil.getInstance().getDefaultApplication().getAccessToken().getTokenValue(), + getFCMToken(device.getProperties())); } } else { if (log.isDebugEnabled()) { log.debug("Not using FCM notifier as notifier type is set to " + config.getType() + - " in Platform Configurations."); + " in Platform Configurations."); } } } catch (DeviceManagementException e) { @@ -79,71 +79,75 @@ public class FCMNotificationStrategy implements NotificationStrategy { } } - @Override - public NotificationContext buildContext() { - return null; - } - - @Override - public void undeploy() { - } + /** + * Send FCM message to the FCM server to initiate the push notification + * @param accessToken Access token to authenticate with the FCM server + * @param registrationId Registration ID of the device + * @throws IOException If an error occurs while sending the request + * @throws PushNotificationExecutionFailedException If an error occurs while sending the push notification + */ + private void sendWakeUpCall(String accessToken, String registrationId) throws IOException, + PushNotificationExecutionFailedException { + HttpURLConnection conn = null; + + String fcmServerEndpoint = FCMUtil.getInstance().getContextMetadataProperties() + .getProperty(FCM_ENDPOINT_KEY); + if(fcmServerEndpoint == null) { + String msg = "Encountered configuration issue. " + FCM_ENDPOINT_KEY + " is not defined"; + log.error(msg); + throw new PushNotificationExecutionFailedException(msg); + } - private void sendWakeUpCall(String message, Device device) throws IOException, - PushNotificationExecutionFailedException { - if (device.getProperties() != null) { - OutputStream os = null; - byte[] bytes = getFCMRequest(message, getFCMToken(device.getProperties())).getBytes(); - - HttpURLConnection conn = null; - try { - conn = (HttpURLConnection) new URL(FCM_ENDPOINT).openConnection(); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setRequestProperty("Authorization", "key=" + config.getProperty(FCM_API_KEY)); - conn.setRequestMethod("POST"); - conn.setDoOutput(true); - os = conn.getOutputStream(); + try { + byte[] bytes = getFCMRequest(registrationId).getBytes(); + URL url = new URL(fcmServerEndpoint); + conn = (HttpURLConnection) url.openConnection(); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("Authorization", "Bearer " + accessToken); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + + try (OutputStream os = conn.getOutputStream()) { os.write(bytes); - } finally { - if (os != null) { - os.close(); - } - if (conn != null) { - conn.disconnect(); - } } + int status = conn.getResponseCode(); - if (log.isDebugEnabled()) { - log.debug("Result code: " + status + ", Message: " + conn.getResponseMessage()); + if (status != 200) { + log.error("Response Status: " + status + ", Response Message: " + conn.getResponseMessage()); } - if (status != HTTP_STATUS_CODE_OK) { - throw new PushNotificationExecutionFailedException("Push notification sending failed with the HTTP " + - "error code '" + status + "'"); + } finally { + if (conn != null) { + conn.disconnect(); } } } - private static String getFCMRequest(String message, String registrationId) { - JsonObject fcmRequest = new JsonObject(); - fcmRequest.addProperty("delay_while_idle", false); - fcmRequest.addProperty("time_to_live", TIME_TO_LIVE); - fcmRequest.addProperty("priority", "high"); - - //Add message to FCM request - JsonObject data = new JsonObject(); - if (message != null && !message.isEmpty()) { - data.addProperty("data", message); - fcmRequest.add("data", data); - } + /** + * Get the FCM request as a JSON string + * @param registrationId Registration ID of the device + * @return FCM request as a JSON string + */ + private static String getFCMRequest(String registrationId) { + JsonObject messageObject = new JsonObject(); + messageObject.addProperty("token", registrationId); - //Set device reg-id - JsonArray regIds = new JsonArray(); - regIds.add(new JsonPrimitive(registrationId)); + JsonObject fcmRequest = new JsonObject(); + fcmRequest.add("message", messageObject); - fcmRequest.add("registration_ids", regIds); return fcmRequest.toString(); } + @Override + public NotificationContext buildContext() { + return null; + } + + @Override + public void undeploy() { + + } + private static String getFCMToken(List properties) { String fcmToken = null; for (Device.Property property : properties) { @@ -159,5 +163,4 @@ public class FCMNotificationStrategy implements NotificationStrategy { public PushNotificationConfig getConfig() { return config; } - } diff --git a/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/src/main/java/io/entgra/device/mgt/core/device/mgt/extensions/push/notification/provider/fcm/util/FCMUtil.java b/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/src/main/java/io/entgra/device/mgt/core/device/mgt/extensions/push/notification/provider/fcm/util/FCMUtil.java new file mode 100644 index 0000000000..0c6c433cc7 --- /dev/null +++ b/components/device-mgt-extensions/io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm/src/main/java/io/entgra/device/mgt/core/device/mgt/extensions/push/notification/provider/fcm/util/FCMUtil.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018 - 2024, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. 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. + */ +package io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm.util; + +import com.google.auth.oauth2.GoogleCredentials; +import io.entgra.device.mgt.core.device.mgt.core.config.DeviceConfigurationManager; +import io.entgra.device.mgt.core.device.mgt.core.config.push.notification.ContextMetadata; +import io.entgra.device.mgt.core.device.mgt.core.config.push.notification.PushNotificationConfiguration; +import io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm.FCMNotificationStrategy; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.utils.CarbonUtils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Properties; + +public class FCMUtil { + + private static final Log log = LogFactory.getLog(FCMUtil.class); + private static volatile FCMUtil instance; + private static GoogleCredentials defaultApplication; + private static final String FCM_SERVICE_ACCOUNT_PATH = CarbonUtils.getCarbonHome() + File.separator + + "repository" + File.separator + "resources" + File.separator + "service-account.json"; + private static final String[] FCM_SCOPES = { "https://www.googleapis.com/auth/firebase.messaging" }; + private Properties contextMetadataProperties; + + private FCMUtil() { + initContextConfigs(); + initDefaultOAuthApplication(); + } + + private void initDefaultOAuthApplication() { + if (defaultApplication == null) { + Path serviceAccountPath = Paths.get(FCM_SERVICE_ACCOUNT_PATH); + try { + defaultApplication = GoogleCredentials. + fromStream(Files.newInputStream(serviceAccountPath)). + createScoped(FCM_SCOPES); + } catch (IOException e) { + String msg = "Fail to initialize default OAuth application for FCM communication"; + log.error(msg); + throw new IllegalStateException(msg, e); + } + } + } + + /** + * Initialize the context metadata properties from the cdm-config.xml. This file includes the fcm server URL + * to be invoked when sending the wakeup call to the device. + */ + private void initContextConfigs() { + PushNotificationConfiguration pushNotificationConfiguration = DeviceConfigurationManager.getInstance(). + getDeviceManagementConfig().getPushNotificationConfiguration(); + List contextMetadata = pushNotificationConfiguration.getContextMetadata(); + Properties properties = new Properties(); + if (contextMetadata != null) { + for (ContextMetadata metadata : contextMetadata) { + properties.setProperty(metadata.getKey(), metadata.getValue()); + } + } + contextMetadataProperties = properties; + } + + /** + * Get the instance of FCMUtil. FCMUtil is a singleton class which should not be + * instantiating more than once. Instantiating the class requires to read the service account file from + * the filesystem and instantiation of the GoogleCredentials object which are costly operations. + * @return FCMUtil instance + */ + public static FCMUtil getInstance() { + if (instance == null) { + synchronized (FCMUtil.class) { + if (instance == null) { + instance = new FCMUtil(); + } + } + } + return instance; + } + + public GoogleCredentials getDefaultApplication() { + return defaultApplication; + } + + public Properties getContextMetadataProperties() { + return contextMetadataProperties; + } +} diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/push/notification/ContextMetadata.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/push/notification/ContextMetadata.java new file mode 100644 index 0000000000..a5ead67f0a --- /dev/null +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/push/notification/ContextMetadata.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 - 2023, Entgra (Pvt) Ltd. (http://www.entgra.io) All Rights Reserved. + * + * Entgra (Pvt) Ltd. 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. + */ +package io.entgra.device.mgt.core.device.mgt.core.config.push.notification; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlValue; + +@XmlRootElement(name = "ContextMetadata") +public class ContextMetadata { + private String key; + private String value; + + @XmlAttribute(name = "key") + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + @XmlValue + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/push/notification/PushNotificationConfiguration.java b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/push/notification/PushNotificationConfiguration.java index 90c6639cb1..0d64e45cdd 100644 --- a/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/push/notification/PushNotificationConfiguration.java +++ b/components/device-mgt/io.entgra.device.mgt.core.device.mgt.core/src/main/java/io/entgra/device/mgt/core/device/mgt/core/config/push/notification/PushNotificationConfiguration.java @@ -33,6 +33,7 @@ public class PushNotificationConfiguration { private int schedulerTaskInitialDelay; private boolean schedulerTaskEnabled; private List pushNotificationProviders; + private List contextMetadata; @XmlElement(name = "SchedulerBatchSize", required = true) public int getSchedulerBatchSize() { @@ -79,4 +80,14 @@ public class PushNotificationConfiguration { public void setPushNotificationProviders(List pushNotificationProviders) { this.pushNotificationProviders = pushNotificationProviders; } + + @XmlElementWrapper(name = "ProviderContextMetadata") + @XmlElement(name = "ContextMetadata", required = true) + public List getContextMetadata() { + return contextMetadata; + } + + public void setContextMetadata(List contextMetadata) { + this.contextMetadata = contextMetadata; + } } diff --git a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf_templates/templates/repository/conf/cdm-config.xml.j2 b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf_templates/templates/repository/conf/cdm-config.xml.j2 index 59e026f679..2d5f7639f8 100644 --- a/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf_templates/templates/repository/conf/cdm-config.xml.j2 +++ b/features/device-mgt/io.entgra.device.mgt.core.device.mgt.basics.feature/src/main/resources/conf_templates/templates/repository/conf/cdm-config.xml.j2 @@ -48,6 +48,11 @@ {% endfor %} {% endif %} + {% if device_mgt_conf.push_notification_conf.fcm_server_endpoint is defined %} + + {{device_mgt_conf.push_notification_conf.fcm_server_endpoint}} + + {% endif %} {% if device_mgt_conf.pull_notification_conf is defined %} diff --git a/pom.xml b/pom.xml index 90835d054e..f97db94c29 100644 --- a/pom.xml +++ b/pom.xml @@ -405,7 +405,6 @@ ${io.entgra.device.mgt.core.version} - org.wso2.carbon @@ -1916,6 +1915,51 @@ mockito-inline ${mokito.version} + + com.google.auth + google-auth-library-oauth2-http + ${com.google.auth.library.auth2.http.version} + + + org.wso2.orbit.com.google.http-client + google-http-client + ${com.google.http.client.version} + + + org.wso2.orbit.com.google.auth-library-oauth2-http + google-auth-library-oauth2-http + ${com.google.auth.library.wso2.auth2.http.version} + + + org.wso2.orbit.io.opencensus + opencensus + ${io.opencensus.version} + + + io.opencensus + opencensus-api + ${io.opencensus.api.version} + + + io.opencensus + opencensus-contrib-http-util + ${io.opencensus.contrib.http.util.version} + + + org.wso2.orbit.io.grpc + grpc-context + ${io.grpc.context.version} + + + com.google.http-client + google-http-client-gson + ${com.google.http.client.gson.version} + + + com.google.guava + failureaccess + ${com.google.failureaccess.version} + @@ -2306,6 +2350,16 @@ 4.3.1.wso2v1 1.4.199.wso2v1 1.1.3 + + 1.20.0.wso2v1 + 1.20.0 + 1.41.2.wso2v2 + 1.0.1 + 1.43.3 + 1.27.2.wso2v1 + 0.30.0.wso2v1 + 0.30.0 + 0.30.0