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

This commit is contained in:
Jan Kaluza 2016-03-04 14:06:07 +01:00
parent 2c338a2447
commit 84ea5f3249
30 changed files with 2238 additions and 7966 deletions

View file

@ -32,6 +32,7 @@ class StorageBackend;
class UserManager;
class NetworkPluginServer;
class UserRegistration;
class AdminInterfaceCommand;
class AdminInterface {
public:
@ -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;
};
}

View file

@ -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;
};
}

View file

@ -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; }

View file

@ -38,6 +38,7 @@ namespace Transport {
class Factory;
class Config;
class UserManager;
class AdminInterface;
class Component {
public:
@ -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 {
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 {
Factory *m_factory;
Swift::EventLoop *m_loop;
Frontend *m_frontend;
AdminInterface *m_adminInterface;
friend class User;
friend class UserRegistration;

File diff suppressed because it is too large Load diff

View file

@ -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 "";
}
}

View file

@ -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;

View file

@ -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() {
}

View file

@ -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);

View file

@ -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 {
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) {
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;
}
}

View file

@ -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;

View file

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

View file

@ -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
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
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
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
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
}
}
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
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"));
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() != 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());
}
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");
std::string response = server->send_command(instance, "join_room " +
username + " " + name + " " + legacy_room + " " + legacy_server + " " + frontend_room);
if (response.find("Joined the room") == std::string::npos) {
send_ack(conn, true, response);
}
else {
send_ack(conn, false, response);
}
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
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.");
std::string response = server->send_command(instance, "variables");
std::vector<std::string> commands;
boost::split(commands, response, boost::is_any_of("\n"));
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_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::string frontend_room = get_http_var(hm, "frontend_room");
std::string response = server->send_command(instance, "leave_room " + username + " " + frontend_room);
if (response.find("Left the room") == std::string::npos) {
send_ack(conn, true, response);
std::vector<std::string> args;
boost::split(args, response, boost::is_any_of("\n"));
Document json;
json.SetObject();
json.AddMember("error", 0, 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());
}
else {
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_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, "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 execute.");
return;
}
if (tokens[8] == "User") {
userContext = true;
}
found = true;
break;
}
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);
}
}
void APIServer::serve_instances_join_room_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);
// 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");
else {
send_ack(conn, true, response);
}
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());
send_json(conn, json);
}
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_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
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);

View file

@ -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);

File diff suppressed because one or more lines are too long

View file

@ -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 @@
<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>

View file

@ -2,7 +2,7 @@
<script type="text/javascript">
$(function() {
show_list_rooms();
show_instance();
});
</script>

View file

@ -1,37 +0,0 @@
<!--#include virtual="/header.shtml" -->
<h2>Join room</h2>
<form action="/api/v1/instances/join_room" class="basic-grey" method="POST">
<h1>Join room
<span id="description">Join the room using this Spectrum 2 instance.</span>
</h1>
<label>
<span id="name_desc"></span>
<input type="text" id="name" name="name" placeholder=""></textarea>
</label>
<label>
<span id="legacy_room_desc"></span>
<input type="text" id="legacy_room" name="legacy_name" placeholder=""></textarea>
</label>
<label>
<span id="legacy_server_desc"></span>
<input type="text" id="legacy_server" name="legacy_server" placeholder=""></textarea>
</label>
<label>
<span id="frontend_room_desc"></span>
<input type="text" id="frontend_room" name="frontend_room" placeholder=""></textarea>
</label>
<label>
<span>&nbsp;</span>
<input type="submit" class="button_command" value="Join room" />
</label>
<input type="hidden" name="instance" id="instance" value=""></input>
</form><br/>
<script type="text/javascript">
$(function() {
fill_instances_join_room_form();
});
</script>
<!--#include virtual="/footer.shtml" -->

View file

@ -1,32 +0,0 @@
<!--#include virtual="/header.shtml" -->
<h2>Register Spectrum 2 instance</h2>
<form action="/api/v1/instances/register" class="basic-grey" method="POST">
<h1>Register Spectrum 2 instance
<span id="description"></span>
</h1>
<label>
<span id="jid_desc"></span>
<input type="text" id="jid" name="jid" placeholder=""></textarea>
</label>
<label id="uin_label">
<span id="uin_desc"></span>
<input type="text" id="uin" name="uin" placeholder=""></textarea>
</label>
<label id="password_label"><span id="password_desc"></span>
<input type="password" id="password" name="password" placeholder=""></textarea>
</label>
<label>
<span>&nbsp;</span>
<input type="submit" class="button_command" value="Register" />
</label>
<input type="hidden" name="instance" id="instance" value=""></input>
</form><br/>
<script type="text/javascript">
$(function() {
fill_instances_register_form();
});
</script>
<!--#include virtual="/footer.shtml" -->

View file

@ -14,16 +14,20 @@ function getQueryParams(qs) {
function show_instances() {
$.get($.cookie("base_location") + "api/v1/instances", function(data) {
$("#main_content").html("<h2>List of Spectrum 2 instances</h2><table id='main_result'><tr><th>Name<th>Status</th><th>Actions</th></tr>");
var admin = $.cookie("admin") == "1";
if (admin) {
$("#main_content").html("<h2>List of Spectrum 2 instances</h2><table id='main_result'><tr><th>Name<th>Status</th><th>Actions</th></tr></table>");
}
else {
$("#main_content").html("<h2>List of Spectrum 2 instances</h2><table id='main_result'><tr><th>Name<th>Status</th></tr></table>");
}
$.each(data.instances, function(i, instance) {
if (instance.running) {
if (admin) {
var command = instance.running ? "stop" : "start";
}
else {
var command = instance.registered ? "unregister" : "register";
if (instance.registered) {
instance.status += "<br/>Registered as " + instance.username;
}
@ -36,68 +40,37 @@ function show_instances() {
var command = "";
}
var row = '<tr>'
row += '<td>' + instance.name + '</td>'
row += '<td><a href="instance.shtml?id=' + instance.id + '">' + instance.name + '</a></td>'
row += '<td>' + instance.status + '</td>'
if (admin) {
if (command == "") {
row += '<td></td></tr>';
$("#main_result > tbody:last-child").append(row);
}
else {
row += '<td>';
row += '<a class="button_command" href="' + $.cookie("base_location") + 'api/v1/instances/' + command + '/' + instance.id + '">' + command + '</a>';
row += '</td></tr>';
$("#main_result > tbody:last-child").append(row);
$(".button_command").click(function(e) {
e.preventDefault();
$(this).parent().empty().progressbar( {value: false} ).css('height', '1em');
if (command == 'register') {
row += '<td><a href="' + $.cookie("base_location") + 'instances/register.shtml?id=' + instance.id + '">' + command + '</a>' + '</td></tr>';
$("#main_result > tbody:last-child").append(row);
}
else if (command == "") {
row += '<td></td></tr>';
$("#main_result > tbody:last-child").append(row);
var url = $(this).attr('href');
$.get(url, function(data) {
show_instances();
});
})
}
}
else {
row += '<td>';
if (command == 'unregister' && instance.frontend == "slack") {
row += '<a href="' + $.cookie("base_location") + 'instances/join_room.shtml?id=' + instance.id + '">Join room</a> | ';
row += '<a href="' + $.cookie("base_location") + 'instances/list_rooms.shtml?id=' + instance.id + '">List joined rooms</a> | ';
}
row += '<a class="button_command" href="' + $.cookie("base_location") + 'api/v1/instances/' + command + '/' + instance.id + '">' + command + '</a>';
row += '</td></tr>';
row += '</tr>';
$("#main_result > tbody:last-child").append(row);
$(".button_command").click(function(e) {
e.preventDefault();
$(this).parent().empty().progressbar( {value: false} ).css('height', '1em');
var url = $(this).attr('href');
$.get(url, function(data) {
show_instances();
});
})
}
});
});
}
function show_list_rooms() {
var query = getQueryParams(document.location.search);
$.get($.cookie("base_location") + "api/v1/instances/list_rooms/" + query.id, function(data) {
$("#main_content").html("<h2>Joined rooms</h2><table id='main_result'><tr><th>" + data.frontend_room_label + "</th><th>" + data.legacy_room_label + "</th><th>" + data.legacy_server_label + "</th><th>" + data.name_label + "</th><th>Actions</th></tr>");
$.each(data.rooms, function(i, room) {
var row = '<tr>';
row += '<td>' + room.frontend_room + '</td>';
row += '<td>' + room.legacy_room + '</td>';
row += '<td>' + room.legacy_server + '</td>';
row += '<td>' + room.name + '</td>';
row += '<td><a class="button_command" href="' + $.cookie("base_location") + 'api/v1/instances/leave_room/' + query.id + '?frontend_room=' + encodeURIComponent(room.frontend_room) + '">Leave</a></td>';
row += '</tr>';
$("#main_result > tbody:last-child").append(row);
$(".button_command").click(function(e) {
e.preventDefault();
$(this).parent().empty().progressbar( {value: false} ).css('height', '1em');
var url = $(this).attr('href');
$.get(url, function(data) {
show_list_rooms();
});
})
});
});
}
function show_users() {
var admin = $.cookie("admin") == "1";
if (!admin) {
@ -126,85 +99,6 @@ function show_users() {
});
}
function fill_instances_join_room_form() {
var query = getQueryParams(document.location.search);
$("#instance").attr("value", query.id);
$(".button_command").click(function(e) {
e.preventDefault();
$(this).parent().empty().progressbar( {value: false} ).css('height', '1em');
var postdata ={
"name": $("#name").val(),
"legacy_room": $("#legacy_room").val(),
"legacy_server": $("#legacy_server").val(),
"frontend_room": $("#frontend_room").val()
};
$.post($.cookie("base_location") + "api/v1/instances/join_room/" + $("#instance").val(), postdata, function(data) {
window.location.replace("index.shtml");
});
})
$.get($.cookie("base_location") + "api/v1/instances/join_room_form/" + query.id, function(data) {
$("#name_desc").html(data.name_label + ":");
$("#legacy_room_desc").html(data.legacy_room_label + ":");
$("#legacy_server_desc").html(data.legacy_server_label + ":");
$("#frontend_room_desc").html(data.frontend_room_label + ":");
$("#name").attr("placeholder", data.name_example);
$("#legacy_room").attr("placeholder", data.legacy_room_example);
$("#legacy_server").attr("placeholder", data.legacy_server_example);
$("#frontend_room").attr("placeholder", data.frontend_room_example);
});
}
function fill_instances_register_form() {
var query = getQueryParams(document.location.search);
$("#instance").attr("value", query.id);
$(".button_command").click(function(e) {
e.preventDefault();
$(this).parent().empty().progressbar( {value: false} ).css('height', '1em');
var postdata ={
"jid": $("#jid").val(),
"uin": $("#uin").val(),
"password": $("#password").val()
};
$.post($.cookie("base_location") + "api/v1/instances/register/" + $("#instance").val(), postdata, function(data) {
if (data.oauth2_url) {
window.location.replace(data.oauth2_url);
}
else {
window.location.replace("index.shtml");
}
});
})
$.get($.cookie("base_location") + "api/v1/instances/register_form/" + query.id, function(data) {
$("#jid_desc").html(data.username_label + ":");
$("#jid").attr("placeholder", data.username_label);
if (data.legacy_username_label.length == 0) {
$('#uin_label').hide();
}
else {
$("#uin_desc").html(data.legacy_username_label + ":");
$("#uin").attr("placeholder", data.legacy_username_label);
}
if (data.password_label.length == 0) {
$('#password_label').hide();
}
else {
$("#password_desc").html(data.password_label + ":");
$("#password").attr("placeholder", data.password_label);
}
});
}
function fill_users_register_form() {
$(".button").click(function(e) {
e.preventDefault();
@ -231,3 +125,154 @@ function fill_users_register_form() {
})
}
function execute_command(instance, command) {
$.get($.cookie("base_location") + 'api/v1/instances/command_args/' + instance + '?command=' + command, function(data) {
var form = '<div class="row">';
if (data.args.length != 0) {
form += '<div class="col-md-12"><form class="form-horizontal">';
$.each(data.args, function(i, arg) {
form += '<div class="form-group">';
form += '<label class="col-md-4 control-label" for="' + arg.name + '">' + arg.label + ':</label>';
form += '<div class="col-md-4">';
form += '<input id="command_arg' + i + '" name="command_arg' + i + '" type="text" placeholder="' + arg.example + '" class="form-control input-md"/>';
form += '</div></div>';
console.log('command_arg' + i );
});
}
else {
form += '<div><form class="form-horizontal">';
form += '<div class="form-group">';
form += '<label class="control-label">No arguments needed for this command, you can just execute it.</label>';
form += '</div>';
}
form += '</form></div></div>'
bootbox.dialog({
title: "Command execution: " + command + ".",
message: form,
buttons: {
cancel: {
label: "Cancel",
className: "btn-cancel"
},
success: {
label: "Execute",
className: "btn-success",
callback: function () {
if (command == "register") {
var postdata = {};
if ($("#command_arg0").val()) {
postdata["jid"] = $("#command_arg0").val();
}
if ($("#command_arg1").val()) {
postdata["uin"] = $("#command_arg1").val();
}
if ($("#command_arg2").val()) {
postdata["password"] = $("#command_arg2").val();
}
$.post($.cookie("base_location") + "api/v1/instances/register/" + instance, postdata, function(data) {
if (data.oauth2_url) {
window.location.replace(data.oauth2_url);
}
else {
var dialog = bootbox.dialog({
title: "Command result: " + command + ".",
message: "<pre>" + data.message + "</pre>",
buttons: {
success: {
label: "OK",
className: "btn-success",
callback: function () {
location.reload();
}
}
}
})
dialog.find("div.modal-dialog").addClass("largeWidth");
dialog.find("div.modal-body").addClass("maxHeight");
}
});
}
else {
if (command == "unregister") {
var posturl = $.cookie("base_location") + "api/v1/instances/unregister/" + instance;
}
else {
var posturl = $.cookie("base_location") + "api/v1/instances/execute/" + instance + "?command=" + command;
}
var postdata = {}
for (i = 0; i < 10; i++) {
var val = $('#command_arg' + i).val();
if (val) {
postdata["command_arg" + i] = val;
}
}
$.post(posturl, postdata, function(data) {
var dialog = bootbox.dialog({
title: "Command result: " + command + ".",
message: "<pre>" + data.message + "</pre>",
buttons: {
success: {
label: "OK",
className: "btn-success",
callback: function () {
if (command == "unregister") {
location.reload();
}
}
}
}
})
dialog.find("div.modal-dialog").addClass("largeWidth");
dialog.find("div.modal-body").addClass("maxHeight");
});
}
}
}
}
})
});
}
function show_instance() {
var query = getQueryParams(document.location.search);
$("#main_content").html("<h2>Instance: " + query.id + "</h2><h4>Available commands:</h4><table id='commands'><tr><th>Name<th>Category</th><th>Description</th></tr></table><h4>Available variables:</h4><table id='variables'><tr><th>Name<th>Value</th><th>Read-only</th><th>Desc</th></tr></table>");
$.get($.cookie("base_location") + "api/v1/instances/commands/" + query.id, function(data) {
$.each(data.commands, function(i, command) {
var row = '<tr>'
row += '<td><a class="button_command" command="' + command.name + '" instance="' + query.id + '" href="' + $.cookie("base_location") + 'api/v1/instances/command_args/' + query.id + '?command=' + command.name +'">' + command.name + '</a></td>';
row += '<td>' + command.category + '</td>';
row += '<td>' + command.desc + '</td>';
row += '</tr>';
$("#commands > tbody:last-child").append(row);
});
$(".button_command").click(function(e) {
e.preventDefault();
var command = $(this).attr('command');
var instance = $(this).attr('instance');
execute_command(instance, command);
})
});
$.get($.cookie("base_location") + "api/v1/instances/variables/" + query.id, function(data) {
$.each(data.variables, function(i, variable) {
var row = '<tr>'
row += '<td>' + variable.name + '</td>';
row += '<td>' + variable.value + '</td>';
row += '<td>' + variable.read_only + '</td>';
row += '<td>' + variable.desc + '</td>';
row += '</tr>';
$("#variables > tbody:last-child").append(row);
});
});
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
var BaseLocation = "/spectrum/";

File diff suppressed because one or more lines are too long

View file

@ -463,3 +463,14 @@ Small Device Styles
}
}
.largeWidth {
margin: 0 auto;
width: 90%;
}
.maxHeight {
max-height: 500px;
overflow-y: auto;
}

View file

@ -446,6 +446,7 @@ void Server::event_handler(struct mg_connection *conn, int ev, void *p) {
boost::replace_all(resp, "src=\"/", std::string("src=\"") + CONFIG_STRING(m_config, "service.base_location"));
boost::replace_all(resp, "action=\"/", std::string("action=\"") + CONFIG_STRING(m_config, "service.base_location"));
strcpy(conn->send_mbuf.buf, resp.c_str());
conn->send_mbuf.len = resp.size();
mbuf_trim(&conn->send_mbuf);
return;
}

View file

@ -0,0 +1,132 @@
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <Swiften/Swiften.h>
#include <Swiften/EventLoop/DummyEventLoop.h>
#include <Swiften/Server/Server.h>
#include <Swiften/Network/DummyNetworkFactories.h>
#include <Swiften/Network/DummyConnectionServer.h>
#include "Swiften/Server/ServerStanzaChannel.h"
#include "Swiften/Server/ServerFromClientSession.h"
#include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h"
#include "BasicSlackTest.h"
#include "transport/AdminInterface.h"
#if !HAVE_SWIFTEN_3
#define get_value_or(X) substr()
#endif
using namespace Transport;
class AdminInterfaceTest : public CPPUNIT_NS :: TestFixture, public BasicSlackTest {
CPPUNIT_TEST_SUITE(AdminInterfaceTest);
CPPUNIT_TEST(helpCommand);
CPPUNIT_TEST(statusCommand);
CPPUNIT_TEST(joinRoomArgs);
CPPUNIT_TEST(getOAuth2URLCommand);
CPPUNIT_TEST(unknownCommand);
CPPUNIT_TEST(listJoinLeaveRoomsCommand);
CPPUNIT_TEST(badArgCount);
CPPUNIT_TEST(commandsCommand);
CPPUNIT_TEST(variablesCommand);
CPPUNIT_TEST_SUITE_END();
public:
AdminInterface *admin;
NetworkPluginServer *serv;
void setUp (void) {
setMeUp();
serv = new NetworkPluginServer(component, cfg, userManager, NULL);
admin = new AdminInterface(component, userManager, serv, storage, NULL);
component->setAdminInterface(admin);
}
void tearDown (void) {
delete admin;
delete serv;
tearMeDown();
}
std::string sendAdminMessage(const std::string &cmd) {
Swift::Message::ref msg = boost::shared_ptr<Swift::Message>(new Swift::Message());
msg->setFrom(Swift::JID("me@localhost"));
msg->setTo(Swift::JID("localhost"));
msg->setBody(cmd);
admin->handleMessageReceived(msg);
return msg->getBody().get_value_or("");
}
void helpCommand() {
std::string resp = sendAdminMessage("help");
CPPUNIT_ASSERT(resp.find(" VAR status - Shows instance status\n") != std::string::npos);
}
void statusCommand() {
std::string resp = sendAdminMessage("status");
CPPUNIT_ASSERT_EQUAL(std::string("Running (0 users connected using 0 backends)"), resp);
}
void joinRoomArgs() {
std::string resp = sendAdminMessage("args join_room");
CPPUNIT_ASSERT_EQUAL(std::string("nickname - \"Nickname in 3rd-party room\" Example: \"BotNickname\"\n"
"legacy_room - \"3rd-party room name\" Example: \"3rd-party room name\"\n"
"legacy_server - \"3rd-party server\" Example: \"3rd.party.server.org\"\n"
"slack_channel - \"Slack Chanel\" Example: \"mychannel\"\n"), resp);
}
void getOAuth2URLCommand() {
std::string resp = sendAdminMessage("get_oauth2_url x y z");
CPPUNIT_ASSERT(resp.find("https://slack.com/oauth/authorize?client_id=&scope=channels%3Aread%20channels%3Awrite%20team%3Aread%20im%3Aread%20im%3Awrite%20chat%3Awrite%3Abot%20bot&redirect_uri=https%3A%2F%2Fslack.spectrum.im%2Foauth2%2Flocalhost&state=") != std::string::npos);
}
void unknownCommand() {
std::string resp = sendAdminMessage("unknown_command test");
CPPUNIT_ASSERT_EQUAL(std::string("Error: Unknown variable or command"), resp);
}
void listJoinLeaveRoomsCommand() {
addUser();
std::string resp = sendAdminMessage("list_rooms user@localhost");
CPPUNIT_ASSERT_EQUAL(std::string(""), resp);
resp = sendAdminMessage("join_room user@localhost SlackBot spectrum conference.spectrum.im slack_channel");
CPPUNIT_ASSERT_EQUAL(std::string("Joined the room"), resp);
resp = sendAdminMessage("list_rooms user@localhost");
CPPUNIT_ASSERT_EQUAL(std::string("connected room SlackBot spectrum conference.spectrum.im slack_channel\n"), resp);
resp = sendAdminMessage("leave_room user@localhost slack_channel");
CPPUNIT_ASSERT_EQUAL(std::string("Left the room"), resp);
resp = sendAdminMessage("list_rooms user@localhost");
CPPUNIT_ASSERT_EQUAL(std::string(""), resp);
}
void badArgCount() {
addUser();
std::string resp;
resp = sendAdminMessage("join_room user@localhost SlackBot spectrum conference.spectrum.im slack_channel unknown");
CPPUNIT_ASSERT_EQUAL(std::string("Error: Too many arguments."), resp);
resp = sendAdminMessage("join_room user@localhost SlackBot spectrum conference.spectrum.im");
CPPUNIT_ASSERT_EQUAL(std::string("Error: Argument is missing."), resp);
}
void commandsCommand() {
addUser();
std::string resp;
resp = sendAdminMessage("commands");
CPPUNIT_ASSERT(resp.find("join_room - \"Join the room\" Category: Frontend AccesMode: User Context: User") != std::string::npos);
}
void variablesCommand() {
addUser();
std::string resp;
resp = sendAdminMessage("variables");
CPPUNIT_ASSERT(resp.find("backends_count - \"Number of active backends\" Value: \"0\" Read-only: true") != std::string::npos);
}
};
CPPUNIT_TEST_SUITE_REGISTRATION (AdminInterfaceTest);

View file

@ -34,7 +34,7 @@
using namespace Transport;
void BasicSlackTest::setMeUp (void) {
std::istringstream ifs("service.server_mode = 1\nservice.jid=localhost\nservice.more_resources=1\n");
std::istringstream ifs("service.server_mode = 1\nservice.jid=localhost\nservice.more_resources=1\nservice.admin_jid=me@localhost\n");
cfg = new Config();
cfg->load(ifs);

View file

@ -56,7 +56,7 @@
using namespace Transport;
class BasicSlackTest : public Swift::XMPPParserClient {
class BasicSlackTest {
public:
void setMeUp (void);

View file

@ -35,7 +35,7 @@ using namespace Transport;
void BasicTest::setMeUp (void) {
streamEnded = false;
std::istringstream ifs("service.server_mode = 1\nservice.jid=localhost\nservice.more_resources=1\n");
std::istringstream ifs("service.server_mode = 1\nservice.jid=localhost\nservice.more_resources=1\nservice.admin_jid=me@localhost\n");
cfg = new Config();
cfg->load(ifs);