Migrate from legacy FCM APIs to HTTP v1

pull/419/head
Rajitha Kumara 6 months ago
parent 3abe68788c
commit 0c5a8d05c6

@ -111,6 +111,46 @@
<groupId>org.wso2.carbon.analytics-common</groupId> <groupId>org.wso2.carbon.analytics-common</groupId>
<artifactId>org.wso2.carbon.event.output.adapter.core</artifactId> <artifactId>org.wso2.carbon.event.output.adapter.core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.wso2.orbit.com.google.http-client</groupId>
<artifactId>google-http-client</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.orbit.com.google.auth-library-oauth2-http</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.orbit.io.opencensus</groupId>
<artifactId>opencensus</artifactId>
</dependency>
<dependency>
<groupId>io.opencensus</groupId>
<artifactId>opencensus-api</artifactId>
</dependency>
<dependency>
<groupId>io.opencensus</groupId>
<artifactId>opencensus-contrib-http-util</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.orbit.io.grpc</groupId>
<artifactId>grpc-context</artifactId>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-gson</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>failureaccess</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.carbon</groupId>
<artifactId>org.wso2.carbon.utils</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -137,12 +177,27 @@
com.google.gson, com.google.gson,
org.osgi.framework.*;version="${imp.package.version.osgi.framework}", org.osgi.framework.*;version="${imp.package.version.osgi.framework}",
org.osgi.service.*;version="${imp.package.version.osgi.service}", 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.operation.mgt,
io.entgra.device.mgt.core.device.mgt.common.push.notification, io.entgra.device.mgt.core.device.mgt.common.push.notification,
org.apache.commons.logging, org.apache.commons.logging,
io.entgra.device.mgt.core.device.mgt.common.*, 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.*
</Import-Package> </Import-Package>
<Embed-Dependency>
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
</Embed-Dependency>
<Embed-Transitive>true</Embed-Transitive>
</instructions> </instructions>
</configuration> </configuration>
</plugin> </plugin>

@ -32,7 +32,9 @@ public class FCMBasedPushNotificationProvider implements PushNotificationProvide
@Override @Override
public NotificationStrategy getNotificationStrategy(PushNotificationConfig config) { public NotificationStrategy getNotificationStrategy(PushNotificationConfig config) {
return new FCMNotificationStrategy(config); FCMNotificationStrategy fcmNotificationStrategy = new FCMNotificationStrategy(config);
fcmNotificationStrategy.init();
return fcmNotificationStrategy;
} }
} }

@ -17,9 +17,11 @@
*/ */
package io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm; package io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm;
import com.google.gson.JsonArray; import com.google.auth.oauth2.GoogleCredentials;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive; 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 org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import io.entgra.device.mgt.core.device.mgt.common.Device; import io.entgra.device.mgt.core.device.mgt.common.Device;
@ -29,24 +31,34 @@ import io.entgra.device.mgt.core.device.mgt.common.push.notification.Notificatio
import io.entgra.device.mgt.core.device.mgt.common.push.notification.PushNotificationConfig; import io.entgra.device.mgt.core.device.mgt.common.push.notification.PushNotificationConfig;
import io.entgra.device.mgt.core.device.mgt.common.push.notification.PushNotificationExecutionFailedException; import io.entgra.device.mgt.core.device.mgt.common.push.notification.PushNotificationExecutionFailedException;
import io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm.internal.FCMDataHolder; import io.entgra.device.mgt.core.device.mgt.extensions.push.notification.provider.fcm.internal.FCMDataHolder;
import org.wso2.carbon.utils.CarbonUtils;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.Properties;
public class FCMNotificationStrategy implements NotificationStrategy { public class FCMNotificationStrategy implements NotificationStrategy {
private static final Log log = LogFactory.getLog(FCMNotificationStrategy.class); private static final Log log = LogFactory.getLog(FCMNotificationStrategy.class);
private static final String NOTIFIER_TYPE_FCM = "FCM"; private static final String NOTIFIER_TYPE_FCM = "FCM";
private static final String FCM_TOKEN = "FCM_TOKEN"; 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 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 static final int HTTP_STATUS_CODE_OK = 200;
private final PushNotificationConfig config; private final PushNotificationConfig config;
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 static final String FCM_ENDPOINT_KEY = "FCM_SERVER_ENDPOINT";
private Properties contextMetadataProperties;
private volatile GoogleCredentials defaultApplication;
public FCMNotificationStrategy(PushNotificationConfig config) { public FCMNotificationStrategy(PushNotificationConfig config) {
this.config = config; this.config = config;
@ -54,7 +66,8 @@ public class FCMNotificationStrategy implements NotificationStrategy {
@Override @Override
public void init() { public void init() {
initContextConfigs();
initDefaultOAuthApplication();
} }
@Override @Override
@ -64,7 +77,9 @@ public class FCMNotificationStrategy implements NotificationStrategy {
Device device = FCMDataHolder.getInstance().getDeviceManagementProviderService() Device device = FCMDataHolder.getInstance().getDeviceManagementProviderService()
.getDeviceWithTypeProperties(ctx.getDeviceId()); .getDeviceWithTypeProperties(ctx.getDeviceId());
if(device.getProperties() != null && getFCMToken(device.getProperties()) != null) { if(device.getProperties() != null && getFCMToken(device.getProperties()) != null) {
this.sendWakeUpCall(ctx.getOperation().getCode(), device); defaultApplication.refresh();
sendWakeUpCall(defaultApplication.getAccessToken().getTokenValue(),
getFCMToken(device.getProperties()));
} }
} else { } else {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
@ -79,31 +94,65 @@ public class FCMNotificationStrategy implements NotificationStrategy {
} }
} }
@Override private void initDefaultOAuthApplication() {
public NotificationContext buildContext() { if (defaultApplication == null) {
return null; synchronized (FCMNotificationStrategy.class) {
if (defaultApplication == null) {
Path serviceAccountPath = Paths.get(FCM_SERVICE_ACCOUNT_PATH);
try {
this.defaultApplication = GoogleCredentials.
fromStream(Files.newInputStream(serviceAccountPath)).
createScoped(FCM_SCOPES);
} catch (IOException e) {
log.error("Fail to initialize default OAuth application for FCM communication");
throw new IllegalStateException(e);
}
}
}
}
} }
@Override private void initContextConfigs() {
public void undeploy() { PushNotificationConfiguration pushNotificationConfiguration = DeviceConfigurationManager.getInstance().
getDeviceManagementConfig().getPushNotificationConfiguration();
List<ContextMetadata> contextMetadata = pushNotificationConfiguration.getContextMetadata();
Properties properties = new Properties();
if (contextMetadata != null) {
for (ContextMetadata metadata : contextMetadata) {
properties.setProperty(metadata.getKey(), metadata.getValue());
}
}
contextMetadataProperties = properties;
} }
private void sendWakeUpCall(String message, Device device) throws IOException, private void sendWakeUpCall(String accessToken, String registrationId) throws IOException,
PushNotificationExecutionFailedException { PushNotificationExecutionFailedException {
if (device.getProperties() != null) {
OutputStream os = null; OutputStream os = null;
byte[] bytes = getFCMRequest(message, getFCMToken(device.getProperties())).getBytes();
HttpURLConnection conn = null; HttpURLConnection conn = null;
String fcmServerEndpoint = contextMetadataProperties.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);
}
try { try {
conn = (HttpURLConnection) new URL(FCM_ENDPOINT).openConnection(); byte[] bytes = getFCMRequest(registrationId).getBytes();
URL url = new URL(fcmServerEndpoint);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Content-Type", "application/json"); conn.setRequestProperty("Content-Type", "application/json");
conn.setRequestProperty("Authorization", "key=" + config.getProperty(FCM_API_KEY)); conn.setRequestProperty("Authorization", "Bearer " + accessToken);
conn.setRequestMethod("POST"); conn.setRequestMethod("POST");
conn.setDoOutput(true); conn.setDoOutput(true);
os = conn.getOutputStream(); os = conn.getOutputStream();
os.write(bytes); os.write(bytes);
int status = conn.getResponseCode();
if (status != 200) {
log.error("Response Status: " + status + ", Response Message: " + conn.getResponseMessage());
}
} finally { } finally {
if (os != null) { if (os != null) {
os.close(); os.close();
@ -112,36 +161,26 @@ public class FCMNotificationStrategy implements NotificationStrategy {
conn.disconnect(); conn.disconnect();
} }
} }
int status = conn.getResponseCode();
if (log.isDebugEnabled()) {
log.debug("Result code: " + status + ", Message: " + conn.getResponseMessage());
}
if (status != HTTP_STATUS_CODE_OK) {
throw new PushNotificationExecutionFailedException("Push notification sending failed with the HTTP " +
"error code '" + status + "'");
}
}
} }
private static String getFCMRequest(String message, String registrationId) { private static String getFCMRequest(String registrationId) {
JsonObject messageObject = new JsonObject();
messageObject.addProperty("token", registrationId);
JsonObject fcmRequest = new JsonObject(); JsonObject fcmRequest = new JsonObject();
fcmRequest.addProperty("delay_while_idle", false); fcmRequest.add("message", messageObject);
fcmRequest.addProperty("time_to_live", TIME_TO_LIVE);
fcmRequest.addProperty("priority", "high");
//Add message to FCM request return fcmRequest.toString();
JsonObject data = new JsonObject(); }
if (message != null && !message.isEmpty()) {
data.addProperty("data", message); @Override
fcmRequest.add("data", data); public NotificationContext buildContext() {
return null;
} }
//Set device reg-id @Override
JsonArray regIds = new JsonArray(); public void undeploy() {
regIds.add(new JsonPrimitive(registrationId));
fcmRequest.add("registration_ids", regIds);
return fcmRequest.toString();
} }
private static String getFCMToken(List<Device.Property> properties) { private static String getFCMToken(List<Device.Property> properties) {
@ -159,5 +198,4 @@ public class FCMNotificationStrategy implements NotificationStrategy {
public PushNotificationConfig getConfig() { public PushNotificationConfig getConfig() {
return config; return config;
} }
} }

@ -0,0 +1,30 @@
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;
}
}

@ -33,6 +33,7 @@ public class PushNotificationConfiguration {
private int schedulerTaskInitialDelay; private int schedulerTaskInitialDelay;
private boolean schedulerTaskEnabled; private boolean schedulerTaskEnabled;
private List<String> pushNotificationProviders; private List<String> pushNotificationProviders;
private List<ContextMetadata> contextMetadata;
@XmlElement(name = "SchedulerBatchSize", required = true) @XmlElement(name = "SchedulerBatchSize", required = true)
public int getSchedulerBatchSize() { public int getSchedulerBatchSize() {
@ -79,4 +80,14 @@ public class PushNotificationConfiguration {
public void setPushNotificationProviders(List<String> pushNotificationProviders) { public void setPushNotificationProviders(List<String> pushNotificationProviders) {
this.pushNotificationProviders = pushNotificationProviders; this.pushNotificationProviders = pushNotificationProviders;
} }
@XmlElementWrapper(name = "ProviderContextMetadata")
@XmlElement(name = "ContextMetadata", required = true)
public List<ContextMetadata> getContextMetadata() {
return contextMetadata;
}
public void setContextMetadata(List<ContextMetadata> contextMetadata) {
this.contextMetadata = contextMetadata;
}
} }

@ -48,6 +48,11 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</PushNotificationProviders> </PushNotificationProviders>
{% if device_mgt_conf.push_notification_conf.fcm_server_endpoint is defined %}
<ProviderContextMetadata>
<ContextMetadata key="FCM_SERVER_ENDPOINT">{{device_mgt_conf.push_notification_conf.fcm_server_endpoint}}</ContextMetadata>
</ProviderContextMetadata>
{% endif %}
</PushNotificationConfiguration> </PushNotificationConfiguration>
<PullNotificationConfiguration> <PullNotificationConfiguration>
{% if device_mgt_conf.pull_notification_conf is defined %} {% if device_mgt_conf.pull_notification_conf is defined %}

@ -405,7 +405,11 @@
<version>${io.entgra.device.mgt.core.version}</version> <version>${io.entgra.device.mgt.core.version}</version>
</dependency> </dependency>
<!-- End of Conditional Email Access dependencies --> <!-- End of Conditional Email Access dependencies -->
<dependency>
<groupId>org.wso2.orbit.com.google.auth-library-oauth2-http</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>1.20.0.wso2v1</version>
</dependency>
<!-- Governance dependencies --> <!-- Governance dependencies -->
<dependency> <dependency>
<groupId>org.wso2.carbon</groupId> <groupId>org.wso2.carbon</groupId>
@ -1916,6 +1920,51 @@
<artifactId>mockito-inline</artifactId> <artifactId>mockito-inline</artifactId>
<version>${mokito.version}</version> <version>${mokito.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>1.20.0</version>
</dependency>
<dependency>
<groupId>org.wso2.orbit.com.google.http-client</groupId>
<artifactId>google-http-client</artifactId>
<version>${com.google.http.client.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.orbit.com.google.auth-library-oauth2-http</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>${com.google.auth.library.auth2.http.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.orbit.io.opencensus</groupId>
<artifactId>opencensus</artifactId>
<version>${io.opencensus.version}</version>
</dependency>
<dependency>
<groupId>io.opencensus</groupId>
<artifactId>opencensus-api</artifactId>
<version>${io.opencensus.api.version}</version>
</dependency>
<dependency>
<groupId>io.opencensus</groupId>
<artifactId>opencensus-contrib-http-util</artifactId>
<version>${io.opencensus.contrib.http.util.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.orbit.io.grpc</groupId>
<artifactId>grpc-context</artifactId>
<version>${io.grpc.context.version}</version>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-gson</artifactId>
<version>${com.google.http.client.gson.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>failureaccess</artifactId>
<version>${com.google.failureaccess.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@ -2306,6 +2355,15 @@
<org.wso2.apache.httpcomponents.httpclient.version>4.3.1.wso2v1</org.wso2.apache.httpcomponents.httpclient.version> <org.wso2.apache.httpcomponents.httpclient.version>4.3.1.wso2v1</org.wso2.apache.httpcomponents.httpclient.version>
<orbit.h2.version>1.4.199.wso2v1</orbit.h2.version> <orbit.h2.version>1.4.199.wso2v1</orbit.h2.version>
<securevault.version>1.1.3</securevault.version> <securevault.version>1.1.3</securevault.version>
<com.google.auth.library.auth2.http.version>1.20.0.wso2v1</com.google.auth.library.auth2.http.version>
<com.google.http.client.version>1.41.2.wso2v2</com.google.http.client.version>
<com.google.failureaccess.version>1.0.1</com.google.failureaccess.version>
<com.google.http.client.gson.version>1.43.3</com.google.http.client.gson.version>
<io.grpc.context.version>1.27.2.wso2v1</io.grpc.context.version>
<io.opencensus.version>0.30.0.wso2v1</io.opencensus.version>
<io.opencensus.api.version>0.30.0</io.opencensus.api.version>
<io.opencensus.contrib.http.util.version>0.30.0</io.opencensus.contrib.http.util.version>
</properties> </properties>
<pluginRepositories> <pluginRepositories>

Loading…
Cancel
Save