diff --git a/keycloak/sms-provider/Msg91Creds.json b/keycloak/sms-provider/Msg91Creds.json new file mode 100644 index 00000000..4887665e --- /dev/null +++ b/keycloak/sms-provider/Msg91Creds.json @@ -0,0 +1,4 @@ +{ + "token": "AKIAI4XIRSGGFIW5VOKQ", + "secret": "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" +} \ No newline at end of file diff --git a/keycloak/sms-provider/awsSnsCreds.json b/keycloak/sms-provider/awsSnsCreds.json new file mode 100644 index 00000000..4887665e --- /dev/null +++ b/keycloak/sms-provider/awsSnsCreds.json @@ -0,0 +1,4 @@ +{ + "token": "AKIAI4XIRSGGFIW5VOKQ", + "secret": "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" +} \ No newline at end of file diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorConstants.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorConstants.java index edd6b353..77837852 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorConstants.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorConstants.java @@ -21,4 +21,8 @@ public class KeycloakSmsAuthenticatorConstants { // User credentials (used to persist the sent sms code + expiration time cluster wide) public static final String USR_CRED_MDL_SMS_CODE = "sms-auth.code"; public static final String USR_CRED_MDL_SMS_EXP_TIME = "sms-auth.exp-time"; + + public static final String SMS_PROVIDER_CONFIGURATIONS_PATH = "sms-provider/awsSnsCreds.json"; + + } diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorUtil.java b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorUtil.java index 60254461..fec8b5b2 100644 --- a/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorUtil.java +++ b/keycloak/sms-provider/src/main/java/org/sunbird/keycloak/KeycloakSmsAuthenticatorUtil.java @@ -1,12 +1,16 @@ package org.sunbird.keycloak; -import com.amazonaws.services.sns.model.PublishResult; +import com.amazonaws.util.StringUtils; import org.jboss.logging.Logger; import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.UserModel; -import org.sunbird.aws.snsclient.SnsNotificationService; +import org.sunbird.sms.MessageProviderFactory; +import org.sunbird.sms.provider.ISmsProvider; +import org.sunbird.utils.JsonUtil; +import java.io.File; import java.util.List; +import java.util.Map; import java.util.Random; /** @@ -85,19 +89,25 @@ public static String setDefaultCountryCodeIfZero(String mobileNumber) { static boolean sendSmsCode(String mobileNumber, String code, AuthenticatorConfigModel config) { // Send an SMS - KeycloakSmsAuthenticatorUtil.logger.debug("Sending " + code + " to mobileNumber " + mobileNumber); - - String smsUsr = getConfigString(config, KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CLIENTTOKEN); - String smsPwd = getConfigString(config, KeycloakSmsAuthenticatorConstants.CONF_PRP_SMS_CLIENTSECRET); String smsText = createMessage(code, mobileNumber, config); - try { - PublishResult send_result = new SnsNotificationService().send(setDefaultCountryCodeIfZero(mobileNumber), smsText, smsUsr, smsPwd); - return true; - } catch (Exception e) { - //Just like pokemon - return false; + logger.debug("KeycloakSmsAuthenticatorUtil@sendSmsCode : smsText - " + smsText); + + String filePath = new File(KeycloakSmsAuthenticatorConstants.SMS_PROVIDER_CONFIGURATIONS_PATH).getAbsolutePath(); + logger.debug("KeycloakSmsAuthenticatorUtil@sendSmsCode : filePath - " + filePath); + + if (!StringUtils.isNullOrEmpty(filePath)) { + Map configurations = JsonUtil.readFromJson(filePath); + logger.debug("KeycloakSmsAuthenticatorUtil@sendSmsCode : configurations - " + configurations); + + ISmsProvider amazonSmsProvider = MessageProviderFactory.getSnsClient(configurations); + + if (amazonSmsProvider != null) { + return amazonSmsProvider.send(setDefaultCountryCodeIfZero(mobileNumber), smsText); + } } + + return false; } static String getSmsCode(long nrOfDigits) { diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/sms/MessageProviderFactory.java b/keycloak/sms-provider/src/main/java/org/sunbird/sms/MessageProviderFactory.java new file mode 100644 index 00000000..97399766 --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/sms/MessageProviderFactory.java @@ -0,0 +1,30 @@ +package org.sunbird.sms; + +import org.sunbird.sms.amazonsns.AmazonSnsFactory; +import org.sunbird.sms.msg91.Msg91SmsProviderFactory; +import org.sunbird.sms.provider.ISmsProvider; + +import java.util.Map; + +public class MessageProviderFactory { + + private static Msg91SmsProviderFactory msg91SmsProviderFactory; + private static AmazonSnsFactory amazonSnsFactory = null; + + + public static ISmsProvider getMsg91SmsProvider(Map configurations) { + if (msg91SmsProviderFactory == null) { + msg91SmsProviderFactory = new Msg91SmsProviderFactory(); + } + + return msg91SmsProviderFactory.create(configurations); + } + + + public static ISmsProvider getSnsClient(Map configurations) { + if (amazonSnsFactory == null) { + amazonSnsFactory = new AmazonSnsFactory(); + } + return amazonSnsFactory.create(configurations); + } +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/sms/SMSAuthenticatorUtil.java b/keycloak/sms-provider/src/main/java/org/sunbird/sms/SMSAuthenticatorUtil.java new file mode 100644 index 00000000..4bec7128 --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/sms/SMSAuthenticatorUtil.java @@ -0,0 +1,26 @@ +package org.sunbird.sms; + +import java.util.Map; + +/** + * Created by joris on 18/11/2016. + */ +public class SMSAuthenticatorUtil { + + public static String getConfigString(Map config, String configName) { + return getConfigString(config, configName, null); + } + + public static String getConfigString(Map config, String configName, String defaultValue) { + + String value = defaultValue; + + if (config.containsKey(configName)) { + // Get value + value = config.get(configName); + } + + return value; + } + +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/sms/SmsConfigurationType.java b/keycloak/sms-provider/src/main/java/org/sunbird/sms/SmsConfigurationType.java new file mode 100644 index 00000000..4ed62d51 --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/sms/SmsConfigurationType.java @@ -0,0 +1,17 @@ +package org.sunbird.sms; + +public interface SmsConfigurationType { + String CONF_SMS_TOKEN = "token"; + String CONF_SMS_SECRET = "secret"; + String CONF_SMS_URL = "sms_url"; + String CONF_SMS_METHOD_TYPE = "sms_method_type"; + String CONF_SMS_USERNAME = "sms_username"; + String CONF_SMS_PASSWORD = "sms_password"; + String CONF_SMS_AUTHTYPE = "sms_authtype"; + String CONF_SMS_CONTENT_TYPE = "sms_content_type"; + String CONF_SMS_PROXY_URL = "sms_proxy_url"; + String CONF_SMS_PROXY_USERNAME = "sms_proxy_username"; + String CONF_SMS_PROXY_PASSWORD = "sms_proxy_password"; + String CONF_AUTH_METHOD_BASIC = "basic_authentication"; + String CONF_AUTH_METHOD_INMESSAGE = "in_message_authentication"; +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/sms/amazonsns/AmazonSnsFactory.java b/keycloak/sms-provider/src/main/java/org/sunbird/sms/amazonsns/AmazonSnsFactory.java new file mode 100644 index 00000000..90b51e46 --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/sms/amazonsns/AmazonSnsFactory.java @@ -0,0 +1,20 @@ +package org.sunbird.sms.amazonsns; + +import org.sunbird.sms.provider.ISmsProvider; +import org.sunbird.sms.provider.ISmsProviderFactory; + +import java.util.Map; + +public class AmazonSnsFactory implements ISmsProviderFactory { + private static AmazonSnsProvider amazonSnsProvider = null; + + @Override + public ISmsProvider create(Map configurations) { + if (amazonSnsProvider == null) { + amazonSnsProvider = new AmazonSnsProvider(); + amazonSnsProvider.configure(configurations); + } + + return amazonSnsProvider; + } +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/sms/amazonsns/AmazonSnsProvider.java b/keycloak/sms-provider/src/main/java/org/sunbird/sms/amazonsns/AmazonSnsProvider.java new file mode 100644 index 00000000..632d9598 --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/sms/amazonsns/AmazonSnsProvider.java @@ -0,0 +1,52 @@ +package org.sunbird.sms.amazonsns; + +import com.amazonaws.services.sns.model.MessageAttributeValue; +import com.amazonaws.services.sns.model.PublishRequest; +import org.jboss.logging.Logger; +import org.sunbird.aws.snsclient.SnsClientFactory; +import org.sunbird.sms.SMSAuthenticatorUtil; +import org.sunbird.sms.SmsConfigurationType; +import org.sunbird.sms.provider.ISmsProvider; + +import java.util.HashMap; +import java.util.Map; + +public class AmazonSnsProvider implements ISmsProvider { + + private static Logger logger = Logger.getLogger(AmazonSnsProvider.class); + + private Map configurations; + + @Override + public boolean send(String phoneNumber, String smsText) { + logger.debug("AmazonSnsProvider@send : phoneNumber - " + phoneNumber + " & Sms text - " + smsText); + + Map smsAttributes = new HashMap(); + smsAttributes.put("AWS.SNS.SMS.SenderID", new MessageAttributeValue() + .withStringValue("HomeOffice") + .withDataType("String")); + + String clientToken = SMSAuthenticatorUtil.getConfigString(configurations, SmsConfigurationType.CONF_SMS_TOKEN); + String clientSecret = SMSAuthenticatorUtil.getConfigString(configurations, SmsConfigurationType.CONF_SMS_SECRET); + + logger.debug("AmazonSnsProvider@send : clientToken - " + clientToken + " & clientSecret - " + clientSecret); + + + try { + SnsClientFactory.getSnsClient(clientToken, clientSecret).publish(new PublishRequest() + .withMessage(smsText) + .withPhoneNumber(phoneNumber) + .withMessageAttributes(smsAttributes)); + + return true; + } catch (Exception e) { + logger.debug("AmazonSnsProvider@Send : Exception Caught -" + e.getMessage()); + return false; + } + } + + @Override + public void configure(Map configurations) { + this.configurations = configurations; + } +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/sms/msg91/Msg91SmsProvider.java b/keycloak/sms-provider/src/main/java/org/sunbird/sms/msg91/Msg91SmsProvider.java new file mode 100644 index 00000000..acf610f1 --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/sms/msg91/Msg91SmsProvider.java @@ -0,0 +1,180 @@ +package org.sunbird.sms.msg91; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.StatusLine; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.jboss.logging.Logger; +import org.sunbird.sms.SMSAuthenticatorUtil; +import org.sunbird.sms.SmsConfigurationType; +import org.sunbird.sms.provider.ISmsProvider; + +import javax.ws.rs.HttpMethod; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Map; + +public class Msg91SmsProvider implements ISmsProvider { + + private static Logger logger = Logger.getLogger(Msg91SmsProvider.class); + + private Map configurations; + + @Override + public void configure(Map configurations) { + this.configurations = configurations; + } + + @Override + public boolean send(String phoneNumber, String smsText) { + return sendSmsCode(phoneNumber, smsText); + } + + private boolean sendSmsCode(String mobileNumber, String smsText) { + // Send an SMS + logger.debug("Sending " + smsText + " to mobileNumber " + mobileNumber); + + String smsUrl = SMSAuthenticatorUtil.getConfigString(configurations, SmsConfigurationType.CONF_SMS_URL); + String smsUsr = SMSAuthenticatorUtil.getConfigString(configurations, SmsConfigurationType.CONF_SMS_USERNAME); + String smsPwd = SMSAuthenticatorUtil.getConfigString(configurations, SmsConfigurationType.CONF_SMS_PASSWORD); + + String proxyUrl = SMSAuthenticatorUtil.getConfigString(configurations, SmsConfigurationType.CONF_SMS_PROXY_URL); + String proxyUsr = SMSAuthenticatorUtil.getConfigString(configurations, SmsConfigurationType.CONF_SMS_PROXY_USERNAME); + String proxyPwd = SMSAuthenticatorUtil.getConfigString(configurations, SmsConfigurationType.CONF_SMS_PROXY_PASSWORD); + String contentType = SMSAuthenticatorUtil.getConfigString(configurations, SmsConfigurationType.CONF_SMS_CONTENT_TYPE); + + CloseableHttpClient httpClient = null; + try { + URL smsURL = (smsUrl != null && smsUrl.length() > 0) ? new URL(smsUrl) : null; + URL proxyURL = (proxyUrl != null && proxyUrl.length() > 0) ? new URL(proxyUrl) : null; + + if (smsURL == null) { + logger.error("SMS gateway URL is not configured."); + return false; + } + + + CredentialsProvider credsProvider; + if (SMSAuthenticatorUtil.getConfigString(configurations, SmsConfigurationType.CONF_SMS_AUTHTYPE, "").equals(SmsConfigurationType.CONF_AUTH_METHOD_INMESSAGE)) { + credsProvider = getCredentialsProvider(null, null, proxyUsr, proxyPwd, smsURL, proxyURL); + } else { + credsProvider = getCredentialsProvider(smsUsr, smsPwd, proxyUsr, proxyPwd, smsURL, proxyURL); + } + + HttpHost target = new HttpHost(smsURL.getHost(), smsURL.getPort(), smsURL.getProtocol()); + HttpHost proxy = (proxyURL != null) ? new HttpHost(proxyURL.getHost(), proxyURL.getPort(), proxyURL.getProtocol()) : null; + + httpClient = HttpClients.custom() + .setDefaultCredentialsProvider(credsProvider) + .build(); + + RequestConfig requestConfig; + requestConfig = RequestConfig.custom() + .setProxy(proxy) + .build(); + + String httpMethod = SMSAuthenticatorUtil.getConfigString(configurations, SmsConfigurationType.CONF_SMS_METHOD_TYPE); + if (httpMethod.equals(HttpMethod.GET)) { + + String path = getPath(mobileNumber, smsURL, smsText); + + HttpGet httpGet = new HttpGet(path); + httpGet.setConfig(requestConfig); + if (isNotEmpty(contentType)) { + httpGet.addHeader("Content-type", contentType); + } + + logger.debug("Executing request " + httpGet.getRequestLine() + " to " + target + " via " + proxy); + + CloseableHttpResponse response = httpClient.execute(target, httpGet); + StatusLine sl = response.getStatusLine(); + response.close(); + if (sl.getStatusCode() != 200) { + logger.error("SMS code for " + mobileNumber + " could not be sent: " + sl.getStatusCode() + " - " + sl.getReasonPhrase()); + } + return sl.getStatusCode() == 200; + + } else if (httpMethod.equals(HttpMethod.POST)) { + + String path = getPath(mobileNumber, smsURL, smsText); + String uri = smsURL.getProtocol() + "://" + smsURL.getHost() + ":" + smsURL.getPort() + path; + + HttpPost httpPost = new HttpPost(uri); + httpPost.setConfig(requestConfig); + if (isNotEmpty(contentType)) { + httpPost.addHeader("Content-type", contentType); + } + + HttpEntity entity = new ByteArrayEntity(smsText.getBytes("UTF-8")); + httpPost.setEntity(entity); + + CloseableHttpResponse response = httpClient.execute(httpPost); + StatusLine sl = response.getStatusLine(); + response.close(); + if (sl.getStatusCode() != 200) { + logger.error("SMS code for " + mobileNumber + " could not be sent: " + sl.getStatusCode() + " - " + sl.getReasonPhrase()); + } + return sl.getStatusCode() == 200; + } + return true; + } catch (IOException e) { + logger.error(e); + return false; + } finally { + if (httpClient != null) { + try { + httpClient.close(); + } catch (IOException ignore) { + // Ignore ... + } + } + } + } + + private CredentialsProvider getCredentialsProvider(String smsUsr, String smsPwd, String proxyUsr, String proxyPwd, URL smsURL, URL proxyURL) { + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + + // If defined, add BASIC Authentication parameters + if (isNotEmpty(smsUsr) && isNotEmpty(smsPwd)) { + credsProvider.setCredentials( + new AuthScope(smsURL.getHost(), smsURL.getPort()), + new UsernamePasswordCredentials(smsUsr, smsPwd)); + + } + + // If defined, add Proxy Authentication parameters + if (isNotEmpty(proxyUsr) && isNotEmpty(proxyPwd)) { + credsProvider.setCredentials( + new AuthScope(proxyURL.getHost(), proxyURL.getPort()), + new UsernamePasswordCredentials(proxyUsr, proxyPwd)); + + } + return credsProvider; + } + + private String getPath(String mobileNumber, URL smsURL, String smsText) throws UnsupportedEncodingException { + String path = smsURL.getPath(); + if(smsURL.getQuery() != null && smsURL.getQuery().length() > 0) { + path += smsURL.getQuery(); + } + path = path.replaceFirst("\\{message\\}", URLEncoder.encode(smsText, "UTF-8")); + path = path.replaceFirst("\\{phonenumber\\}", URLEncoder.encode(mobileNumber, "UTF-8")); + return path; + } + + private boolean isNotEmpty(String s) { + return (s != null && s.length() > 0); + } +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/sms/msg91/Msg91SmsProviderFactory.java b/keycloak/sms-provider/src/main/java/org/sunbird/sms/msg91/Msg91SmsProviderFactory.java new file mode 100644 index 00000000..73b4f4d2 --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/sms/msg91/Msg91SmsProviderFactory.java @@ -0,0 +1,21 @@ +package org.sunbird.sms.msg91; + +import org.sunbird.sms.provider.ISmsProvider; +import org.sunbird.sms.provider.ISmsProviderFactory; + +import java.util.Map; + +public class Msg91SmsProviderFactory implements ISmsProviderFactory { + + private static Msg91SmsProvider msg91SmsProvider = null; + + @Override + public ISmsProvider create(Map configurations) { + if (msg91SmsProvider == null){ + msg91SmsProvider = new Msg91SmsProvider(); + msg91SmsProvider.configure(configurations); + } + + return msg91SmsProvider; + } +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/sms/provider/ISmsProvider.java b/keycloak/sms-provider/src/main/java/org/sunbird/sms/provider/ISmsProvider.java new file mode 100644 index 00000000..b6e41e70 --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/sms/provider/ISmsProvider.java @@ -0,0 +1,6 @@ +package org.sunbird.sms.provider; + +public interface ISmsProvider extends ISmsProviderConfigurations { + + boolean send(String phoneNumber,String smsText); +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/sms/provider/ISmsProviderConfigurations.java b/keycloak/sms-provider/src/main/java/org/sunbird/sms/provider/ISmsProviderConfigurations.java new file mode 100644 index 00000000..1944bc1c --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/sms/provider/ISmsProviderConfigurations.java @@ -0,0 +1,7 @@ +package org.sunbird.sms.provider; + +import java.util.Map; + +public interface ISmsProviderConfigurations { + void configure(Map configurations); +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/sms/provider/ISmsProviderFactory.java b/keycloak/sms-provider/src/main/java/org/sunbird/sms/provider/ISmsProviderFactory.java new file mode 100644 index 00000000..a0497ddf --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/sms/provider/ISmsProviderFactory.java @@ -0,0 +1,8 @@ +package org.sunbird.sms.provider; + +import java.util.Map; + +public interface ISmsProviderFactory { + + ISmsProvider create(Map configurations); +} diff --git a/keycloak/sms-provider/src/main/java/org/sunbird/utils/JsonUtil.java b/keycloak/sms-provider/src/main/java/org/sunbird/utils/JsonUtil.java new file mode 100644 index 00000000..98a68c51 --- /dev/null +++ b/keycloak/sms-provider/src/main/java/org/sunbird/utils/JsonUtil.java @@ -0,0 +1,40 @@ +package org.sunbird.utils; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +public class JsonUtil { + + public static Map readFromJson(String jsonFile) { + ObjectMapper mapper = new ObjectMapper(); + + // read JSON from a file + Map map = null; + + try { + map = mapper.readValue( + new File(jsonFile), + new TypeReference>() { + }); + } catch (IOException e) { + e.printStackTrace(); + } + +// String json = null; +// try { +// JSONParser parser = new JSONParser(); +// //Use JSONObject for simple JSON and JSONArray for array of JSON. +// JSONObject data = (JSONObject) parser.parse(new FileReader(jsonFile));//path to the JSON file. +// json = data.toString(); +// } catch (IOException | ParseException e) { +// e.printStackTrace(); +// } + + return map; + } + +}