Commit 7ce94065 authored by armin's avatar armin
Browse files

Merge branch 'forwardGateway' into 'master'

Forward gateway

See merge request !5
parents a99bc9d4 c43b233d
......@@ -15,8 +15,21 @@ include_directories(libs/cpp-jwt/include)
include_directories(libs/cpp-base64)
set(SOURCE_FILES main.cpp HandlerFactory.cpp handlers/NorFoundHandler.cpp utils.cpp utils.h date.h handlers/ApplePushHandler.cpp handlers/ApplePushHandler.h handlers/GooglePushHandler.cpp handlers/GooglePushHandler.h models/GooglePushModel.cpp models/GooglePushModel.h models/ApplePushModel.cpp models/ApplePushModel.h
libs/cpp-jwt/include/jwt/jwt.hpp libs/cpp-base64/base64.h libs/cpp-base64/base64.cpp)
set(SOURCE_FILES
main.cpp
HandlerFactory.cpp
handlers/NorFoundHandler.cpp
utils.cpp
date.h
Settings.cpp
handlers/ApplePushHandler.cpp
handlers/GooglePushHandler.cpp
handlers/GooglePushHandler.h
models/GooglePushModel.cpp
models/ApplePushModel.cpp
models/ForwardGatewayModel.cpp
libs/cpp-base64/base64.cpp
)
add_executable(rocketChatMobilePushGateway ${SOURCE_FILES})
target_link_libraries(rocketChatMobilePushGateway -lwangle -lfolly -lz -lssl -lcrypto -levent -lgflags -lglog -L/usr/lib64 -lpthread -pthread -lfolly -lglog -ldouble-conversion -lboost_system -lboost_thread -lproxygenhttpserver -ljsoncpp -lcurl)
......@@ -7,7 +7,7 @@ ADD . /pushGateway/RocketChatMobilePushGateway
RUN cd /pushGateway/RocketChatMobilePushGateway \
&& cmake . \
&& make \
&& make -j$[$(nproc) + 1] \
&& rm CMake* -rf \
&& rm cmake* -rf
......
## lightweight, push gateway for RocketChat servers
Fork of https://git.fairkom.net/chat/RocketChatMobilePushGateway
## lightweight, push gateway for Rocket.Chat servers
### usage
......@@ -23,9 +21,23 @@ This project has dependencies, included via submodules, so you have to clone rec
- install libjsoncpp-dev, libcurlpp0-dev, cmake
- cmake
- make
- place the credentials in the servers "credentials" directory:
- FCM -> google/serverKey.txt
- APNS ->
- either:
- set the environment variables
- FCM_SERVER_KEY
- APNS_PRIVATE_KEY(PEM format)
1. create key (see https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token_based_connection_to_apns)
2. convert the *.p8 to pem
- wget https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar
- wget https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar.pubkey
- chmod +x jose.phar
- ./jose.phar key:convert:pkcs1 $(./jose.phar key:load:key ./AuthKey_*.p8) > key.pem
- APNS_TEAM_ID
- APNS_KEY
- APNS_APPID
- (depricated)place the credentials in the servers "credentials" directory:
- FCM -> google/serverKey.txt
- APNS ->
1. create key (see https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/establishing_a_token_based_connection_to_apns)
2. convert the *.p8 to pem
- wget https://github.com/web-token/jwt-app/raw/gh-pages/jose.phar
......@@ -40,7 +52,10 @@ This project has dependencies, included via submodules, so you have to clone rec
"key": "THE_KEY_RELATED_TO_THE_p8(also part of the name AuthKey_[KEY].p8)",
"appId": "YOUR_APP_ID (Bundle Id)"
}``
- opional
- enable forward gateway(allows to support chaining of gateways e.g. your own and gateway.rocket.chat, to support your own as well as the official apps)
- set environment variable FORWARD_GATEWAY_ENABLE=TRUE
- if neccessary set FORWARD_GATEWAY_URL (default is https://gateway.rocket.chat)
### Docker build
- run `docker build .`
- place the credentials in the servers "credentials" directory (see "manual build instructions" for details)
......
//
// Created by armin on 21.04.19.
//
#include <cstdlib>
#include <string>
#include <regex>
#include <proxygen/lib/utils/UtilInl.h>
#include "Settings.h"
std::string Settings::mForwardGatewayUrl;
bool Settings::mForwardGatewayEnabled = false;
std::string Settings::mFcmServerKey;
std::string Settings::mApnsPrivateKey;
std::string Settings::mApnsTeamId;
std::string Settings::mApnsKey;
std::string Settings::mApnsAppId;
void Settings::init() {
std::regex newLine("([\\n]+)");
auto enableForwardGateway = std::getenv("FORWARD_GATEWAY_ENABLE");
if(enableForwardGateway && std::string(enableForwardGateway) == "TRUE"){
mForwardGatewayEnabled = true;
}
auto forwardGatewayUrl = std::getenv("FORWARD_GATEWAY_URL");
if(forwardGatewayUrl){
mForwardGatewayUrl = std::regex_replace(std::string(forwardGatewayUrl),newLine,"");
}else{
mForwardGatewayUrl = "https://gateway.rocket.chat";
}
auto fcmServerKey = std::getenv("FCM_SERVER_KEY");
if(fcmServerKey){
mFcmServerKey = std::regex_replace(std::string(fcmServerKey),newLine,"");
}
auto apnsPrivateKey = std::getenv("APNS_PRIVATE_KEY");
if(apnsPrivateKey){
mApnsPrivateKey = std::string(apnsPrivateKey);
}
auto apnsTeamId = std::getenv("APNS_TEAM_ID");
if(apnsTeamId){
mApnsTeamId = std::string(apnsTeamId);
}
auto apnsKey = std::getenv("APNS_KEY");
if(apnsKey){
mApnsKey = std::string(apnsKey);
}
auto apnsAppId = std::getenv("APNS_APPID");
if(apnsAppId){
mApnsPrivateKey = std::string(apnsAppId);
}
}
bool Settings::forwardGatewayEnabled() {
return mForwardGatewayEnabled;
}
const std::string &Settings::forwardGatewayUrl() {
return mForwardGatewayUrl;
}
const std::string &Settings::fcmServerKey() {
return mFcmServerKey;
}
const std::string &Settings::apnsPrivateKey() {
return mApnsPrivateKey;
}
const std::string &Settings::apnsTeamId() {
return mApnsTeamId;
}
const std::string &Settings::apnsAppId() {
return mApnsAppId;
}
const std::string &Settings::apnsKey() {
return mApnsKey;
}
//
// Created by armin on 21.04.19.
//
#ifndef ROCKETCHATMOBILEPUSHGATEWAY_SETTINGS_H
#define ROCKETCHATMOBILEPUSHGATEWAY_SETTINGS_H
class Settings {
public:
static void init();
static bool forwardGatewayEnabled();
static const std::string &forwardGatewayUrl();
static const std::string &fcmServerKey();
static const std::string &apnsPrivateKey();
static const std::string &apnsTeamId();
static const std::string &apnsAppId();
static const std::string &apnsKey();
private:
static bool mForwardGatewayEnabled;
static std::string mForwardGatewayUrl;
static std::string mFcmServerKey;
static std::string mApnsPrivateKey;
static std::string mApnsTeamId;
static std::string mApnsKey;
static std::string mApnsAppId;
};
#endif //ROCKETCHATMOBILEPUSHGATEWAY_SETTINGS_H
......@@ -22,11 +22,12 @@
#include "ApplePushHandler.h"
#include "../models/ApplePushModel.h"
#include "../Settings.h"
#include "../models/ForwardGatewayModel.h"
void ApplePushHandler::onRequest(std::unique_ptr<HTTPMessage> headers) noexcept {
UNUSED(headers);
mHeaders = std::move(headers);
}
void ApplePushHandler::onBody(std::unique_ptr<folly::IOBuf> body) noexcept {
......@@ -38,7 +39,7 @@ void ApplePushHandler::onBody(std::unique_ptr<folly::IOBuf> body) noexcept {
}
void ApplePushHandler::onUpgrade(proxygen::UpgradeProtocol prot) noexcept {
UNUSED(prot);
UNUSED(prot)
}
void ApplePushHandler::onEOM() noexcept {
......@@ -50,14 +51,22 @@ void ApplePushHandler::onEOM() noexcept {
if (applePushModel.sendMessage()) {
ResponseBuilder(downstream_).status(200, "OK").body("").sendWithEOM();
} else {
ResponseBuilder(downstream_).status(500, "FAILURE").body("failed to send push mesage").sendWithEOM();
}else if(Settings::forwardGatewayEnabled()){
ForwardGatewayModel forwardModel;
if(forwardModel.forwardMessage(std::move(mHeaders), body)){
ResponseBuilder(downstream_).status(200, "OK").body("").sendWithEOM();
}else{
ResponseBuilder(downstream_).status(500, "FAILURE").body("failed to send push message through forwardgateway").sendWithEOM();
}
}
else {
ResponseBuilder(downstream_).status(500, "FAILURE").body("failed to send push message").sendWithEOM();
}
} catch (Exception &e) {
std::cout << "exception " << e.what() << std::endl;
ResponseBuilder(downstream_).status(500, "FAILURE").body("failed to send push mesage").sendWithEOM();
LOG(ERROR) << "exception " << e.what() << std::endl;
ResponseBuilder(downstream_).status(500, "FAILURE").body("failed to send push message").sendWithEOM();
}
} else {
ResponseBuilder(downstream_).status(400, "BAD REQUEST").body("failed to send push message").sendWithEOM();
......@@ -69,5 +78,5 @@ void ApplePushHandler::requestComplete() noexcept {
}
void ApplePushHandler::onError(ProxygenError err) noexcept {
UNUSED(err);
UNUSED(err)
}
......@@ -45,6 +45,7 @@ public:
private:
std::unique_ptr<folly::IOBuf> mBody;
std::unique_ptr<HTTPMessage> mHeaders;
};
......
......@@ -20,9 +20,12 @@
#define UNUSED(x) (void)x;
#include "GooglePushHandler.h"
#include "../models/GooglePushModel.h"
#include "../Settings.h"
#include "../models/ForwardGatewayModel.h"
void GooglePushHandler::onRequest(std::unique_ptr<HTTPMessage> headers) noexcept {
UNUSED(headers)
mHeaders = std::move(headers);
}
void GooglePushHandler::onBody(std::unique_ptr<folly::IOBuf> body) noexcept {
......@@ -46,14 +49,21 @@ void GooglePushHandler::onEOM() noexcept {
if (googlePushModel.sendMessage()) {
ResponseBuilder(downstream_).status(200, "OK").body("").sendWithEOM();
} else {
ResponseBuilder(downstream_).status(500, "FAILURE").body("failed to send push mesage").sendWithEOM();
} else if(Settings::forwardGatewayEnabled()){
ForwardGatewayModel forwardModel;
if(forwardModel.forwardMessage(std::move(mHeaders), body)){
ResponseBuilder(downstream_).status(200, "OK").body("").sendWithEOM();
}else{
ResponseBuilder(downstream_).status(500, "FAILURE").body("failed to send push message through forwardgateway").sendWithEOM();
}
}else {
ResponseBuilder(downstream_).status(500, "FAILURE").body("failed to send push message").sendWithEOM();
}
} catch (Exception &e) {
std::cout << "exception " << e.what() << std::endl;
ResponseBuilder(downstream_).status(500, "FAILURE").body("failed to send push mesage").sendWithEOM();
LOG(ERROR) << "exception " << e.what() << std::endl;
ResponseBuilder(downstream_).status(500, "FAILURE").body("failed to send push message").sendWithEOM();
}
} else {
ResponseBuilder(downstream_).status(400, "BAD REQUEST").body("failed to send push message").sendWithEOM();
......
......@@ -48,7 +48,7 @@ public:
private:
std::unique_ptr<folly::IOBuf> mBody;
std::unique_ptr<HTTPMessage> mHeaders;
};
......
......@@ -26,7 +26,7 @@ NotFoundHandler::NotFoundHandler() {
}
void NotFoundHandler::onRequest(std::unique_ptr<proxygen::HTTPMessage> headers) noexcept {
UNUSED(headers);
UNUSED(headers)
}
void NotFoundHandler::onBody(std::unique_ptr<folly::IOBuf> body) noexcept {
......@@ -43,7 +43,7 @@ void NotFoundHandler::onEOM() noexcept {
}
void NotFoundHandler::onUpgrade(proxygen::UpgradeProtocol proto) noexcept {
UNUSED(proto);
UNUSED(proto)
}
void NotFoundHandler::requestComplete() noexcept {
......@@ -51,5 +51,5 @@ void NotFoundHandler::requestComplete() noexcept {
}
void NotFoundHandler::onError(proxygen::ProxygenError err) noexcept {
UNUSED(err);
UNUSED(err)
}
\ No newline at end of file
Subproject commit 87dcef903f48a8c33df0bd9658d775d6d5db1918
Subproject commit 1cbc5eb5a54c45b79399483c4357e7e47100f474
......@@ -27,6 +27,8 @@
#include "HandlerFactory.h"
#include "models/GooglePushModel.h"
#include "models/ApplePushModel.h"
#include "models/ForwardGatewayModel.h"
#include "Settings.h"
using namespace proxygen;
......@@ -46,13 +48,21 @@ DEFINE_int32(threads, 0, "Number of threads to listen on. Numbers <= 0 "
int main(int argc, char* argv[]) {
FLAGS_logtostderr = true;
gflags::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
google::InstallFailureSignalHandler();
GooglePushModel::loadApiKey();
ApplePushModel::loadApiKey();
Settings::init();
if(Settings::fcmServerKey().empty()&&Settings::apnsPrivateKey().empty()) {
GooglePushModel::loadApiKey();
ApplePushModel::loadApiKey();
}else{
GooglePushModel::initFromSettings();
ApplePushModel::initFromSettings();
}
......
......@@ -18,6 +18,7 @@
* *
********************************************************************************************************************/
#include <curl/curl.h>
#include <exception>
#include <jsoncpp/json/json.h>
#include <iostream>
......@@ -41,6 +42,8 @@
#include "../libs/cpp-jwt/include/jwt/algorithm.hpp"
#include "../libs/cpp-jwt/include/jwt/parameters.hpp"
#include "../libs/cpp-base64/base64.h"
#include "ForwardGatewayModel.h"
#include "../Settings.h"
std::string ApplePushModel::mPem;
......@@ -65,7 +68,7 @@ ApplePushModel::ApplePushModel(const std::string &pJson) {
Json::Value apn = options["apn"];
std::string test(fast.write(options));
if (options.isMember("text")) {
std::string temp = std::move(options["text"].asString());
std::string temp = options["text"].asString();
unsigned long index = 0;
while (true) {
index = temp.find('\n', index);
......@@ -80,39 +83,55 @@ ApplePushModel::ApplePushModel(const std::string &pJson) {
}
if (options.isMember("title")) {
mTitle = std::move(options["title"].asString());
mTitle = options["title"].asString();
}
/*if (options.isMember("text")) {
mText = std::move(options["text"].asString());
}*/
if (options.isMember("from")) {
mFrom = std::move(options["from"].asString());
mFrom = options["from"].asString();
}
if (options.isMember("badge")) {
mBadge = options["badge"].asInt();
}
if (options.isMember("payload")) {
//std::string test = fast.write(options["payload"]);
// std::string test4 = Json::valueToQuotedString(test.c_str());
mPayload = std::move(fast.write(options["payload"]));
mPayload = fast.write(options["payload"]);
}
if (options.isMember("topic")) {
mTopic = std::move(options["topic"].asString());
mTopic = options["topic"].asString();
}
if (options.isMember("sound")) {
mSound = std::move(options["sound"].asString());
mSound = options["sound"].asString();
}
mDeviceToken = std::move(obj["token"].asString());
mDeviceToken = obj["token"].asString();
}
}
}
size_t ApplePushModel::curlWriteCallback(void *buffer, size_t size, size_t nmemb,
void *this_ptr) {
auto thiz = static_cast<ApplePushModel *>(this_ptr);
Json::Reader reader;
Json::Value obj;
if (buffer != nullptr) {
std::string bufferString(static_cast<char *>(buffer), nmemb);
reader.parse(bufferString, obj);
if (obj.isMember("reason") && obj["reason"].asString() == "DeviceTokenNotForTopic") {
ForwardGatewayModel::claimRegistrationId(thiz->mDeviceToken);
return 0;
}
}
return 1;
}
bool ApplePushModel::sendMessage() {
if (ForwardGatewayModel::ownsRegistrationId(mDeviceToken)) {
return false;
}
Json::Value obj;
Json::FastWriter fast;
......@@ -134,12 +153,10 @@ bool ApplePushModel::sendMessage() {
using namespace date;
using namespace std::chrono;
boost::uuids::uuid uuidObj = boost::uuids::random_generator()();
std::string uuidString = boost::lexical_cast<std::string>(uuidObj);
std::cout << "[" << system_clock::now() << "]\t" << uuidString << "\tApple push data\t" << json << std::endl;
CURL *curl;
CURLcode res;
......@@ -150,11 +167,11 @@ bool ApplePushModel::sendMessage() {
try {
jwt::jwt_object obj{jwt::params::algorithm("ES256"),
jwt::params::headers({
{"alg", "ES256"},
{"kid", mKey}
}),
jwt::params::secret(mPem)
jwt::params::headers({
{"alg", "ES256"},
{"kid", mKey}
}),
jwt::params::secret(mPem)
};
auto n = system_clock::now();
......@@ -164,51 +181,51 @@ bool ApplePushModel::sendMessage() {
std::string encoded_jwt = obj.signature();
chunk = curl_slist_append(chunk, std::string("Authorization: Bearer "+obj.signature()).c_str());
chunk = curl_slist_append(chunk, std::string("apns-id: "+uuidString).c_str());
chunk = curl_slist_append(chunk, std::string("Authorization: Bearer " + obj.signature()).c_str());
chunk = curl_slist_append(chunk, std::string("apns-id: " + uuidString).c_str());
chunk = curl_slist_append(chunk, std::string("apns-expiration: 0").c_str());
chunk = curl_slist_append(chunk, std::string("apns-priority: 10").c_str());
chunk = curl_slist_append(chunk, std::string("apns-topic: "+mAppId).c_str());
chunk = curl_slist_append(chunk, std::string("apns-topic: " + mAppId).c_str());
std::string url = mApiUrl+mDeviceToken;
std::cout << "[" << system_clock::now() << "]\t" << uuidString << "\tApple push url\t" << url << std::endl;
std::string url = mApiUrl + mDeviceToken;
// curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, trace );
curl_easy_setopt(curl, CURLOPT_VERBOSE, false);
curl_easy_setopt(curl, CURLOPT_URL,url.c_str());
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json.c_str());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,json.size());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, json.size());
curl_easy_setopt(curl, CURLOPT_POST, true);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
curl_easy_setopt(curl, CURLOPT_USE_SSL, true);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, true);
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
std::cout << "[" << system_clock::now() << "]\t" << uuidString << "\tApple push result\t";
res = curl_easy_perform(curl);
std::cout << std::endl;
if (res != CURLE_OK) {
std::cerr<< "[" << system_clock::now() << "]\t" << uuidString << "\tApple push conn error: " << curl_easy_strerror(res) << std::endl;
if (res == CURLE_WRITE_ERROR) {
LOG(INFO) << "\tApple push device token rejected, forward message to forwardgateway";
} else {
LOG(ERROR) << "\tApple push conn error: " << curl_easy_strerror(res);
}
} else {
std::cout << "[" << system_clock::now() << "]\t" << uuidString << "\tApple push conn status: OK " << std::endl;
LOG(INFO) << uuidString << "\tApple push conn status: OK";
curl_easy_cleanup(curl);
curl_slist_free_all(chunk);
return true;
}
curl_easy_cleanup(curl);
curl_slist_free_all(chunk);
} catch (std::exception &e) {
std::cout << "[" << system_clock::now() << "] " << e.what() << std::endl;
LOG(INFO) << e.what();
curl_easy_cleanup(curl);
curl_slist_free_all(chunk);
return false;
}
curl_easy_cleanup(curl);
curl_slist_free_all(chunk);
}
return false;
......@@ -217,29 +234,36 @@ bool ApplePushModel::sendMessage() {
void ApplePushModel::loadApiKey() {
std::ifstream ifsPem("/certs/apple/key.pem");
std::ifstream ifsSettings("/certs/apple/settings.json");
std::string pemContent((std::istreambuf_iterator<char>(ifsPem)),(std::istreambuf_iterator<char>()));
std::string settingsContent((std::istreambuf_iterator<char>(ifsSettings)),(std::istreambuf_iterator<char>()));
if(pemContent.length()&&settingsContent.length()){
std::string pemContent((std::istreambuf_iterator<char>(ifsPem)), (std::istreambuf_iterator<char>()));
std::string settingsContent((std::istreambuf_iterator<char>(ifsSettings)), (std::istreambuf_iterator<char>()));
if (pemContent.length() && settingsContent.length()) {
mPem = pemContent;
Json::Reader reader;
Json::Value obj;
reader.parse(settingsContent,obj);
if(obj.isMember("appId")&&obj.isMember("teamId")&&obj.isMember("key")){
std::string appId = std::move(obj["appId"].asString());
std::string teamId = std::move(obj["teamId"].asString());
std::string key = std::move(obj["key"].asString());
if(appId.length()&&teamId.length()&&key.length()){
reader.parse(settingsContent, obj);
if (obj.isMember("appId") && obj.isMember("teamId") && obj.isMember("key")) {
std::string appId = obj["appId"].asString();
std::string teamId = obj["teamId"].asString();
std::string key = obj["key"].asString();
if (appId.length() && teamId.length() && key.length()) {
mAppId = std::move(appId);
mTeamId = std::move(teamId);
mKey = std::move(key);
}else{
std::cout << "Error JSON data invalid" << std::endl;
} else {
LOG(ERROR) << "Error JSON data invalid";
exit(EXIT_FAILURE);
}
}
}else{
std::cout << "Error loading APNS credentials, check if the settings.json, and key.pem exists" << std::endl;
} else {