com.predic8.membrane.core.interceptor.authentication.session.TelekomSMSTokenProvider Maven / Gradle / Ivy
/* Copyright 2012,2014 predic8 GmbH, www.predic8.com
Licensed 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 com.predic8.membrane.core.interceptor.authentication.session;
import com.fasterxml.jackson.core.*;
import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.http.*;
import com.predic8.membrane.core.transport.http.*;
import com.predic8.membrane.core.util.*;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.*;
import javax.annotation.concurrent.*;
import java.io.*;
import java.net.*;
import java.util.*;
import static com.predic8.membrane.core.http.Header.*;
import static com.predic8.membrane.core.http.MimeType.*;
import static java.nio.charset.StandardCharsets.*;
/**
* @explanation A token provider using Deutsche Telekom's REST interface developer garden to send a randomly generated numeric token
* to the user via text message.
* @description
* The telekomSMSTokenProvider randomly generates a 6-digit token after the user entered her correct
* password.
*
*
* The token is then sent to the user via text message. The user's attribute sms is used as the
* recipient phone number. If this attribute has not been provided by the user data provider, the
* login attempt fails.
*
*
* The text message is sent via Deutsche Telekom's developer
* garden REST API. To use this API, a registered user account with sufficient balance is necessary and
* the Send SMS API has to be enabled for this account. Membrane API Gateway must be registered as an
* "application" on the developer garden website, and the "Global SMS API" must be enabled both for the user account
* as well as the registered application. Once completed, the scope, clientId and clientSecret
* settings must be copied from the website into Membrane's proxies.xml configuration file. Membrane uses these three
* parameters to identify itself when connecting to the Telekom API Gateway.
*
*
* When using a non-standard environment (see https://www.developergarden.com/apis/documentation/bundle/telekom-api-rest/html/sendsms.html#environmental_infos for more information),
* the parameters senderName and senderAddress may be used to set the SMS sender address and name.
*
*
* The token is prepended by prefixText to generate the text message's text.
*
*
* If normalizeTelephoneNumber is set, the user's sms attribute will be normalized according
* to the following rules before using it:
*
* - '+' is replaced by '00'.
* - Any characters within round brackets, '(' and ')', are removed.
* - '-' and ' ' are removed.
*
*
*/
@MCElement(name="telekomSMSTokenProvider", topLevel=false)
public class TelekomSMSTokenProvider extends SMSTokenProvider {
private static final Logger log = LoggerFactory.getLogger(TelekomSMSTokenProvider.class.getName());
private HttpClient hc;
private String scope, clientId, clientSecret;
private String senderAddress = "0191011";
private String senderName;
private EnvironmentType environment = EnvironmentType.BUDGET;
public enum EnvironmentType {
BUDGET,
PREMIUM,
MOCK,
SANDBOX,
}
@GuardedBy("this")
private String token;
@GuardedBy("this")
private long tokenExpiration;
@Override
public void init(Router router) {
hc = router.getHttpClientFactory().createClient(null);
}
@Override
protected String normalizeNumber(String number) {
return number.replaceAll("\\+", "00").replaceAll("[- ]|\\(.*\\)", "");
}
@Override
protected void sendSMS(String text, String recipientNumber) {
recipientNumber = recipientNumber.replaceAll("^00", "\\+");
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
JsonFactory jsonFactory = new JsonFactory();
JsonGenerator jg = jsonFactory.createGenerator(baos, JsonEncoding.UTF8);
jg.writeStartObject();
jg.writeObjectFieldStart("outboundSMSMessageRequest");
jg.writeArrayFieldStart("address");
jg.writeString("tel:" + recipientNumber);
jg.writeEndArray();
jg.writeStringField("senderAddress", senderAddress);
jg.writeObjectFieldStart("outboundSMSTextMessage");
jg.writeStringField("message", text);
jg.writeEndObject();
jg.writeStringField("outboundEncoding", "7bitGSM");
jg.writeStringField("clientCorrelator", "" + ((long)(Math.random() * Long.MAX_VALUE)));
if (senderName != null)
jg.writeStringField("senderName", senderName);
jg.writeEndObject();
jg.writeEndObject();
jg.close();
Exchange exc = new Request.Builder().
post("https://gateway.developer.telekom.com/plone/sms/rest/" + environment.name().toLowerCase()
+ "/smsmessaging/v1/outbound/" + URLEncoder.encode(senderAddress, UTF_8) + "/requests").
header("Host", "gateway.developer.telekom.com").
header("Authorization", "OAuth realm=\"developergarden.com\",oauth_token=\"" + getAccessToken() + "\"").
header("Accept", APPLICATION_JSON).
contentType(APPLICATION_JSON).
body(baos.toByteArray()).
buildExchange();
hc.call(exc, false, true);
if (exc.getResponse().getStatusCode() != 201)
throw new RuntimeException("Could not send SMS: " + exc.getResponse());
log.debug("sent SMS to " + recipientNumber);
} catch (Exception e2) {
throw new RuntimeException(e2);
}
}
private synchronized String getAccessToken() throws Exception {
long now = System.currentTimeMillis();
if (token == null || tokenExpiration < now) {
Exchange exc = new Request.Builder().
post("https://global.telekom.com/gcp-web-api/oauth").
header(HOST, "global.telekom.com").
header(AUTHORIZATION, "Basic " + new String(Base64.encodeBase64((clientId + ":" + clientSecret).getBytes(UTF_8)), UTF_8)).
header(ACCEPT, APPLICATION_JSON).
header(USER_AGENT, Constants.PRODUCT_NAME + " " + Constants.VERSION).
header(CONTENT_TYPE, "application/x-www-form-urlencoded").
body(new URLParamUtil.ParamBuilder().add("grant_type", "client_credentials").add("scope", scope).build()).
buildExchange();
hc.call(exc, false, true);
if (exc.getResponse().getStatusCode() != 200)
throw new RuntimeException("Telekom Authentication Server returned: " + exc.getResponse());
HashMap values = Util.parseSimpleJSONResponse(exc.getResponse());
if (!values.containsKey("access_token") || !values.containsKey("expires_in"))
throw new Exception("Telekom Authentication: Received 200 and JSON body, but no access_token or no expires_in.");
token = values.get("access_token");
tokenExpiration = Long.parseLong(values.get("expires_in")) + System.currentTimeMillis() - 2000;
}
return token;
}
public String getScope() {
return scope;
}
/**
* @description The scope assigned to you by developergarden.com .
*/
@Required
@MCAttribute
public void setScope(String scope) {
this.scope = scope;
}
public String getClientId() {
return clientId;
}
/**
* @description The clientId assigned to you by developergarden.com .
*/
@Required
@MCAttribute
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
/**
* @description The clientSecret assigned to you by developergarden.com .
*/
@Required
@MCAttribute
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public String getSenderName() {
return senderName;
}
/**
* @description The sender name of the text messages. May only be set, if you are a member of the premium program on developergarded.com .
*/
@MCAttribute
public void setSenderName(String senderName) {
this.senderName = senderName;
}
public String getSenderAddress() {
return senderAddress;
}
/**
* @description The sender address (telephone number) of the text messages. May only be set, if you are a member of the premium program on developergarded.com .
*/
@MCAttribute
public void setSenderAddress(String senderAddress) {
this.senderAddress = senderAddress;
}
public EnvironmentType getEnvironment() {
return environment;
}
/**
* @description The environment (program name) you are paying for on developergarden.com .
* @example premium
* @default budget
*/
@MCAttribute
public void setEnvironment(EnvironmentType environment) {
this.environment = environment;
}
}