Browse Source

Libtransport, Web Interface: Rewrite the AdminInterface to support pluggable commands - use this API in Web Interface instead of coding one page per command.

master
Jan Kaluza 7 years ago
parent
commit
84ea5f3249
  1. 8
      include/transport/AdminInterface.h
  2. 129
      include/transport/AdminInterfaceCommand.h
  3. 1
      include/transport/Frontend.h
  4. 13
      include/transport/Transport.h
  5. 1360
      libtransport/AdminInterface.cpp
  6. 120
      libtransport/AdminInterfaceCommand.cpp
  7. 1
      libtransport/Transport.cpp
  8. 6
      spectrum/src/frontends/slack/SlackFrontend.cpp
  9. 1
      spectrum/src/frontends/slack/SlackFrontend.h
  10. 276
      spectrum/src/frontends/slack/SlackUserManager.cpp
  11. 3
      spectrum/src/frontends/slack/SlackUserManager.h
  12. 1
      spectrum/src/main.cpp
  13. 430
      spectrum_manager/src/APIServer.cpp
  14. 9
      spectrum_manager/src/APIServer.h
  15. 5
      spectrum_manager/src/html/bootstrap.min.css
  16. 3
      spectrum_manager/src/html/header.shtml
  17. 2
      spectrum_manager/src/html/instances/instance.shtml
  18. 37
      spectrum_manager/src/html/instances/join_room.shtml
  19. 32
      spectrum_manager/src/html/instances/register.shtml
  20. 313
      spectrum_manager/src/html/js/app.js
  21. 6
      spectrum_manager/src/html/js/bootbox.min.js
  22. 7
      spectrum_manager/src/html/js/bootstrap.min.js
  23. 1
      spectrum_manager/src/html/js/config.js
  24. 7184
      spectrum_manager/src/html/js/jquery.js
  25. 11
      spectrum_manager/src/html/style.css
  26. 1
      spectrum_manager/src/server.cpp
  27. 132
      tests/libtransport/AdminInterface.cpp
  28. 2
      tests/libtransport/BasicSlackTest.cpp
  29. 2
      tests/libtransport/BasicSlackTest.h
  30. 2
      tests/libtransport/basictest.cpp

8
include/transport/AdminInterface.h

@ -32,6 +32,7 @@ class StorageBackend; @@ -32,6 +32,7 @@ class StorageBackend;
class UserManager;
class NetworkPluginServer;
class UserRegistration;
class AdminInterfaceCommand;
class AdminInterface {
public:
@ -41,15 +42,18 @@ class AdminInterface { @@ -41,15 +42,18 @@ class AdminInterface {
void handleQuery(Swift::Message::ref message);
private:
void addCommand(AdminInterfaceCommand *command);
void handleMessageReceived(Swift::Message::ref message);
private:
Component *m_component;
StorageBackend *m_storageBackend;
UserManager *m_userManager;
NetworkPluginServer *m_server;
UserRegistration *m_userRegistration;
time_t m_start;
std::map<std::string, AdminInterfaceCommand *> m_commands;
};
}

129
include/transport/AdminInterfaceCommand.h

@ -0,0 +1,129 @@ @@ -0,0 +1,129 @@
/**
* libtransport -- C++ library for easy XMPP Transports development
*
* Copyright (C) 2016, 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 <string>
#include <map>
#include "Swiften/Elements/Message.h"
#include "transport/StorageBackend.h"
namespace Transport {
class User;
class AdminInterfaceCommand {
public:
typedef enum {
GlobalContext,
UserContext
} Context;
typedef enum {
None = 0,
Get = 1,
Set = 2,
Execute = 4
} Actions;
typedef enum {
AdminMode,
UserMode
} AccessMode;
typedef enum {
General,
Users,
Messages,
Frontend,
Backends,
Memory
} Category;
class Arg {
public:
Arg(const std::string &_name, const std::string &_label, const std::string &_example) :
name(_name), label(_label), example(_example) {}
~Arg() {}
std::string name;
std::string label;
std::string example;
};
AdminInterfaceCommand(const std::string &name, Category category, Context context, AccessMode accessMode, Actions actions);
virtual ~AdminInterfaceCommand() { }
void setDescription(const std::string &desc) {
m_desc = desc;
}
const std::string &getDescription() {
return m_desc;
}
const std::string &getName() {
return m_name;
}
Actions getActions() {
return m_actions;
}
Category getCategory() {
return m_category;
}
const std::string getCategoryName(Category category);
Context getContext() {
return m_context;
}
AccessMode getAccessMode() {
return m_accessMode;
}
void addArg(const std::string &name, const std::string &label, const std::string &example = "") {
Arg arg(name, label, example);
m_args.push_back(arg);
}
const std::list<Arg> &getArgs() {
return m_args;
}
virtual std::string handleSetRequest(UserInfo &uinfo, User *user, std::vector<std::string> &args);
virtual std::string handleGetRequest(UserInfo &uinfo, User *user, std::vector<std::string> &args);
virtual std::string handleExecuteRequest(UserInfo &uinfo, User *user, std::vector<std::string> &args);
private:
std::string m_name;
Category m_category;
Context m_context;
AccessMode m_accessMode;
Actions m_actions;
std::string m_desc;
std::list<Arg> m_args;
};
}

1
include/transport/Frontend.h

@ -94,7 +94,6 @@ class Frontend { @@ -94,7 +94,6 @@ class 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 ""; }
virtual std::string getRegistrationFields() { return "Jabber ID\n3rd-party network username\n3rd-party network password"; }
virtual bool handleAdminMessage(Swift::Message::ref /*message*/) { return false; }
virtual bool isRawXMLEnabled() { return false; }

13
include/transport/Transport.h

@ -38,6 +38,7 @@ namespace Transport { @@ -38,6 +38,7 @@ namespace Transport {
class Factory;
class Config;
class UserManager;
class AdminInterface;
class Component {
public:
@ -108,6 +109,8 @@ namespace Transport { @@ -108,6 +109,8 @@ namespace Transport {
boost::signal<void (Swift::Presence::ref presence)> onUserPresenceReceived;
boost::signal<void (boost::shared_ptr<Swift::IQ>)> onRawIQReceived;
boost::signal<void ()> onAdminInterfaceSet;
void handlePresence(Swift::Presence::ref presence);
void handleConnected();
@ -121,6 +124,15 @@ namespace Transport { @@ -121,6 +124,15 @@ namespace Transport {
PresenceOracle *getPresenceOracle();
void setAdminInterface(AdminInterface *adminInterface) {
m_adminInterface = adminInterface;
onAdminInterfaceSet();
}
AdminInterface *getAdminInterface() {
return m_adminInterface;
}
private:
void handleDiscoInfoResponse(boost::shared_ptr<Swift::DiscoInfo> info, Swift::ErrorPayload::ref error, const Swift::JID& jid);
void handleCapsChanged(const Swift::JID& jid);
@ -139,6 +151,7 @@ namespace Transport { @@ -139,6 +151,7 @@ namespace Transport {
Factory *m_factory;
Swift::EventLoop *m_loop;
Frontend *m_frontend;
AdminInterface *m_adminInterface;
friend class User;
friend class UserRegistration;

1360
libtransport/AdminInterface.cpp

File diff suppressed because it is too large Load Diff

120
libtransport/AdminInterfaceCommand.cpp

@ -0,0 +1,120 @@ @@ -0,0 +1,120 @@
/**
* libtransport -- C++ library for easy XMPP Transports development
*
* Copyright (C) 2011, 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
*/
#include "transport/AdminInterfaceCommand.h"
#include "transport/User.h"
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <Swiften/Version.h>
#define HAVE_SWIFTEN_3 (SWIFTEN_VERSION >= 0x030000)
namespace Transport {
AdminInterfaceCommand::AdminInterfaceCommand(const std::string &name, Category category, Context context, AccessMode accessMode, Actions actions) {
m_name = name;
m_category = category;
m_context = context;
m_accessMode = accessMode;
m_actions = actions;
}
const std::string AdminInterfaceCommand::getCategoryName(Category category) {
switch (category) {
case AdminInterfaceCommand::General:
return "General";
case AdminInterfaceCommand::Users:
return "Users";
case AdminInterfaceCommand::Messages:
return "Messages";
case AdminInterfaceCommand::Frontend:
return "Frontend";
case AdminInterfaceCommand::Backends:
return "Backends";
case AdminInterfaceCommand::Memory:
return "Memory";
default:
return "Unknown";
}
}
std::string AdminInterfaceCommand::handleSetRequest(UserInfo &uinfo, User *user, std::vector<std::string> &args) {
if ((m_actions & Set) == 0) {
return "Error: This variable cannot be set.";
}
if (user && (m_accessMode & AdminMode) != 0) {
return "Error: You do not have rights to set this variable.";
}
if ((!user && uinfo.id == -1) && (m_context & UserContext)) {
return "Error: This variable can be set only in user context.";
}
if (args.empty()) {
return "Error: Value is missing.";
}
return "";
}
std::string AdminInterfaceCommand::handleGetRequest(UserInfo &uinfo, User *user, std::vector<std::string> &args) {
if ((m_actions & Get) == 0) {
return "Error: This variable cannot be get.";
}
if (user && (m_accessMode & AdminMode) != 0) {
return "Error: You do not have rights to get this variable.";
}
if ((!user && uinfo.id == -1) && (m_context & UserContext)) {
return "Error: This variable can be get only in user context.";
}
return "";
}
std::string AdminInterfaceCommand::handleExecuteRequest(UserInfo &uinfo, User *user, std::vector<std::string> &args) {
if ((m_actions & Execute) == 0) {
return "Error: This is not a command, but a variable.";
}
if (user && (m_accessMode & AdminMode) != 0) {
return "Error: You do not have rights to execute this command.";
}
if ((!user && uinfo.id == -1) && (m_context & UserContext)) {
return "Error: This command can be executed only in user context.";
}
if (m_args.size() > args.size()) {
return "Error: Argument is missing.";
}
if (m_args.size() < args.size()) {
return "Error: Too many arguments.";
}
return "";
}
}

1
libtransport/Transport.cpp

@ -38,6 +38,7 @@ DEFINE_LOGGER(logger_xml, "Component.RAW"); @@ -38,6 +38,7 @@ DEFINE_LOGGER(logger_xml, "Component.RAW");
Component::Component(Frontend *frontend, Swift::EventLoop *loop, Swift::NetworkFactories *factories, Config *config, Factory *factory, Transport::UserRegistry *userRegistry) {
m_frontend = frontend;
m_userRegistry = NULL;
m_adminInterface = NULL;
m_reconnectCount = 0;
m_config = config;
m_factory = factory;

6
spectrum/src/frontends/slack/SlackFrontend.cpp

@ -127,17 +127,13 @@ std::string SlackFrontend::getOAuth2URL(const std::vector<std::string> &args) { @@ -127,17 +127,13 @@ std::string SlackFrontend::getOAuth2URL(const std::vector<std::string> &args) {
std::string SlackFrontend::getRegistrationFields() {
std::string fields = "Main Slack channel";
if (CONFIG_BOOL(m_config, "registration.needRegistration")) {
if (CONFIG_BOOL_DEFAULTED(m_config, "registration.needRegistration", true)) {
fields += "\n" + CONFIG_STRING(m_config, "registration.username_label") + "\n";
fields += CONFIG_STRING(m_config, "registration.password_label");
}
return fields;
}
bool SlackFrontend::handleAdminMessage(Swift::Message::ref message) {
return static_cast<SlackUserManager *>(m_userManager)->handleAdminMessage(message);
}
void SlackFrontend::disconnectFromServer() {
}

1
spectrum/src/frontends/slack/SlackFrontend.h

@ -68,7 +68,6 @@ namespace Transport { @@ -68,7 +68,6 @@ namespace Transport {
virtual std::string setOAuth2Code(const std::string &code, const std::string &state);
virtual std::string getOAuth2URL(const std::vector<std::string> &args);
virtual std::string getRegistrationFields();
virtual bool handleAdminMessage(Swift::Message::ref message);
void handleMessage(boost::shared_ptr<Swift::Message> message);

276
spectrum/src/frontends/slack/SlackUserManager.cpp

@ -29,6 +29,8 @@ @@ -29,6 +29,8 @@
#include "transport/StorageBackend.h"
#include "transport/Logging.h"
#include "transport/Config.h"
#include "transport/AdminInterface.h"
#include "transport/AdminInterfaceCommand.h"
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
@ -40,18 +42,176 @@ namespace Transport { @@ -40,18 +42,176 @@ namespace Transport {
DEFINE_LOGGER(logger, "SlackUserManager");
class ListRoomsCommand : public AdminInterfaceCommand {
public:
ListRoomsCommand(StorageBackend *storageBackend) : AdminInterfaceCommand("list_rooms",
AdminInterfaceCommand::Frontend,
AdminInterfaceCommand::UserContext,
AdminInterfaceCommand::UserMode,
AdminInterfaceCommand::Execute) {
m_storageBackend = storageBackend;
setDescription("List connected rooms");
}
virtual std::string handleExecuteRequest(UserInfo &uinfo, User *user, std::vector<std::string> &args) {
std::string ret = AdminInterfaceCommand::handleExecuteRequest(uinfo, user, args);
if (!ret.empty()) {
return ret;
}
if (uinfo.id == -1) {
return "Error: Unknown user";
}
std::string rooms = "";
int type = (int) TYPE_STRING;
m_storageBackend->getUserSetting(uinfo.id, "rooms", type, rooms);
return rooms;
}
private:
StorageBackend *m_storageBackend;
};
class JoinRoomCommand : public AdminInterfaceCommand {
public:
JoinRoomCommand(StorageBackend *storageBackend, Config *cfg) : AdminInterfaceCommand("join_room",
AdminInterfaceCommand::Frontend,
AdminInterfaceCommand::UserContext,
AdminInterfaceCommand::UserMode,
AdminInterfaceCommand::Execute) {
m_storageBackend = storageBackend;
setDescription("Join the room");
std::string legacyRoomLabel = CONFIG_STRING_DEFAULTED(cfg, "service.join_room_room_label", "3rd-party room name");
if (legacyRoomLabel[0] == '%') {
legacyRoomLabel[0] = '#';
}
std::string legacyRoomExample = CONFIG_STRING_DEFAULTED(cfg, "service.join_room_room_example", "3rd-party room name");
if (legacyRoomExample[0] == '%') {
legacyRoomExample[0] = '#';
}
addArg("nickname",
CONFIG_STRING_DEFAULTED(cfg, "service.join_room_nickname_label", "Nickname in 3rd-party room"),
CONFIG_STRING_DEFAULTED(cfg, "service.join_room_nickname_example", "BotNickname"));
addArg("legacy_room", legacyRoomLabel, legacyRoomExample);
addArg("legacy_server",
CONFIG_STRING_DEFAULTED(cfg, "service.join_room_server_label", "3rd-party server"),
CONFIG_STRING_DEFAULTED(cfg, "service.join_room_server_example", "3rd.party.server.org"));
addArg("slack_channel", "Slack Chanel", "mychannel");
}
virtual std::string handleExecuteRequest(UserInfo &uinfo, User *u, std::vector<std::string> &args) {
std::string ret = AdminInterfaceCommand::handleExecuteRequest(uinfo, u, args);
if (!ret.empty()) {
return ret;
}
if (uinfo.id == -1) {
return "Error: Unknown user";
}
std::string rooms = "";
int type = (int) TYPE_STRING;
m_storageBackend->getUserSetting(uinfo.id, "rooms", type, rooms);
// 'unknown' is here to stay compatible in args.size() with older version.
rooms += "connected room " + args[0] + " " + args[1] + " " + args[2] + " " + args[3] + "\n";
m_storageBackend->updateUserSetting(uinfo.id, "rooms", rooms);
SlackUser *user = static_cast<SlackUser *>(u);
if (user) {
user->getSession()->handleJoinMessage("", args, true);
}
return "Joined the room";
}
private:
StorageBackend *m_storageBackend;
};
class LeaveRoomCommand : public AdminInterfaceCommand {
public:
LeaveRoomCommand(StorageBackend *storageBackend) : AdminInterfaceCommand("leave_room",
AdminInterfaceCommand::Frontend,
AdminInterfaceCommand::UserContext,
AdminInterfaceCommand::UserMode,
AdminInterfaceCommand::Execute) {
m_storageBackend = storageBackend;
setDescription("Leave the room");
addArg("slack_channel", "Slack Chanel", "mychannel");
}
virtual std::string handleExecuteRequest(UserInfo &uinfo, User *u, std::vector<std::string> &args) {
std::string ret = AdminInterfaceCommand::handleExecuteRequest(uinfo, u, args);
if (!ret.empty()) {
return ret;
}
if (uinfo.id == -1) {
return "Error: Unknown user";
}
std::string rooms = "";
int type = (int) TYPE_STRING;
m_storageBackend->getUserSetting(uinfo.id, "rooms", type, rooms);
std::vector<std::string> commands;
boost::split(commands, rooms, boost::is_any_of("\n"));
rooms = "";
BOOST_FOREACH(const std::string &command, commands) {
if (command.size() > 5) {
std::vector<std::string> args2;
boost::split(args2, command, boost::is_any_of(" "));
if (args2.size() == 6) {
if (args[0] != args2[5]) {
rooms += command + "\n";
}
}
}
}
m_storageBackend->updateUserSetting(uinfo.id, "rooms", rooms);
SlackUser *user = static_cast<SlackUser *>(u);
if (user) {
user->getSession()->leaveRoom(args[0]);
}
return "Left the room";
}
private:
StorageBackend *m_storageBackend;
};
SlackUserManager::SlackUserManager(Component *component, UserRegistry *userRegistry, StorageBackend *storageBackend) : UserManager(component, userRegistry, storageBackend) {
m_component = component;
m_storageBackend = storageBackend;
m_userRegistration = new SlackUserRegistration(component, this, storageBackend);
onUserCreated.connect(boost::bind(&SlackUserManager::handleUserCreated, this, _1));
m_component->onAdminInterfaceSet.connect(boost::bind(&SlackUserManager::handleAdminInterfaceSet, this));
}
SlackUserManager::~SlackUserManager() {
delete m_userRegistration;
}
void SlackUserManager::handleAdminInterfaceSet() {
AdminInterface *adminInterface = m_component->getAdminInterface();
if (adminInterface) {
adminInterface->addCommand(new ListRoomsCommand(m_storageBackend));
adminInterface->addCommand(new JoinRoomCommand(m_storageBackend, m_component->getConfig()));
adminInterface->addCommand(new LeaveRoomCommand(m_storageBackend));
}
}
void SlackUserManager::reconnectUser(const std::string &user) {
UserInfo uinfo;
if (!m_storageBackend->getUser(user, uinfo)) {
@ -109,121 +269,5 @@ void SlackUserManager::handleUserCreated(User *user) { @@ -109,121 +269,5 @@ void SlackUserManager::handleUserCreated(User *user) {
static_cast<SlackUser *>(user)->getSession()->handleConnected();
}
bool SlackUserManager::handleAdminMessage(Swift::Message::ref message) {
#if HAVE_SWIFTEN_3
std::string body = message->getBody().get_value_or("");
#else
std::string body = message->getBody();
#endif
if (body.find("list_rooms") == 0) {
std::vector<std::string> args;
boost::split(args, body, boost::is_any_of(" "));
if (args.size() == 2) {
UserInfo uinfo;
if (!m_storageBackend->getUser(args[1], uinfo)) {
message->setBody("Error: Unknown user");
return true;
}
std::string rooms = "";
int type = (int) TYPE_STRING;
m_storageBackend->getUserSetting(uinfo.id, "rooms", type, rooms);
message->setBody(rooms);
return true;
}
}
else if (body.find("join_room_fields") == 0) {
std::string ret;
Config *cfg = m_component->getConfig();
ret += CONFIG_STRING_DEFAULTED(cfg, "service.join_room_nickname_label", "Nickname in 3rd-party room") + "\n";
std::string room_name = CONFIG_STRING_DEFAULTED(cfg, "service.join_room_room_label", "3rd-party room name");
if (room_name[0] == '%') {
room_name[0] = '#';
}
ret += room_name + "\n";
ret += CONFIG_STRING_DEFAULTED(cfg, "service.join_room_server_label", "3rd-party server") + "\n";
ret += "Slack Channel\n";
ret += CONFIG_STRING_DEFAULTED(cfg, "service.join_room_nickname_example", "BotNickname") + "\n";
room_name = CONFIG_STRING_DEFAULTED(cfg, "service.join_room_room_example", "3rd-party room name");
if (room_name[0] == '%') {
room_name[0] = '#';
}
ret += room_name + "\n";
ret += CONFIG_STRING_DEFAULTED(cfg, "service.join_room_server_example", "3rd.party.server.org") + "\n";
ret += "mychannel";
message->setBody(ret);
return true;
}
else if (body.find("join_room ") == 0) {
std::vector<std::string> args;
boost::split(args, body, boost::is_any_of(" "));
if (args.size() == 6) {
UserInfo uinfo;
if (!m_storageBackend->getUser(args[1], uinfo)) {
message->setBody("Error: Unknown user");
return true;
}
std::string rooms = "";
int type = (int) TYPE_STRING;
m_storageBackend->getUserSetting(uinfo.id, "rooms", type, rooms);
rooms += body + "\n";
m_storageBackend->updateUserSetting(uinfo.id, "rooms", rooms);
SlackUser *user = static_cast<SlackUser *>(getUser(args[1]));
if (user) {
user->getSession()->handleJoinMessage("", args, true);
}
message->setBody("Joined the room");
return true;
}
}
else if (body.find("leave_room ") == 0) {
std::vector<std::string> args;
boost::split(args, body, boost::is_any_of(" "));
if (args.size() == 3) {
UserInfo uinfo;
if (!m_storageBackend->getUser(args[1], uinfo)) {
message->setBody("Error: Unknown user");
return true;
}
std::string rooms = "";
int type = (int) TYPE_STRING;
m_storageBackend->getUserSetting(uinfo.id, "rooms", type, rooms);
std::vector<std::string> commands;
boost::split(commands, rooms, boost::is_any_of("\n"));
rooms = "";
BOOST_FOREACH(const std::string &command, commands) {
if (command.size() > 5) {
std::vector<std::string> args2;
boost::split(args2, command, boost::is_any_of(" "));
if (args2.size() == 6) {
if (args[2] != args2[5]) {
rooms += command + "\n";
}
}
}
}
m_storageBackend->updateUserSetting(uinfo.id, "rooms", rooms);
SlackUser *user = static_cast<SlackUser *>(getUser(args[1]));
if (user) {
user->getSession()->leaveRoom(args[2]);
}
message->setBody("Left the room");
return true;
}
}
return false;
}
}

3
spectrum/src/frontends/slack/SlackUserManager.h

@ -62,10 +62,9 @@ class SlackUserManager : public UserManager { @@ -62,10 +62,9 @@ class SlackUserManager : public UserManager {
SlackSession *moveTempSession(const std::string &user);
void moveTempSession(const std::string &user, SlackSession *session);
bool handleAdminMessage(Swift::Message::ref message);
private:
void handleUserCreated(User *user);
void handleAdminInterfaceSet();
private:
Component *m_component;

1
spectrum/src/main.cpp

@ -275,6 +275,7 @@ int mainloop() { @@ -275,6 +275,7 @@ int mainloop() {
AdminInterface adminInterface(&transport, userManager, &plugin, storageBackend, userRegistration);
plugin.setAdminInterface(&adminInterface);
transport.setAdminInterface(&adminInterface);
eventLoop_ = &eventLoop;

430
spectrum_manager/src/APIServer.cpp

@ -16,6 +16,10 @@ @@ -16,6 +16,10 @@
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#include <boost/tokenizer.hpp>
using boost::tokenizer;
using boost::escaped_list_separator;
#define ALLOW_ONLY_ADMIN() if (!session->admin) { \
send_ack(conn, true, "Only administrators can do this API call."); \
return; \
@ -138,58 +142,6 @@ void APIServer::serve_instances(Server *server, Server::session *session, struct @@ -138,58 +142,6 @@ void APIServer::serve_instances(Server *server, Server::session *session, struct
send_json(conn, json);
}
void APIServer::serve_instances_list_rooms(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
std::string uri(hm->uri.p, hm->uri.len);
std::string instance = uri.substr(uri.rfind("/") + 1);
UserInfo info;
m_storage->getUser(session->user, info);
std::string username = "";
int type = (int) TYPE_STRING;
m_storage->getUserSetting(info.id, instance, type, username);
if (username.empty()) {
send_ack(conn, true, "You are not registered to this Spectrum 2 instance.");
return;
}
std::string response = server->send_command(instance, "list_rooms " + username);
std::vector<std::string> commands;
boost::split(commands, response, boost::is_any_of("\n"));
Document json;
json.SetObject();
json.AddMember("error", 0, json.GetAllocator());
json.AddMember("name_label", "Nickname in 3rd-party room", json.GetAllocator());
json.AddMember("legacy_room_label", "3rd-party room name", json.GetAllocator());
json.AddMember("legacy_server_label", "3rd-party server", json.GetAllocator());
json.AddMember("frontend_room_label", "Slack channel", json.GetAllocator());
std::vector<std::vector<std::string> > tmp;
Value rooms(kArrayType);
BOOST_FOREACH(const std::string &command, commands) {
if (command.size() > 5) {
std::vector<std::string> args2;
boost::split(args2, command, boost::is_any_of(" "));
if (args2.size() == 6) {
tmp.push_back(args2);
Value room;
room.SetObject();
room.AddMember("name", tmp.back()[2].c_str(), json.GetAllocator());
room.AddMember("legacy_room", tmp.back()[3].c_str(), json.GetAllocator());
room.AddMember("legacy_server", tmp.back()[4].c_str(), json.GetAllocator());
room.AddMember("frontend_room", tmp.back()[5].c_str(), json.GetAllocator());
rooms.PushBack(room, json.GetAllocator());
}
}
}
json.AddMember("rooms", rooms, json.GetAllocator());
send_json(conn, json);
}
void APIServer::serve_instances_start(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
ALLOW_ONLY_ADMIN();
@ -225,6 +177,10 @@ void APIServer::serve_instances_unregister(Server *server, Server::session *sess @@ -225,6 +177,10 @@ void APIServer::serve_instances_unregister(Server *server, Server::session *sess
int type = (int) TYPE_STRING;
m_storage->getUserSetting(info.id, instance, type, username);
if (username.empty() && session->admin) {
username = get_http_var(hm, "command_arg0");
}
if (!username.empty()) {
std::string response = server->send_command(instance, "unregister " + username);
if (!response.empty()) {
@ -275,7 +231,7 @@ void APIServer::serve_instances_register(Server *server, Server::session *sessio @@ -275,7 +231,7 @@ void APIServer::serve_instances_register(Server *server, Server::session *sessio
else {
// Check if the frontend wants to use OAuth2 (Slack for example).
std::string response = server->send_command(instance, "get_oauth2_url " + jid + " " + uin + " " + password);
if (!response.empty()) {
if (!response.empty() && response.find("Error:") != 0) {
Document json;
json.SetObject();
json.AddMember("error", false, json.GetAllocator());
@ -288,6 +244,7 @@ void APIServer::serve_instances_register(Server *server, Server::session *sessio @@ -288,6 +244,7 @@ void APIServer::serve_instances_register(Server *server, Server::session *sessio
std::string value = jid;
int type = (int) TYPE_STRING;
m_storage->updateUserSetting(info.id, instance, value);
send_ack(conn, false, response);
}
else {
send_ack(conn, true, response);
@ -297,7 +254,32 @@ void APIServer::serve_instances_register(Server *server, Server::session *sessio @@ -297,7 +254,32 @@ void APIServer::serve_instances_register(Server *server, Server::session *sessio
}
}
void APIServer::serve_instances_join_room(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
// void APIServer::serve_instances_register_form(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
// std::string uri(hm->uri.p, hm->uri.len);
// std::string instance = uri.substr(uri.rfind("/") + 1);
//
// std::string response = server->send_command(instance, "registration_fields");
// std::vector<std::string> fields;
// boost::split(fields, response, boost::is_any_of("\n"));
//
// if (fields.empty()) {
// fields.push_back("Jabber ID");
// fields.push_back("3rd-party network username");
// fields.push_back("3rd-party network password");
// }
//
// Document json;
// json.SetObject();
// json.AddMember("error", 0, json.GetAllocator());
// json.AddMember("username_label", fields[0].c_str(), json.GetAllocator());
// json.AddMember("legacy_username_label", fields.size() >= 2 ? fields[1].c_str() : "", json.GetAllocator());
// json.AddMember("password_label", fields.size() >= 3 ? fields[2].c_str() : "", json.GetAllocator());
// send_json(conn, json);
// }
void APIServer::serve_instances_commands(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
std::string uri(hm->uri.p, hm->uri.len);
std::string instance = uri.substr(uri.rfind("/") + 1);
@ -308,29 +290,61 @@ void APIServer::serve_instances_join_room(Server *server, Server::session *sessi @@ -308,29 +290,61 @@ void APIServer::serve_instances_join_room(Server *server, Server::session *sessi
int type = (int) TYPE_STRING;
m_storage->getUserSetting(info.id, instance, type, username);
if (username.empty()) {
send_ack(conn, true, "You are not registered to this Spectrum 2 instance.");
return;
}
std::string response = server->send_command(instance, "commands");
std::vector<std::string> commands;
boost::split(commands, response, boost::is_any_of("\n"));
std::string name = get_http_var(hm, "name");
boost::replace_all(name, " ", "_");
std::string legacy_room = get_http_var(hm, "legacy_room");
std::string legacy_server = get_http_var(hm, "legacy_server");
std::string frontend_room = get_http_var(hm, "frontend_room");
Document json;
json.SetObject();
json.AddMember("error", 0, json.GetAllocator());
std::string response = server->send_command(instance, "join_room " +
username + " " + name + " " + legacy_room + " " + legacy_server + " " + frontend_room);
std::vector<std::vector<std::string> > tmp;
Value cmds(kArrayType);
BOOST_FOREACH(const std::string &command, commands) {
escaped_list_separator<char> els('\\', ' ', '\"');
tokenizer<escaped_list_separator<char> > tok(command, els);
if (response.find("Joined the room") == std::string::npos) {
send_ack(conn, true, response);
}
else {
send_ack(conn, false, response);
std::vector<std::string> tokens;
for(tokenizer<escaped_list_separator<char> >::iterator beg=tok.begin(); beg!=tok.end(); ++beg) {
tokens.push_back(*beg);
}
if (tokens.size() != 9) {
continue;
}
if (!session->admin && tokens[6] == "Admin") {
continue;
}
// Skip command which needs registered users.
if (!session->admin && username.empty() && tokens[8] == "User") {
continue;
}
// Skip 'register' command when user is registered.
if (!session->admin && !username.empty() && tokens[0] == "register") {
continue;
}
tmp.push_back(tokens);
Value cmd;
cmd.SetObject();
cmd.AddMember("name", tokens[0].c_str(), json.GetAllocator());
cmd.AddMember("desc", tokens[2].c_str(), json.GetAllocator());
cmd.AddMember("category", tokens[4].c_str(), json.GetAllocator());
cmd.AddMember("context", tokens[8].c_str(), json.GetAllocator());
cmds.PushBack(cmd, json.GetAllocator());
}
json.AddMember("commands", cmds, json.GetAllocator());
send_json(conn, json);
}
void APIServer::serve_instances_leave_room(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
void APIServer::serve_instances_variables(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
std::string uri(hm->uri.p, hm->uri.len);
std::string instance = uri.substr(uri.rfind("/") + 1);
@ -341,78 +355,231 @@ void APIServer::serve_instances_leave_room(Server *server, Server::session *sess @@ -341,78 +355,231 @@ void APIServer::serve_instances_leave_room(Server *server, Server::session *sess
int type = (int) TYPE_STRING;
m_storage->getUserSetting(info.id, instance, type, username);
if (username.empty()) {
send_ack(conn, true, "You are not registered to this Spectrum 2 instance.");
return;
}
std::string response = server->send_command(instance, "variables");
std::string frontend_room = get_http_var(hm, "frontend_room");
std::string response = server->send_command(instance, "leave_room " + username + " " + frontend_room);
std::vector<std::string> commands;
boost::split(commands, response, boost::is_any_of("\n"));
if (response.find("Left the room") == std::string::npos) {
send_ack(conn, true, response);
}
else {
send_ack(conn, false, response);
Document json;
json.SetObject();
json.AddMember("error", 0, json.GetAllocator());
std::vector<std::vector<std::string> > tmp;
Value cmds(kArrayType);
BOOST_FOREACH(const std::string &command, commands) {
escaped_list_separator<char> els('\\', ' ', '\"');
tokenizer<escaped_list_separator<char> > tok(command, els);
std::vector<std::string> tokens;
for(tokenizer<escaped_list_separator<char> >::iterator beg=tok.begin(); beg!=tok.end(); ++beg) {
tokens.push_back(*beg);
}
if (tokens.size() != 13) {
continue;
}
if (!session->admin && tokens[10] == "Admin") {
continue;
}
tmp.push_back(tokens);
Value cmd;
cmd.SetObject();
cmd.AddMember("name", tokens[0].c_str(), json.GetAllocator());
cmd.AddMember("desc", tokens[2].c_str(), json.GetAllocator());
cmd.AddMember("value", tokens[4].c_str(), json.GetAllocator());
cmd.AddMember("read_only", tokens[6].c_str(), json.GetAllocator());
cmd.AddMember("category", tokens[8].c_str(), json.GetAllocator());
cmd.AddMember("context", tokens[12].c_str(), json.GetAllocator());
cmds.PushBack(cmd, json.GetAllocator());
}
json.AddMember("variables", cmds, json.GetAllocator());
send_json(conn, json);
}
void APIServer::serve_instances_join_room_form(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
void APIServer::serve_instances_command_args(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
std::string uri(hm->uri.p, hm->uri.len);
std::string instance = uri.substr(uri.rfind("/") + 1);
std::string command = get_http_var(hm, "command");
boost::trim(command);
std::string response = server->send_command(instance, "commands");
bool found = false;
bool userContext = false;
std::vector<std::string> commands;
boost::split(commands, response, boost::is_any_of("\n"));
BOOST_FOREACH(const std::string &cmd, commands) {
escaped_list_separator<char> els('\\', ' ', '\"');
tokenizer<escaped_list_separator<char> > tok(cmd, els);
std::vector<std::string> tokens;
for(tokenizer<escaped_list_separator<char> >::iterator beg=tok.begin(); beg!=tok.end(); ++beg) {
tokens.push_back(*beg);
}
if (tokens.size() != 9) {
continue;
}
std::cout << tokens[0] << " " << command << "\n";
if (tokens[0] != command) {
continue;
}
if (!session->admin && tokens[6] == "Admin") {
send_ack(conn, false, "Only admin is able to query this command.");
return;
}
if (tokens[8] == "User") {
userContext = true;
}
found = true;
break;
}
if (!found) {
command = "unknown";
}
response = server->send_command(instance, "args " + command);
if (response.find("Error:") == 0) {
send_ack(conn, false, response);
return;
}
std::vector<std::string> args;
boost::split(args, response, boost::is_any_of("\n"));
// So far we support just Slack here. For XMPP, it is up to user to initiate the join room request.
Document json;
json.SetObject();
json.AddMember("error", 0, json.GetAllocator());
std::string response = server->send_command(instance, "join_room_fields");
std::vector<std::string> fields;
boost::split(fields, response, boost::is_any_of("\n"));
if (fields.size() != 8) {
fields.push_back("Nickname in 3rd-party room");
fields.push_back("3rd-party room name");
fields.push_back("3rd-party server");
fields.push_back("Slack Channel");
fields.push_back("BotNickname");
fields.push_back("room_name");
fields.push_back("3rd.party.server.org");
fields.push_back("mychannel");
}
json.AddMember("name_label", fields[0].c_str(), json.GetAllocator());
json.AddMember("legacy_room_label", fields[1].c_str(), json.GetAllocator());
json.AddMember("legacy_server_label", fields[2].c_str(), json.GetAllocator());
json.AddMember("frontend_room_label", fields[3].c_str(), json.GetAllocator());
json.AddMember("name_example", fields[4].c_str(), json.GetAllocator());
json.AddMember("legacy_room_example", fields[5].c_str(), json.GetAllocator());
json.AddMember("legacy_server_example", fields[6].c_str(), json.GetAllocator());
json.AddMember("frontend_room_example", fields[7].c_str(), json.GetAllocator());
std::vector<std::vector<std::string> > tmp;
Value argList(kArrayType);
if (userContext && session->admin) {
Value arg;
arg.SetObject();
arg.AddMember("name", "username", json.GetAllocator());
arg.AddMember("label", "Username", json.GetAllocator());
arg.AddMember("example", "", json.GetAllocator());
argList.PushBack(arg, json.GetAllocator());
}
BOOST_FOREACH(const std::string &argument, args) {
escaped_list_separator<char> els('\\', ' ', '\"');
tokenizer<escaped_list_separator<char> > tok(argument, els);
std::vector<std::string> tokens;
for(tokenizer<escaped_list_separator<char> >::iterator beg=tok.begin(); beg!=tok.end(); ++beg) {
tokens.push_back(*beg);
}
if (tokens.size() != 5) {
continue;
}
tmp.push_back(tokens);
Value arg;
arg.SetObject();
arg.AddMember("name", tokens[0].c_str(), json.GetAllocator());
arg.AddMember("label", tokens[2].c_str(), json.GetAllocator());
arg.AddMember("example", tokens[4].c_str(), json.GetAllocator());
argList.PushBack(arg, json.GetAllocator());
}
json.AddMember("args", argList, json.GetAllocator());
send_json(conn, json);
}
void APIServer::serve_instances_register_form(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
void APIServer::serve_instances_execute(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
std::string uri(hm->uri.p, hm->uri.len);
std::string instance = uri.substr(uri.rfind("/") + 1);
std::string command = get_http_var(hm, "command");
boost::trim(command);
std::string response = server->send_command(instance, "registration_fields");
std::vector<std::string> fields;
boost::split(fields, response, boost::is_any_of("\n"));
std::string response = server->send_command(instance, "commands");
bool found = false;
bool userContext = false;
std::vector<std::string> commands;
boost::split(commands, response, boost::is_any_of("\n"));
BOOST_FOREACH(const std::string &cmd, commands) {
escaped_list_separator<char> els('\\', ' ', '\"');
tokenizer<escaped_list_separator<char> > tok(cmd, els);
if (fields.empty()) {
fields.push_back("Jabber ID");
fields.push_back("3rd-party network username");
fields.push_back("3rd-party network password");
std::vector<std::string> tokens;
for(tokenizer<escaped_list_separator<char> >::iterator beg=tok.begin(); beg!=tok.end(); ++beg) {
tokens.push_back(*beg);
}
if (tokens.size() != 9) {
continue;
}
std::cout << tokens[0] << " " << command << "\n";
if (tokens[0] != command) {
continue;
}
if (!session->admin && tokens[6] == "Admin") {
send_ack(conn, false, "Only admin is able to execute.");
return;
}
if (tokens[8] == "User") {
userContext = true;
}
found = true;
break;
}
Document json;
json.SetObject();
json.AddMember("error", 0, json.GetAllocator());
json.AddMember("username_label", fields[0].c_str(), json.GetAllocator());
json.AddMember("legacy_username_label", fields.size() >= 2 ? fields[1].c_str() : "", json.GetAllocator());
json.AddMember("password_label", fields.size() >= 3 ? fields[2].c_str() : "", json.GetAllocator());
send_json(conn, json);
if (!found) {
command = "unknown";
}
UserInfo info;
m_storage->getUser(session->user, info);
std::string username = "";
int type = (int) TYPE_STRING;
m_storage->getUserSetting(info.id, instance, type, username);
if (userContext && !session->admin) {
if (username.empty()) {
send_ack(conn, false, "Error: You are not registered to this transport instance.");
return;
}
command += " " + username;
}
for (int i = 0; i < 10; ++i) {
std::string var = get_http_var(hm, std::string(std::string("command_arg") + boost::lexical_cast<std::string>(i)).c_str());
if (!var.empty()) {
command += " " + var;
}
}
response = server->send_command(instance, command);
boost::replace_all(response, "\n", "<br/>");
if (response.find("Error:") == 0) {
send_ack(conn, false, response);
}
else {
send_ack(conn, true, response);
}
}
void APIServer::serve_users(Server *server, Server::session *session, struct mg_connection *conn, struct http_message *hm) {
@ -494,23 +661,20 @@ void APIServer::handleRequest(Server *server, Server::session *sess, struct mg_c @@ -494,23 +661,20 @@ void APIServer::handleRequest(Server *server, Server::session *sess, struct mg_c
else if (has_prefix(&hm->uri, "/api/v1/instances/unregister/")) {
serve_instances_unregister(server, sess, conn, hm);
}
else if (has_prefix(&hm->uri, "/api/v1/instances/register_form/")) {
serve_instances_register_form(server, sess, conn, hm);
}
else if (has_prefix(&hm->uri, "/api/v1/instances/register/")) {
serve_instances_register(server, sess, conn, hm);
}
else if (has_prefix(&hm->uri, "/api/v1/instances/join_room_form/")) {
serve_instances_join_room_form(server, sess, conn, hm);
else if (has_prefix(&hm->uri, "/api/v1/instances/commands/")) {
serve_instances_commands(server, sess, conn, hm);
}
else if (has_prefix(&hm->uri, "/api/v1/instances/join_room/")) {
serve_instances_join_room(server, sess, conn, hm);
else if (has_prefix(&hm->uri, "/api/v1/instances/variables/")) {
serve_instances_variables(server, sess, conn, hm);
}
else if (has_prefix(&hm->uri, "/api/v1/instances/list_rooms/")) {
serve_instances_list_rooms(server, sess, conn, hm);
else if (has_prefix(&hm->uri, "/api/v1/instances/command_args/")) {
serve_instances_command_args(server, sess, conn, hm);
}
else if (has_prefix(&hm->uri, "/api/v1/instances/leave_room/")) {
serve_instances_leave_room(server, sess, conn, hm);
else if (has_prefix(&hm->uri, "/api/v1/instances/execute/")) {
serve_instances_execute(server, sess, conn, hm);
}
else if (has_prefix(&hm->uri, "/api/v1/users/remove/")) {
serve_users_remove(server, sess, conn, hm);

9
spectrum_manager/src/APIServer.h

@ -56,12 +56,11 @@ class APIServer { @@ -56,12 +56,11 @@ class APIServer {
void serve_instances_start(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_stop(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_unregister(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_commands(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_variables(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_command_args(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_execute(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_register(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_register_form(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_list_rooms(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_join_room(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_join_room_form(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_instances_leave_room(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_users(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_users_add(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);
void serve_users_remove(Server *server, Server::session *sess, struct mg_connection *conn, struct http_message *hm);

5
spectrum_manager/src/html/bootstrap.min.css vendored

File diff suppressed because one or more lines are too long

3
spectrum_manager/src/html/header.shtml

@ -5,6 +5,7 @@ @@ -5,6 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="description" content="Spectrum 2 : Spectrum 2 IM transports">
<link rel="stylesheet" type="text/css" href="/bootstrap.min.css">
<link href="/style.css" rel="stylesheet" type="text/css" media="all">
<link href="/form.css" rel="stylesheet" type="text/css" media="all">
<link href="https://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css" rel="stylesheet" type="text/css" media="all">
@ -12,6 +13,8 @@ @@ -12,6 +13,8 @@
<script src="/js/jquery-ui.js"></script>
<script src="/js/jquery.cookie.js"></script>
<script src="/js/config.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script src="/js/bootbox.min.js"></script>
<script src="/js/app.js"></script>
<title>Spectrum 2</title>
</head>

2
spectrum_manager/src/html/instances/list_rooms.shtml → spectrum_manager/src/html/instances/instance.shtml