Support for registering OAuth2 users using spectrum2_manager + lot of Slack improvements

This commit is contained in:
Jan Kaluza 2015-11-24 12:26:26 +01:00
parent ad127c5dd6
commit 5adb3d1f97
31 changed files with 959 additions and 51 deletions

View file

@ -92,6 +92,7 @@ class Frontend {
virtual void addRoomToRoomList(const std::string &handle, const std::string &name) = 0; virtual void addRoomToRoomList(const std::string &handle, const std::string &name) = 0;
virtual std::string setOAuth2Code(const std::string &code, const std::string &state) { return "OAuth2 code is not needed for this frontend."; } virtual std::string setOAuth2Code(const std::string &code, const std::string &state) { return "OAuth2 code is not needed for this frontend."; }
virtual std::string getOAuth2URL(const std::vector<std::string> &args) { return ""; }
boost::signal<void (User *, const std::string &name, unsigned int id)> onVCardRequired; boost::signal<void (User *, const std::string &name, unsigned int id)> onVCardRequired;
boost::signal<void (User *, boost::shared_ptr<Swift::VCard> vcard)> onVCardUpdated; boost::signal<void (User *, boost::shared_ptr<Swift::VCard> vcard)> onVCardUpdated;

View file

@ -1,20 +1,27 @@
#ifndef HTTPREQ_H
#define HTTPREQ_H #pragma once
#include "curl/curl.h" #include "curl/curl.h"
#include "transport/Logging.h" #include "transport/Logging.h"
#include "transport/ThreadPool.h"
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <string.h> #include <string.h>
#include "rapidjson/document.h" #include "rapidjson/document.h"
#include <boost/signal.hpp>
namespace Transport { namespace Transport {
class HTTPRequest { class HTTPRequest : public Thread {
public: public:
HTTPRequest(); typedef enum { Get } Type;
typedef boost::function< void (HTTPRequest *, bool, rapidjson::Document &json, const std::string &data) > Callback;
~HTTPRequest() { HTTPRequest(ThreadPool *tp, Type type, const std::string &url, Callback callback);
HTTPRequest(Type type, const std::string &url);
virtual ~HTTPRequest() {
if(curlhandle) { if(curlhandle) {
curl_easy_cleanup(curlhandle); curl_easy_cleanup(curlhandle);
curlhandle = NULL; curlhandle = NULL;
@ -22,17 +29,32 @@ class HTTPRequest {
} }
void setProxy(std::string, std::string, std::string, std::string); void setProxy(std::string, std::string, std::string, std::string);
bool GET(std::string url, std::string &output); bool execute();
bool GET(std::string url, rapidjson::Document &json); bool execute(rapidjson::Document &json);
std::string getCurlError() {return std::string(curl_errorbuffer);} std::string getError() {return std::string(curl_errorbuffer);}
void run();
void finalize();
boost::signal<void ()> onRequestFinished;
private: private:
bool init(); bool init();
bool GET(std::string url, std::string &output);
bool GET(std::string url, rapidjson::Document &json);
CURL *curlhandle; CURL *curlhandle;
char curl_errorbuffer[1024]; char curl_errorbuffer[1024];
std::string error; std::string error;
std::string callbackdata; std::string callbackdata;
ThreadPool *m_tp;
std::string m_url;
bool m_ok;
rapidjson::Document m_json;
std::string m_data;
Callback m_callback;
Type m_type;
static int curlCallBack(char* data, size_t size, size_t nmemb, HTTPRequest *obj); static int curlCallBack(char* data, size_t size, size_t nmemb, HTTPRequest *obj);
@ -40,4 +62,3 @@ class HTTPRequest {
} }
#endif

View file

@ -0,0 +1,33 @@
#pragma once
#include "curl/curl.h"
#include "transport/Logging.h"
#include "transport/ThreadPool.h"
#include <iostream>
#include <sstream>
#include <string.h>
#include "rapidjson/document.h"
namespace Transport {
class HTTPRequest;
class HTTPRequestQueue {
public:
HTTPRequestQueue(int delayBetweenRequests = 1);
virtual ~HTTPRequestQueue();
void queueRequest(HTTPRequest *req);
void sendNextRequest();
private:
int m_delay;
std::queue<HTTPRequest *> m_queue;
bool m_processing;
};
}

View file

@ -83,6 +83,8 @@ class MySQLBackend : public StorageBackend
bool getOnlineUsers(std::vector<std::string> &users); bool getOnlineUsers(std::vector<std::string> &users);
bool getUsers(std::vector<std::string> &users);
long addBuddy(long userId, const BuddyInfo &buddyInfo); long addBuddy(long userId, const BuddyInfo &buddyInfo);
void updateBuddy(long userId, const BuddyInfo &buddyInfo); void updateBuddy(long userId, const BuddyInfo &buddyInfo);
@ -156,6 +158,7 @@ class MySQLBackend : public StorageBackend
Statement *m_getBuddiesSettings; Statement *m_getBuddiesSettings;
Statement *m_setUserOnline; Statement *m_setUserOnline;
Statement *m_getOnlineUsers; Statement *m_getOnlineUsers;
Statement *m_getUsers;
}; };
} }

View file

@ -35,7 +35,11 @@ class OAuth2 {
std::string generateAuthURL(); std::string generateAuthURL();
std::string handleOAuth2Code(const std::string &code, const std::string &state); const std::string &getState() {
return m_state;
}
std::string requestToken(const std::string &code, std::string &error);
private: private:
std::string m_clientId; std::string m_clientId;

View file

@ -83,6 +83,8 @@ class PQXXBackend : public StorageBackend
bool getOnlineUsers(std::vector<std::string> &users); bool getOnlineUsers(std::vector<std::string> &users);
bool getUsers(std::vector<std::string> &users);
long addBuddy(long userId, const BuddyInfo &buddyInfo); long addBuddy(long userId, const BuddyInfo &buddyInfo);
void updateBuddy(long userId, const BuddyInfo &buddyInfo); void updateBuddy(long userId, const BuddyInfo &buddyInfo);

View file

@ -72,6 +72,8 @@ class SQLite3Backend : public StorageBackend
bool getOnlineUsers(std::vector<std::string> &users); bool getOnlineUsers(std::vector<std::string> &users);
bool getUsers(std::vector<std::string> &users);
/// Removes user and all connected data from database. /// Removes user and all connected data from database.
/// \param id id of user - UserInfo.id /// \param id id of user - UserInfo.id
/// \return true if user has been found in database and removed /// \return true if user has been found in database and removed
@ -124,6 +126,7 @@ class SQLite3Backend : public StorageBackend
sqlite3_stmt *m_getBuddiesSettings; sqlite3_stmt *m_getBuddiesSettings;
sqlite3_stmt *m_setUserOnline; sqlite3_stmt *m_setUserOnline;
sqlite3_stmt *m_getOnlineUsers; sqlite3_stmt *m_getOnlineUsers;
sqlite3_stmt *m_getUsers;
}; };
} }

View file

@ -123,6 +123,8 @@ class StorageBackend
/// getOnlineUsers /// getOnlineUsers
virtual bool getOnlineUsers(std::vector<std::string> &users) = 0; virtual bool getOnlineUsers(std::vector<std::string> &users) = 0;
virtual bool getUsers(std::vector<std::string> &users) = 0;
virtual long addBuddy(long userId, const BuddyInfo &buddyInfo) = 0; virtual long addBuddy(long userId, const BuddyInfo &buddyInfo) = 0;
virtual void updateBuddy(long userId, const BuddyInfo &buddyInfo) = 0; virtual void updateBuddy(long userId, const BuddyInfo &buddyInfo) = 0;
virtual void removeBuddy(long id) = 0; virtual void removeBuddy(long id) = 0;

View file

@ -1,5 +1,4 @@
#ifndef THREAD_POOL #pragma once
#define THREAD_POOL
#include <boost/thread.hpp> #include <boost/thread.hpp>
#include <boost/thread/mutex.hpp> #include <boost/thread/mutex.hpp>
@ -8,6 +7,7 @@
#include <iostream> #include <iostream>
#include "Swiften/EventLoop/EventLoop.h" #include "Swiften/EventLoop/EventLoop.h"
namespace Transport {
/* /*
* Thread serves as a base class for any code that has to be excuted as a thread * Thread serves as a base class for any code that has to be excuted as a thread
@ -70,4 +70,4 @@ class ThreadPool
void releaseThread(int i); void releaseThread(int i);
}; };
#endif }

View file

@ -0,0 +1,133 @@
/**
* XMPP - libpurple transport
*
* Copyright (C) 2009, Jan Kaluza <hanzz@soc.pidgin.im>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include "SlackAPI.h"
#include "SlackFrontend.h"
#include "SlackUser.h"
#include "SlackRTM.h"
#include "transport/Transport.h"
#include "transport/HTTPRequest.h"
#include "transport/Util.h"
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <map>
#include <iterator>
namespace Transport {
DEFINE_LOGGER(logger, "SlackAPI");
SlackAPI::SlackAPI(Component *component, UserInfo uinfo) : m_uinfo(uinfo) {
m_component = component;
}
SlackAPI::~SlackAPI() {
}
void SlackAPI::handleSendMessage(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data) {
LOG4CXX_INFO(logger, data);
}
void SlackAPI::sendMessage(const std::string &from, const std::string &to, const std::string &text) {
std::string url = "https://slack.com/api/chat.postMessage?";
url += "&username=" + Util::urlencode(from);
url += "&channel=" + Util::urlencode(to);
url += "&text=" + Util::urlencode(text);
url += "&token=" + Util::urlencode(m_uinfo.encoding);
HTTPRequest *req = new HTTPRequest(THREAD_POOL(m_component), HTTPRequest::Get, url,
boost::bind(&SlackAPI::handleSendMessage, this, _1, _2, _3, _4));
queueRequest(req);
}
std::string SlackAPI::getChannelId(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data) {
if (!ok) {
LOG4CXX_ERROR(logger, req->getError());
LOG4CXX_ERROR(logger, data);
return "";
}
rapidjson::Value &channel = resp["channel"];
if (!channel.IsObject()) {
LOG4CXX_ERROR(logger, "No 'channel' object in the reply.");
LOG4CXX_ERROR(logger, data);
return "";
}
rapidjson::Value &id = channel["id"];
if (!id.IsString()) {
LOG4CXX_ERROR(logger, "No 'id' string in the reply.");
LOG4CXX_ERROR(logger, data);
return "";
}
return id.GetString();
}
void SlackAPI::imOpen(const std::string &uid, HTTPRequest::Callback callback) {
std::string url = "https://slack.com/api/im.open?user=" + Util::urlencode(uid) + "&token=" + Util::urlencode(m_uinfo.encoding);
HTTPRequest *req = new HTTPRequest(THREAD_POOL(m_component), HTTPRequest::Get, url, callback);
queueRequest(req);
}
std::string SlackAPI::getOwnerId(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data) {
if (!ok) {
LOG4CXX_ERROR(logger, req->getError());
return "";
}
rapidjson::Value &members = resp["members"];
if (!members.IsArray()) {
LOG4CXX_ERROR(logger, "No 'members' object in the reply.");
return "";
}
for (int i = 0; i < members.Size(); i++) {
if (!members[i].IsObject()) {
continue;
}
rapidjson::Value &is_primary_owner = members[i]["is_primary_owner"];
if (!is_primary_owner.IsBool()) {
continue;
}
if (is_primary_owner.GetBool()) {
rapidjson::Value &name = members[i]["id"];
if (!name.IsString()) {
LOG4CXX_ERROR(logger, "No 'name' string in the reply.");
return "";
}
return name.GetString();
}
}
return "";
}
void SlackAPI::usersList(HTTPRequest::Callback callback) {
std::string url = "https://slack.com/api/users.list?presence=0&token=" + Util::urlencode(m_uinfo.encoding);
HTTPRequest *req = new HTTPRequest(THREAD_POOL(m_component), HTTPRequest::Get, url, callback);
queueRequest(req);
}
}

View file

@ -0,0 +1,63 @@
/**
* Spectrum 2 Slack Frontend
*
* Copyright (C) 2015, Jan Kaluza <hanzz.k@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#pragma once
#include "transport/HTTPRequestQueue.h"
#include "transport/HTTPRequest.h"
#include "transport/StorageBackend.h"
#include "rapidjson/document.h"
#include <string>
#include <algorithm>
#include <map>
#include <boost/signal.hpp>
namespace Transport {
class Component;
class StorageBackend;
class HTTPRequest;
class SlackRTM;
class SlackAPI : public HTTPRequestQueue {
public:
SlackAPI(Component *component, UserInfo uinfo);
virtual ~SlackAPI();
void usersList(HTTPRequest::Callback callback);
std::string getOwnerId(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data);
void imOpen(const std::string &uid, HTTPRequest::Callback callback);
std::string getChannelId(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data);
void sendMessage(const std::string &from, const std::string &to, const std::string &text);
private:
void handleSendMessage(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data);
private:
Component *m_component;
UserInfo m_uinfo;
};
}

View file

@ -29,7 +29,7 @@
#include "transport/Logging.h" #include "transport/Logging.h"
#include "transport/Config.h" #include "transport/Config.h"
#include "transport/Transport.h" #include "transport/Transport.h"
#include "transport/OAuth2.h" #include "transport/ThreadPool.h"
#include <boost/bind.hpp> #include <boost/bind.hpp>
#include <boost/smart_ptr/make_shared.hpp> #include <boost/smart_ptr/make_shared.hpp>
@ -53,18 +53,10 @@ void SlackFrontend::init(Component *transport, Swift::EventLoop *loop, Swift::Ne
m_transport = transport; m_transport = transport;
m_config = transport->getConfig(); m_config = transport->getConfig();
m_jid = Swift::JID(CONFIG_STRING(m_config, "service.jid")); m_jid = Swift::JID(CONFIG_STRING(m_config, "service.jid"));
m_tp = new ThreadPool(loop, 10);
std::string redirect_url = "http://spectrum.im/slackoauth2/" + CONFIG_STRING(m_config, "service.jid");
m_oauth2 = new OAuth2(CONFIG_STRING_DEFAULTED(m_config, "service.client_id",""),
CONFIG_STRING_DEFAULTED(m_config, "service.client_secret",""),
"https://slack.com/oauth/authorize",
"https://slack.com/api/oauth.access",
redirect_url,
"channels:read channels:write team:read");
} }
SlackFrontend::~SlackFrontend() { SlackFrontend::~SlackFrontend() {
delete m_oauth2;
} }
void SlackFrontend::clearRoomList() { void SlackFrontend::clearRoomList() {
@ -97,7 +89,7 @@ boost::shared_ptr<Swift::DiscoInfo> SlackFrontend::sendCapabilitiesRequest(Swift
} }
void SlackFrontend::reconnectUser(const std::string &user) { void SlackFrontend::reconnectUser(const std::string &user) {
return static_cast<SlackUserManager *>(m_userManager)->reconnectUser(user);
} }
RosterManager *SlackFrontend::createRosterManager(User *user, Component *component) { RosterManager *SlackFrontend::createRosterManager(User *user, Component *component) {
@ -109,20 +101,22 @@ User *SlackFrontend::createUser(const Swift::JID &jid, UserInfo &userInfo, Compo
} }
UserManager *SlackFrontend::createUserManager(Component *component, UserRegistry *userRegistry, StorageBackend *storageBackend) { UserManager *SlackFrontend::createUserManager(Component *component, UserRegistry *userRegistry, StorageBackend *storageBackend) {
return new SlackUserManager(component, userRegistry, storageBackend); m_userManager = new SlackUserManager(component, userRegistry, storageBackend);
return m_userManager;
} }
void SlackFrontend::connectToServer() { void SlackFrontend::connectToServer() {
LOG4CXX_INFO(logger, "Connecting to Slack API server"); LOG4CXX_INFO(logger, "Started.");
m_transport->handleConnected();
std::string url = m_oauth2->generateAuthURL();
LOG4CXX_INFO(logger, url);
} }
std::string SlackFrontend::setOAuth2Code(const std::string &code, const std::string &state) { std::string SlackFrontend::setOAuth2Code(const std::string &code, const std::string &state) {
LOG4CXX_INFO(logger, "Using OAuth2 code " << code << " to get the authorization token"); return static_cast<SlackUserManager *>(m_userManager)->handleOAuth2Code(code, state);
return m_oauth2->handleOAuth2Code(code, state); }
std::string SlackFrontend::getOAuth2URL(const std::vector<std::string> &args) {
return static_cast<SlackUserManager *>(m_userManager)->getOAuth2URL(args);
} }
void SlackFrontend::disconnectFromServer() { void SlackFrontend::disconnectFromServer() {

View file

@ -25,13 +25,15 @@
#include <vector> #include <vector>
#include <boost/bind.hpp> #include <boost/bind.hpp>
#define THREAD_POOL(X) static_cast<SlackFrontend *>(X->getFrontend())->getThreadPool()
namespace Transport { namespace Transport {
class UserRegistry; class UserRegistry;
class Frontend; class Frontend;
class Config; class Config;
class DiscoItemsResponder; class DiscoItemsResponder;
class VCardResponder; class VCardResponder;
class OAuth2; class ThreadPool;
class SlackFrontend : public Frontend { class SlackFrontend : public Frontend {
public: public:
@ -64,13 +66,19 @@ namespace Transport {
virtual void clearRoomList(); virtual void clearRoomList();
virtual void addRoomToRoomList(const std::string &handle, const std::string &name); virtual void addRoomToRoomList(const std::string &handle, const std::string &name);
virtual std::string setOAuth2Code(const std::string &code, const std::string &state); virtual std::string setOAuth2Code(const std::string &code, const std::string &state);
virtual std::string getOAuth2URL(const std::vector<std::string> &args);
void handleMessage(boost::shared_ptr<Swift::Message> message); void handleMessage(boost::shared_ptr<Swift::Message> message);
ThreadPool *getThreadPool() {
return m_tp;
}
private: private:
Config* m_config; Config* m_config;
Swift::JID m_jid; Swift::JID m_jid;
Component *m_transport; Component *m_transport;
OAuth2 *m_oauth2; UserManager *m_userManager;
ThreadPool *m_tp;
}; };
} }

View file

@ -0,0 +1,80 @@
/**
* XMPP - libpurple transport
*
* Copyright (C) 2009, Jan Kaluza <hanzz@soc.pidgin.im>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include "SlackInstallation.h"
#include "SlackFrontend.h"
#include "SlackUser.h"
#include "SlackRTM.h"
#include "SlackAPI.h"
#include "transport/Transport.h"
#include "transport/HTTPRequest.h"
#include "transport/Util.h"
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <map>
#include <iterator>
namespace Transport {
DEFINE_LOGGER(logger, "SlackInstallation");
SlackInstallation::SlackInstallation(Component *component, StorageBackend *storageBackend, UserInfo uinfo) : m_uinfo(uinfo) {
m_component = component;
m_storageBackend = storageBackend;
m_api = new SlackAPI(component, uinfo);
m_api->usersList(boost::bind(&SlackInstallation::handleUsersList, this, _1, _2, _3, _4));
// m_rtm = new SlackRTM(component, storageBackend, uinfo);
}
SlackInstallation::~SlackInstallation() {
// delete m_rtm;
delete m_api;
}
void SlackInstallation::handleImOpen(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data) {
std::string channel = m_api->getChannelId(req, ok, resp, data);
LOG4CXX_INFO(logger, "Opened channel with team owner: " << channel);
std::string msg;
msg = "Hi, It seems you have authorized Spectrum 2 transport for your team. "
"As a team owner, you should now configure it. You should provide username and "
"password you want to use to connect your team to legacy network of your choice.";
m_api->sendMessage("Spectrum 2", channel, msg);
msg = "You can do it by typing \".spectrum2 register <username> <password>\". Password may be optional.";
m_api->sendMessage("Spectrum 2", channel, msg);
msg = "For example to connect the Freenode IRC network, just type \".spectrum2 register irc.freenode.net\".";
m_api->sendMessage("Spectrum 2", channel, msg);
}
void SlackInstallation::handleUsersList(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data) {
std::string ownerId = m_api->getOwnerId(req, ok, resp, data);
LOG4CXX_INFO(logger, "Team owner ID is " << ownerId);
m_api->imOpen(ownerId, boost::bind(&SlackInstallation::handleImOpen, this, _1, _2, _3, _4));
}
}

View file

@ -0,0 +1,61 @@
/**
* Spectrum 2 Slack Frontend
*
* Copyright (C) 2015, Jan Kaluza <hanzz.k@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#pragma once
#include "transport/StorageBackend.h"
#include "rapidjson/document.h"
#include <string>
#include <algorithm>
#include <map>
#include <boost/signal.hpp>
namespace Transport {
class Component;
class StorageBackend;
class HTTPRequest;
class SlackRTM;
class SlackAPI;
class SlackInstallation {
public:
SlackInstallation(Component *component, StorageBackend *storageBackend, UserInfo uinfo);
virtual ~SlackInstallation();
boost::signal<void (const std::string &user)> onInstallationDone;
private:
void handleUsersList(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data);
void handleImOpen(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data);
private:
Component *m_component;
StorageBackend *m_storageBackend;
UserInfo m_uinfo;
std::string m_ownerName;
SlackRTM *m_rtm;
SlackAPI *m_api;
};
}

View file

@ -0,0 +1,120 @@
/**
* XMPP - libpurple transport
*
* Copyright (C) 2009, Jan Kaluza <hanzz@soc.pidgin.im>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include "SlackRTM.h"
#include "SlackFrontend.h"
#include "SlackUser.h"
#include "transport/Transport.h"
#include "transport/HTTPRequest.h"
#include "transport/Util.h"
#include <boost/foreach.hpp>
#include <boost/make_shared.hpp>
#include <map>
#include <iterator>
namespace Transport {
DEFINE_LOGGER(logger, "SlackRTM");
SlackRTM::SlackRTM(Component *component, StorageBackend *storageBackend, UserInfo uinfo) : m_uinfo(uinfo) {
m_component = component;
m_storageBackend = storageBackend;
Swift::TLSOptions o;
Swift::PlatformTLSFactories *m_tlsFactory = new Swift::PlatformTLSFactories();
m_tlsConnectionFactory = new Swift::TLSConnectionFactory(m_tlsFactory->getTLSContextFactory(), component->getNetworkFactories()->getConnectionFactory(), o);
std::string url = "https://slack.com/api/rtm.start?";
url += "token=" + Util::urlencode(m_uinfo.encoding);
// HTTPRequest *req = new HTTPRequest();
// req->GET(THREAD_POOL(m_component), url,
// boost::bind(&SlackRTM::handleRTMStart, this, _1, _2, _3, _4));
}
SlackRTM::~SlackRTM() {
}
void SlackRTM::handleRTMStart(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data) {
if (!ok) {
LOG4CXX_ERROR(logger, req->getError());
LOG4CXX_ERROR(logger, data);
return;
}
rapidjson::Value &url = resp["url"];
if (!url.IsString()) {
LOG4CXX_ERROR(logger, "No 'url' object in the reply.");
LOG4CXX_ERROR(logger, data);
return;
}
std::string u = url.GetString();
LOG4CXX_INFO(logger, "Started RTM, WebSocket URL is " << u);
u = u.substr(6);
m_host = u.substr(0, u.find("/"));
m_path = u.substr(u.find("/"));
LOG4CXX_INFO(logger, "Starting DNS query for " << m_host << " " << m_path);
m_dnsQuery = m_component->getNetworkFactories()->getDomainNameResolver()->createAddressQuery(m_host);
m_dnsQuery->onResult.connect(boost::bind(&SlackRTM::handleDNSResult, this, _1, _2));
m_dnsQuery->run();
}
void SlackRTM::handleDataRead(boost::shared_ptr<Swift::SafeByteArray> data) {
LOG4CXX_INFO(logger, "data read");
std::string d = Swift::safeByteArrayToString(*data);
LOG4CXX_INFO(logger, d);
}
void SlackRTM::handleConnected(bool error) {
if (error) {
LOG4CXX_ERROR(logger, "Connection to " << m_host << " failed");
return;
}
LOG4CXX_INFO(logger, "Connected to " << m_host);
std::string req = "";
req += "GET " + m_path + " HTTP/1.1\r\n";
req += "Host: " + m_host + ":443\r\n";
req += "Upgrade: websocket\r\n";
req += "Connection: Upgrade\r\n";
req += "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n";
req += "Sec-WebSocket-Version: 13\r\n";
req += "\r\n";
m_conn->write(Swift::createSafeByteArray(req));
}
void SlackRTM::handleDNSResult(const std::vector<Swift::HostAddress> &addrs, boost::optional<Swift::DomainNameResolveError>) {
m_conn = m_tlsConnectionFactory->createConnection();
m_conn->onDataRead.connect(boost::bind(&SlackRTM::handleDataRead, this, _1));
m_conn->onConnectFinished.connect(boost::bind(&SlackRTM::handleConnected, this, _1));
m_conn->connect(Swift::HostAddressPort(addrs[0], 443));
}
}

View file

@ -0,0 +1,71 @@
/**
* Spectrum 2 Slack Frontend
*
* Copyright (C) 2015, Jan Kaluza <hanzz.k@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#pragma once
#include "transport/StorageBackend.h"
#include "rapidjson/document.h"
#include <Swiften/Network/TLSConnectionFactory.h>
#include <Swiften/Network/HostAddressPort.h>
#include <Swiften/TLS/PlatformTLSFactories.h>
#include <Swiften/TLS/TLSOptions.h>
#include <Swiften/Network/DomainNameResolveError.h>
#include <Swiften/Network/DomainNameAddressQuery.h>
#include <Swiften/Network/DomainNameResolver.h>
#include <Swiften/Network/HostAddress.h>
#include <Swiften/Base/SafeByteArray.h>
#include <string>
#include <algorithm>
#include <map>
#include <boost/signal.hpp>
namespace Transport {
class Component;
class StorageBackend;
class HTTPRequest;
class SlackRTM {
public:
SlackRTM(Component *component, StorageBackend *storageBackend, UserInfo uinfo);
virtual ~SlackRTM();
private:
void handleDNSResult(const std::vector<Swift::HostAddress>&, boost::optional<Swift::DomainNameResolveError>);
void handleDataRead(boost::shared_ptr<Swift::SafeByteArray> data);
void handleConnected(bool error);
void handleRTMStart(HTTPRequest *req, bool ok, rapidjson::Document &resp, const std::string &data);
private:
Component *m_component;
StorageBackend *m_storageBackend;
UserInfo m_uinfo;
boost::shared_ptr<Swift::DomainNameAddressQuery> m_dnsQuery;
boost::shared_ptr<Swift::Connection> m_conn;
Swift::TLSConnectionFactory *m_tlsConnectionFactory;
std::string m_host;
std::string m_path;
};
}

View file

@ -21,6 +21,7 @@
#include "SlackUserManager.h" #include "SlackUserManager.h"
#include "SlackUserRegistration.h" #include "SlackUserRegistration.h"
#include "SlackFrontend.h" #include "SlackFrontend.h"
#include "SlackInstallation.h"
#include "transport/User.h" #include "transport/User.h"
#include "transport/Transport.h" #include "transport/Transport.h"
@ -33,6 +34,7 @@ DEFINE_LOGGER(logger, "SlackUserManager");
SlackUserManager::SlackUserManager(Component *component, UserRegistry *userRegistry, StorageBackend *storageBackend) : UserManager(component, userRegistry, storageBackend) { SlackUserManager::SlackUserManager(Component *component, UserRegistry *userRegistry, StorageBackend *storageBackend) : UserManager(component, userRegistry, storageBackend) {
m_component = component; m_component = component;
m_storageBackend = storageBackend;
m_userRegistration = new SlackUserRegistration(component, this, storageBackend); m_userRegistration = new SlackUserRegistration(component, this, storageBackend);
} }
@ -40,6 +42,30 @@ SlackUserManager::~SlackUserManager() {
delete m_userRegistration; delete m_userRegistration;
} }
void SlackUserManager::reconnectUser(const std::string &user) {
UserInfo uinfo;
if (!m_storageBackend->getUser(user, uinfo)) {
LOG4CXX_ERROR(logger, "User " << user << " tried to reconnect, but he's not registered.");
return;
}
if (!uinfo.uin.empty()) {
LOG4CXX_INFO(logger, "Reconnecting user " << user);
Swift::Presence::ref response = Swift::Presence::create();
response->setTo(m_component->getJID());
response->setFrom(user + "@" + m_component->getJID().toString());
response->setType(Swift::Presence::Available);
}
else {
LOG4CXX_INFO(logger, "Cannot reconnect user " << user << ","
"because he does not have legacy network configured. "
"Continuing in Installation mode for this user until "
"he configures the legacy network.");
m_installations[user] = new SlackInstallation(m_component, m_storageBackend, uinfo);
m_installations[user]->onInstallationDone.connect(boost::bind(&SlackUserManager::reconnectUser, this, _1));
}
}
void SlackUserManager::sendVCard(unsigned int id, Swift::VCard::ref vcard) { void SlackUserManager::sendVCard(unsigned int id, Swift::VCard::ref vcard) {
} }
@ -49,4 +75,13 @@ UserRegistration *SlackUserManager::getUserRegistration() {
return m_userRegistration; return m_userRegistration;
} }
std::string SlackUserManager::handleOAuth2Code(const std::string &code, const std::string &state) {
return static_cast<SlackUserRegistration *>(m_userRegistration)->handleOAuth2Code(code, state);
}
std::string SlackUserManager::getOAuth2URL(const std::vector<std::string> &args) {
return static_cast<SlackUserRegistration *>(m_userRegistration)->createOAuth2URL(args);
}
} }

View file

@ -39,6 +39,7 @@ class XMPPUserRegistration;
class GatewayResponder; class GatewayResponder;
class AdHocManager; class AdHocManager;
class SettingsAdHocCommandFactory; class SettingsAdHocCommandFactory;
class SlackInstallation;
class SlackUserManager : public UserManager { class SlackUserManager : public UserManager {
public: public:
@ -46,13 +47,21 @@ class SlackUserManager : public UserManager {
virtual ~SlackUserManager(); virtual ~SlackUserManager();
void reconnectUser(const std::string &user);
virtual void sendVCard(unsigned int id, Swift::VCard::ref vcard); virtual void sendVCard(unsigned int id, Swift::VCard::ref vcard);
UserRegistration *getUserRegistration(); UserRegistration *getUserRegistration();
std::string handleOAuth2Code(const std::string &code, const std::string &state);
std::string getOAuth2URL(const std::vector<std::string> &args);
private: private:
Component *m_component; Component *m_component;
UserRegistration *m_userRegistration; UserRegistration *m_userRegistration;
StorageBackend *m_storageBackend;
std::map<std::string, SlackInstallation *> m_installations;
}; };
} }

View file

@ -30,6 +30,11 @@
#include "transport/Logging.h" #include "transport/Logging.h"
#include "transport/Buddy.h" #include "transport/Buddy.h"
#include "transport/Config.h" #include "transport/Config.h"
#include "transport/OAuth2.h"
#include "transport/Util.h"
#include "transport/HTTPRequest.h"
#include "rapidjson/document.h"
#include <boost/shared_ptr.hpp> #include <boost/shared_ptr.hpp>
#include <boost/thread.hpp> #include <boost/thread.hpp>
@ -49,9 +54,97 @@ SlackUserRegistration::SlackUserRegistration(Component *component, UserManager *
m_config = m_component->getConfig(); m_config = m_component->getConfig();
m_storageBackend = storageBackend; m_storageBackend = storageBackend;
m_userManager = userManager; m_userManager = userManager;
} }
SlackUserRegistration::~SlackUserRegistration(){ SlackUserRegistration::~SlackUserRegistration(){
}
std::string SlackUserRegistration::createOAuth2URL(const std::vector<std::string> &args) {
std::string redirect_url = "http://slack.spectrum.im/auth/" + CONFIG_STRING(m_config, "service.jid");
OAuth2 *oauth2 = new OAuth2(CONFIG_STRING_DEFAULTED(m_config, "service.client_id",""),
CONFIG_STRING_DEFAULTED(m_config, "service.client_secret",""),
"https://slack.com/oauth/authorize",
"https://slack.com/api/oauth.access",
redirect_url,
"channels:read channels:write team:read im:read im:write chat:write:bot");
std::string url = oauth2->generateAuthURL();
m_auths[oauth2->getState()] = oauth2;
m_authsData[oauth2->getState()] = args;
return url;
}
std::string SlackUserRegistration::getTeamDomain(const std::string &token) {
std::string url = "https://slack.com/api/team.info?token=" + Util::urlencode(token);
rapidjson::Document resp;
HTTPRequest req(HTTPRequest::Get, url);
if (!req.execute(resp)) {
LOG4CXX_ERROR(logger, req.getError());
return "";
}
rapidjson::Value &team = resp["team"];
if (!team.IsObject()) {
LOG4CXX_ERROR(logger, "No 'team' object in the reply.");
return "";
}
rapidjson::Value &domain = team["domain"];
if (!domain.IsString()) {
LOG4CXX_ERROR(logger, "No 'domain' string in the reply.");
return "";
}
return domain.GetString();
}
std::string SlackUserRegistration::handleOAuth2Code(const std::string &code, const std::string &state) {
OAuth2 *oauth2 = NULL;
std::vector<std::string> data;
if (m_auths.find(state) != m_auths.end()) {
oauth2 = m_auths[state];
data = m_authsData[state];
}
else {
return "Received state code '" + state + "' not found in state codes list.";
}
std::string token;
std::string error = oauth2->requestToken(code, token);
if (!error.empty()) {
return error;
}
UserInfo user;
user.jid = getTeamDomain(token);
user.uin = "";
user.password = "";
user.language = "en";
user.encoding = token; // Use encoding as a token handler... it's BAD, but easy...
user.vip = 0;
user.id = 0;
registerUser(user);
m_storageBackend->getUser(user.jid, user);
std::string value = data[2];
int type = (int) TYPE_STRING;
m_storageBackend->getUserSetting(user.id, "bot_token", type, value);
LOG4CXX_INFO(logger, "Registered Slack user " << user.jid);
m_auths.erase(state);
delete oauth2;
m_authsData.erase(state);
m_component->getFrontend()->reconnectUser(user.jid);
return "";
} }
bool SlackUserRegistration::doUserRegistration(const UserInfo &row) { bool SlackUserRegistration::doUserRegistration(const UserInfo &row) {

View file

@ -31,6 +31,7 @@ class Component;
class StorageBackend; class StorageBackend;
class UserManager; class UserManager;
class Config; class Config;
class OAuth2;
class SlackUserRegistration : public UserRegistration { class SlackUserRegistration : public UserRegistration {
public: public:
@ -38,6 +39,12 @@ class SlackUserRegistration : public UserRegistration {
~SlackUserRegistration(); ~SlackUserRegistration();
std::string createOAuth2URL(const std::vector<std::string> &args);
std::string getTeamDomain(const std::string &token);
std::string handleOAuth2Code(const std::string &code, const std::string &state);
virtual bool doUserRegistration(const UserInfo &userInfo); virtual bool doUserRegistration(const UserInfo &userInfo);
virtual bool doUserUnregistration(const UserInfo &userInfo); virtual bool doUserUnregistration(const UserInfo &userInfo);
@ -47,6 +54,8 @@ class SlackUserRegistration : public UserRegistration {
StorageBackend *m_storageBackend; StorageBackend *m_storageBackend;
UserManager *m_userManager; UserManager *m_userManager;
Config *m_config; Config *m_config;
std::map<std::string, OAuth2 *> m_auths;
std::map<std::string, std::vector<std::string> > m_authsData;
}; };

View file

@ -313,6 +313,18 @@ void AdminInterface::handleQuery(Swift::Message::ref message) {
message->setBody("Bad argument count. See 'help'."); message->setBody("Bad argument count. See 'help'.");
} }
} }
else if (message->getBody().find("get_oauth2_url ") == 0) {
std::string body = message->getBody();
std::vector<std::string> args;
boost::split(args, body, boost::is_any_of(" "));
if (args.size() == 3) {
std::string url = m_component->getFrontend()->getOAuth2URL(args);
message->setBody(url);
}
else {
message->setBody("Bad argument count. See 'help'.");
}
}
else if (message->getBody().find("help") == 0) { else if (message->getBody().find("help") == 0) {
std::string help; std::string help;
help += "General:\n"; help += "General:\n";

View file

@ -103,6 +103,7 @@ bool Config::load(std::istream &ifs, boost::program_options::options_description
("service.jid_escaping", value<bool>()->default_value(true), "") ("service.jid_escaping", value<bool>()->default_value(true), "")
("service.vip_only", value<bool>()->default_value(false), "") ("service.vip_only", value<bool>()->default_value(false), "")
("service.vip_message", value<std::string>()->default_value(""), "") ("service.vip_message", value<std::string>()->default_value(""), "")
("service.reconnect_all_users", value<bool>()->default_value(false), "")
("vhosts.vhost", value<std::vector<std::string> >()->multitoken(), "") ("vhosts.vhost", value<std::vector<std::string> >()->multitoken(), "")
("identity.name", value<std::string>()->default_value("Spectrum 2 Transport"), "Name showed in service discovery.") ("identity.name", value<std::string>()->default_value("Spectrum 2 Transport"), "Name showed in service discovery.")
("identity.category", value<std::string>()->default_value("gateway"), "Disco#info identity category. 'gateway' by default.") ("identity.category", value<std::string>()->default_value("gateway"), "Disco#info identity category. 'gateway' by default.")

View file

@ -4,7 +4,20 @@ namespace Transport {
DEFINE_LOGGER(logger, "HTTPRequest") DEFINE_LOGGER(logger, "HTTPRequest")
HTTPRequest::HTTPRequest() : curlhandle(NULL) { HTTPRequest::HTTPRequest(ThreadPool *tp, Type type, const std::string &url, Callback callback) {
m_type = type;
m_url = url;
m_tp = tp;
m_callback = callback;
init();
}
HTTPRequest::HTTPRequest(Type type, const std::string &url) {
m_type = type;
m_url = url;
m_tp = NULL;
init(); init();
} }
@ -60,7 +73,7 @@ bool HTTPRequest::GET(std::string url, std::string &data) {
/* Set http request and url */ /* Set http request and url */
curl_easy_setopt(curlhandle, CURLOPT_HTTPGET, 1); curl_easy_setopt(curlhandle, CURLOPT_HTTPGET, 1);
curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, 1); curl_easy_setopt(curlhandle, CURLOPT_VERBOSE, 0);
curl_easy_setopt(curlhandle, CURLOPT_URL, url.c_str()); curl_easy_setopt(curlhandle, CURLOPT_URL, url.c_str());
/* Send http request and return status*/ /* Send http request and return status*/
@ -76,18 +89,50 @@ bool HTTPRequest::GET(std::string url, std::string &data) {
} }
bool HTTPRequest::GET(std::string url, rapidjson::Document &json) { bool HTTPRequest::GET(std::string url, rapidjson::Document &json) {
std::string data; if (!GET(url, m_data)) {
if (!GET(url, data)) {
return false; return false;
} }
if(json.Parse<0>(data.c_str()).HasParseError()) { if(json.Parse<0>(m_data.c_str()).HasParseError()) {
LOG4CXX_ERROR(logger, "Error while parsing JSON") LOG4CXX_ERROR(logger, "Error while parsing JSON");
LOG4CXX_ERROR(logger, data) LOG4CXX_ERROR(logger, m_data);
strcpy(curl_errorbuffer, "Error while parsing JSON");
return false; return false;
} }
return true; return true;
} }
void HTTPRequest::run() {
switch (m_type) {
case Get:
m_ok = GET(m_url, m_json);
break;
}
}
void HTTPRequest::finalize() {
m_callback(this, m_ok, m_json, m_data);
onRequestFinished();
}
bool HTTPRequest::execute() {
if (!m_tp) {
return false;
}
m_tp->runAsThread(this);
return true;
}
bool HTTPRequest::execute(rapidjson::Document &json) {
switch (m_type) {
case Get:
m_ok = GET(m_url, json);
break;
}
return m_ok;
}
} }

42
src/HTTPRequestQueue.cpp Normal file
View file

@ -0,0 +1,42 @@
#include "transport/HTTPRequestQueue.h"
#include "transport/HTTPRequest.h"
namespace Transport {
DEFINE_LOGGER(logger, "HTTPRequestQueue")
HTTPRequestQueue::HTTPRequestQueue(int delay) {
m_delay = delay;
m_processing = false;
}
HTTPRequestQueue::~HTTPRequestQueue() {
}
void HTTPRequestQueue::sendNextRequest() {
if (m_queue.empty()) {
m_processing = false;
return;
}
if (m_processing) {
return;
}
HTTPRequest *req = m_queue.front();
m_queue.pop();
req->onRequestFinished.connect(boost::bind(&HTTPRequestQueue::sendNextRequest, this));
req->execute();
}
void HTTPRequestQueue::queueRequest(HTTPRequest *req) {
m_queue.push(req);
if (!m_processing) {
sendNextRequest();
}
}
}

View file

@ -301,6 +301,7 @@ void MySQLBackend::disconnect() {
delete m_getBuddySetting; delete m_getBuddySetting;
delete m_setUserOnline; delete m_setUserOnline;
delete m_getOnlineUsers; delete m_getOnlineUsers;
delete m_getUsers;
mysql_close(&m_conn); mysql_close(&m_conn);
} }
@ -348,6 +349,7 @@ bool MySQLBackend::connect() {
m_setUserOnline = new Statement(&m_conn, "bi", "UPDATE " + m_prefix + "users SET online=?, last_login=NOW() WHERE id=?"); m_setUserOnline = new Statement(&m_conn, "bi", "UPDATE " + m_prefix + "users SET online=?, last_login=NOW() WHERE id=?");
m_getOnlineUsers = new Statement(&m_conn, "|s", "SELECT jid FROM " + m_prefix + "users WHERE online=1"); m_getOnlineUsers = new Statement(&m_conn, "|s", "SELECT jid FROM " + m_prefix + "users WHERE online=1");
m_getUsers = new Statement(&m_conn, "|s", "SELECT jid FROM " + m_prefix + "users");
return true; return true;
} }
@ -482,6 +484,20 @@ bool MySQLBackend::getOnlineUsers(std::vector<std::string> &users) {
return true; return true;
} }
bool MySQLBackend::getUsers(std::vector<std::string> &users) {
EXEC(m_getUsers, getUsers(users));
if (!exec_ok)
return false;
std::string jid;
while (m_getUsers->fetch() == 0) {
*m_getUsers >> jid;
users.push_back(jid);
}
return true;
}
long MySQLBackend::addBuddy(long userId, const BuddyInfo &buddyInfo) { long MySQLBackend::addBuddy(long userId, const BuddyInfo &buddyInfo) {
// "INSERT INTO " + m_prefix + "buddies (user_id, uin, subscription, groups, nickname, flags) VALUES (?, ?, ?, ?, ?, ?)" // "INSERT INTO " + m_prefix + "buddies (user_id, uin, subscription, groups, nickname, flags) VALUES (?, ?, ?, ?, ?, ?)"
std::string groups = StorageBackend::serializeGroups(buddyInfo.groups); std::string groups = StorageBackend::serializeGroups(buddyInfo.groups);

View file

@ -71,12 +71,7 @@ std::string OAuth2::generateAuthURL() {
return url; return url;
} }
std::string OAuth2::handleOAuth2Code(const std::string &code, const std::string &state) { std::string OAuth2::requestToken(const std::string &code, std::string &token) {
if (m_state != state) {
std::string error = "Received state code '" + state + "' does not sent state code '" + m_state + "'";
return error;
}
std::string url = m_tokenURL + "?"; std::string url = m_tokenURL + "?";
url += "client_id=" + Util::urlencode(m_clientId); url += "client_id=" + Util::urlencode(m_clientId);
url += "&client_secret=" + Util::urlencode(m_clientSecret); url += "&client_secret=" + Util::urlencode(m_clientSecret);
@ -86,12 +81,18 @@ std::string OAuth2::handleOAuth2Code(const std::string &code, const std::string
url += "&redirect_uri=" + Util::urlencode(m_redirectURL); url += "&redirect_uri=" + Util::urlencode(m_redirectURL);
} }
std::string data; rapidjson::Document resp;
HTTPRequest req; HTTPRequest req(HTTPRequest::Get, url);
req.GET(url, data); if (!req.execute(resp)) {
return req.getError();
}
LOG4CXX_INFO(logger, "handleOAuth2Code received token: " << data); rapidjson::Value& access_token = resp["access_token"];
if (!access_token.IsString()) {
return "No 'access_token' object in the reply.";
}
token = access_token.GetString();
return ""; return "";
} }

View file

@ -245,6 +245,23 @@ bool PQXXBackend::getOnlineUsers(std::vector<std::string> &users) {
return true; return true;
} }
bool PQXXBackend::getUsers(std::vector<std::string> &users) {
try {
pqxx::nontransaction txn(*m_conn);
pqxx::result r = txn.exec("SELECT jid FROM " + m_prefix + "users");
for (pqxx::result::const_iterator it = r.begin(); it != r.end(); it++) {
users.push_back((*it)[0].as<std::string>());
}
}
catch (std::exception& e) {
LOG4CXX_ERROR(logger, e.what());
return false;
}
return true;
}
long PQXXBackend::addBuddy(long userId, const BuddyInfo &buddyInfo) { long PQXXBackend::addBuddy(long userId, const BuddyInfo &buddyInfo) {
try { try {
pqxx::nontransaction txn(*m_conn); pqxx::nontransaction txn(*m_conn);

View file

@ -103,6 +103,7 @@ SQLite3Backend::~SQLite3Backend(){
FINALIZE_STMT(m_getBuddySetting); FINALIZE_STMT(m_getBuddySetting);
FINALIZE_STMT(m_setUserOnline); FINALIZE_STMT(m_setUserOnline);
FINALIZE_STMT(m_getOnlineUsers); FINALIZE_STMT(m_getOnlineUsers);
FINALIZE_STMT(m_getUsers);
sqlite3_close(m_db); sqlite3_close(m_db);
} }
} }
@ -143,6 +144,7 @@ bool SQLite3Backend::connect() {
PREP_STMT(m_setUserOnline, "UPDATE " + m_prefix + "users SET online=?, last_login=DATETIME('NOW') WHERE id=?"); PREP_STMT(m_setUserOnline, "UPDATE " + m_prefix + "users SET online=?, last_login=DATETIME('NOW') WHERE id=?");
PREP_STMT(m_getOnlineUsers, "SELECT jid FROM " + m_prefix + "users WHERE online=1"); PREP_STMT(m_getOnlineUsers, "SELECT jid FROM " + m_prefix + "users WHERE online=1");
PREP_STMT(m_getUsers, "SELECT jid FROM " + m_prefix + "users");
return true; return true;
} }
@ -283,6 +285,23 @@ bool SQLite3Backend::getOnlineUsers(std::vector<std::string> &users) {
return true; return true;
} }
bool SQLite3Backend::getUsers(std::vector<std::string> &users) {
sqlite3_reset(m_getUsers);
int ret;
while((ret = sqlite3_step(m_getUsers)) == SQLITE_ROW) {
std::string jid = (const char *) sqlite3_column_text(m_getUsers, 0);
users.push_back(jid);
}
if (ret != SQLITE_DONE) {
LOG4CXX_ERROR(logger, "getUsers query"<< (sqlite3_errmsg(m_db) == NULL ? "" : sqlite3_errmsg(m_db)));
return false;
}
return true;
}
long SQLite3Backend::addBuddy(long userId, const BuddyInfo &buddyInfo) { long SQLite3Backend::addBuddy(long userId, const BuddyInfo &buddyInfo) {
// "INSERT INTO " + m_prefix + "buddies (user_id, uin, subscription, groups, nickname, flags) VALUES (?, ?, ?, ?, ?, ?)" // "INSERT INTO " + m_prefix + "buddies (user_id, uin, subscription, groups, nickname, flags) VALUES (?, ?, ?, ?, ?, ?)"
std::string groups = StorageBackend::serializeGroups(buddyInfo.groups); std::string groups = StorageBackend::serializeGroups(buddyInfo.groups);

View file

@ -1,6 +1,8 @@
#include "transport/ThreadPool.h" #include "transport/ThreadPool.h"
#include "transport/Logging.h" #include "transport/Logging.h"
namespace Transport {
DEFINE_LOGGER(logger, "ThreadPool") DEFINE_LOGGER(logger, "ThreadPool")
boost::signals2::signal< void (Thread*, int) > onWorkCompleted; boost::signals2::signal< void (Thread*, int) > onWorkCompleted;
@ -123,3 +125,5 @@ void ThreadPool::runAsThread(Thread *t)
requestQueue.push(t); requestQueue.push(t);
} }
} }
}

View file

@ -23,6 +23,7 @@
#include "transport/Transport.h" #include "transport/Transport.h"
#include "transport/Logging.h" #include "transport/Logging.h"
#include "transport/Frontend.h" #include "transport/Frontend.h"
#include "transport/Config.h"
#include <iostream> #include <iostream>
#include <boost/bind.hpp> #include <boost/bind.hpp>
@ -72,7 +73,12 @@ void UsersReconnecter::handleConnected() {
LOG4CXX_INFO(logger, "Starting UserReconnecter."); LOG4CXX_INFO(logger, "Starting UserReconnecter.");
m_started = true; m_started = true;
if (CONFIG_BOOL_DEFAULTED(m_component->getConfig(), "service.reconnect_all_users", false)) {
m_storageBackend->getUsers(m_users);
}
else {
m_storageBackend->getOnlineUsers(m_users); m_storageBackend->getOnlineUsers(m_users);
}
reconnectNextUser(); reconnectNextUser();
} }