mod_admin_web/admin_web/mod_admin_web.lua
changeset 317 4f78f5020aa9
parent 309 5ec9125575fc
child 319 ba2e78661ea8
equal deleted inserted replaced
316:c86fc337d56f 317:4f78f5020aa9
    13 --   <encrypted/>
    13 --   <encrypted/>
    14 --   <compressed/>
    14 --   <compressed/>
    15 --   <in/> / <out/>
    15 --   <in/> / <out/>
    16 -- </session>
    16 -- </session>
    17 
    17 
    18 local stanza = require "util.stanza";
    18 local st = require "util.stanza";
    19 local uuid_generate = require "util.uuid".generate;
    19 local uuid_generate = require "util.uuid".generate;
       
    20 local is_admin = require "usermanager".is_admin;
       
    21 local pubsub = require "util.pubsub";
    20 local httpserver = require "net.httpserver";
    22 local httpserver = require "net.httpserver";
       
    23 local jid_bare = require "util.jid".bare;
    21 local lfs = require "lfs";
    24 local lfs = require "lfs";
    22 local open = io.open;
    25 local open = io.open;
    23 local stat = lfs.attributes;
    26 local stat = lfs.attributes;
    24 
    27 
    25 local host = module:get_host();
    28 local host = module:get_host();
    26 local service = config.get("*", "core", "webadmin_pubsub_host") or ("pubsub." .. host);
    29 local service;
    27 
    30 
    28 local http_base = (prosody.paths.plugins or "./plugins/") .. "admin_web/www_files";
    31 local http_base = (prosody.paths.plugins or "./plugins/") .. "admin_web/www_files";
    29 
    32 
       
    33 local xmlns_adminsub = "http://prosody.im/adminsub";
    30 local xmlns_c2s_session = "http://prosody.im/streams/c2s";
    34 local xmlns_c2s_session = "http://prosody.im/streams/c2s";
    31 local xmlns_s2s_session = "http://prosody.im/streams/s2s";
    35 local xmlns_s2s_session = "http://prosody.im/streams/s2s";
    32 
    36 
    33 local response_301 = { status = "301 Moved Permanently" };
    37 local response_301 = { status = "301 Moved Permanently" };
    34 local response_400 = { status = "400 Bad Request", body = "<h1>Bad Request</h1>Sorry, we didn't understand your request :(" };
    38 local response_400 = { status = "400 Bad Request", body = "<h1>Bad Request</h1>Sorry, we didn't understand your request :(" };
    49 	local id = idmap[name];
    53 	local id = idmap[name];
    50 	if not id then
    54 	if not id then
    51 		id = uuid_generate();
    55 		id = uuid_generate();
    52 		idmap[name] = id;
    56 		idmap[name] = id;
    53 	end
    57 	end
    54 	local item = stanza.stanza("item", { id = id }):tag("session", {xmlns = xmlns_c2s_session, jid = name}):up();
    58 	local item = st.stanza("item", { id = id }):tag("session", {xmlns = xmlns_c2s_session, jid = name}):up();
    55 	if session.secure then
    59 	if session.secure then
    56 		item:tag("encrypted"):up();
    60 		item:tag("encrypted"):up();
    57 	end
    61 	end
    58 	if session.compressed then
    62 	if session.compressed then
    59 		item:tag("compressed"):up();
    63 		item:tag("compressed"):up();
    60 	end
    64 	end
    61 	hosts[service].modules.pubsub.service:publish(xmlns_c2s_session, service, id, item);
    65 	service:publish(xmlns_c2s_session, host, id, item);
    62 	module:log("debug", "Added client " .. name);
    66 	module:log("debug", "Added client " .. name);
    63 end
    67 end
    64 
    68 
    65 function del_client(session)
    69 function del_client(session)
    66 	local name = session.full_jid;
    70 	local name = session.full_jid;
    67 	local id = idmap[name];
    71 	local id = idmap[name];
    68 	if id then
    72 	if id then
    69 		local notifier = stanza.stanza("retract", { id = id });
    73 		local notifier = st.stanza("retract", { id = id });
    70 		hosts[service].modules.pubsub.service:retract(xmlns_c2s_session, service, id, notifier);
    74 		service:retract(xmlns_c2s_session, host, id, notifier);
    71 	end
    75 	end
    72 end
    76 end
    73 
    77 
    74 function add_host(session, type)
    78 function add_host(session, type)
    75 	local name = (type == "out" and session.to_host) or (type == "in" and session.from_host);
    79 	local name = (type == "out" and session.to_host) or (type == "in" and session.from_host);
    76 	local id = idmap[name.."_"..type];
    80 	local id = idmap[name.."_"..type];
    77 	if not id then
    81 	if not id then
    78 		id = uuid_generate();
    82 		id = uuid_generate();
    79 		idmap[name.."_"..type] = id;
    83 		idmap[name.."_"..type] = id;
    80 	end
    84 	end
    81 	local item = stanza.stanza("item", { id = id }):tag("session", {xmlns = xmlns_s2s_session, jid = name})
    85 	local item = st.stanza("item", { id = id }):tag("session", {xmlns = xmlns_s2s_session, jid = name})
    82 		:tag(type):up();
    86 		:tag(type):up();
    83 	if session.secure then
    87 	if session.secure then
    84 		item:tag("encrypted"):up();
    88 		item:tag("encrypted"):up();
    85 	end
    89 	end
    86 	if session.compressed then
    90 	if session.compressed then
    87 		item:tag("compressed"):up();
    91 		item:tag("compressed"):up();
    88 	end
    92 	end
    89 	hosts[service].modules.pubsub.service:publish(xmlns_s2s_session, service, id, item);
    93 	service:publish(xmlns_s2s_session, host, id, item);
    90 	module:log("debug", "Added host " .. name .. " s2s" .. type);
    94 	module:log("debug", "Added host " .. name .. " s2s" .. type);
    91 end
    95 end
    92 
    96 
    93 function del_host(session, type)
    97 function del_host(session, type)
    94 	local name = (type == "out" and session.to_host) or (type == "in" and session.from_host);
    98 	local name = (type == "out" and session.to_host) or (type == "in" and session.from_host);
    95 	local id = idmap[name.."_"..type];
    99 	local id = idmap[name.."_"..type];
    96 	if id then
   100 	if id then
    97 		local notifier = stanza.stanza("retract", { id = id });
   101 		local notifier = st.stanza("retract", { id = id });
    98 		hosts[service].modules.pubsub.service:retract(xmlns_s2s_session, service, id, notifier);
   102 		service:retract(xmlns_s2s_session, host, id, notifier);
    99 	end
   103 	end
   100 end
   104 end
   101 
   105 
   102 local function preprocess_path(path)
   106 local function preprocess_path(path)
   103 	if path:sub(1,1) ~= "/" then
   107 	if path:sub(1,1) ~= "/" then
   131 		return response_403;
   135 		return response_403;
   132 	end
   136 	end
   133 	local f, err = open(full_path, "rb");
   137 	local f, err = open(full_path, "rb");
   134 	if not f then return response_404; end
   138 	if not f then return response_404; end
   135 	local data = f:read("*a");
   139 	local data = f:read("*a");
   136 	data = data:gsub("%%PUBSUBHOST%%", service);
   140 	data = data:gsub("%%ADMINSUBHOST%%", host);
   137 	f:close();
   141 	f:close();
   138 	if not data then
   142 	if not data then
   139 		return response_403;
   143 		return response_403;
   140 	end
   144 	end
   141 	local ext = path:match("%.([^.]*)$");
   145 	local ext = path:match("%.([^.]*)$");
   159 	httpserver.new_from_config(http_conf, handle_file_request, { base = "admin" });
   163 	httpserver.new_from_config(http_conf, handle_file_request, { base = "admin" });
   160 end
   164 end
   161 
   165 
   162 prosody.events.add_handler("server-started", function ()
   166 prosody.events.add_handler("server-started", function ()
   163 	local host_session = prosody.hosts[host];
   167 	local host_session = prosody.hosts[host];
   164 	if not select(2, hosts[service].modules.pubsub.service:get_nodes(service))[xmlns_s2s_session] then
   168 	if not select(2, service:get_nodes(true))[xmlns_s2s_session] then
   165 		local ok, errmsg = hosts[service].modules.pubsub.service:create(xmlns_s2s_session, service);
   169 		local ok, errmsg = service:create(xmlns_s2s_session, true);
   166 		if not ok then
   170 		if not ok then
   167 			module:log("warn", "Could not create node " .. xmlns_s2s_session .. ": " .. tostring(errmsg));
   171 			module:log("warn", "Could not create node " .. xmlns_s2s_session .. ": " .. tostring(errmsg));
       
   172 		else
       
   173 			service:set_affiliation(xmlns_s2s_session, true, host, "owner")
   168 		end
   174 		end
   169 	end
   175 	end
   170 
   176 
   171 	for remotehost, session in pairs(host_session.s2sout) do
   177 	for remotehost, session in pairs(host_session.s2sout) do
   172 		if session.type ~= "s2sout_unauthed" then
   178 		if session.type ~= "s2sout_unauthed" then
   177 		if session.to_host == host then
   183 		if session.to_host == host then
   178 			add_host(session, "in");
   184 			add_host(session, "in");
   179 		end
   185 		end
   180 	end
   186 	end
   181 
   187 
   182 	if not select(2, hosts[service].modules.pubsub.service:get_nodes(service))[xmlns_c2s_session] then
   188 	if not select(2, service:get_nodes(true))[xmlns_c2s_session] then
   183 		local ok, errmsg = hosts[service].modules.pubsub.service:create(xmlns_c2s_session, service);
   189 		local ok, errmsg = service:create(xmlns_c2s_session, true);
   184 		if not ok then
   190 		if not ok then
   185 			module:log("warn", "Could not create node " .. xmlns_c2s_session .. ": " .. tostring(errmsg));
   191 			module:log("warn", "Could not create node " .. xmlns_c2s_session .. ": " .. tostring(errmsg));
       
   192 		else
       
   193 			service:set_affiliation(xmlns_c2s_session, true, host, "owner")
   186 		end
   194 		end
   187 	end
   195 	end
   188 
   196 
   189 	for username, user in pairs(host_session.sessions or {}) do
   197 	for username, user in pairs(host_session.sessions or {}) do
   190 		for resource, session in pairs(user.sessions or {}) do
   198 		for resource, session in pairs(user.sessions or {}) do
   191 			add_client(session);
   199 			add_client(session);
   192 		end
   200 		end
   193 	end
   201 	end
   194 end);
   202 end);
   195 
   203 
       
   204 function simple_broadcast(node, jids, item)
       
   205 	item = st.clone(item);
       
   206 	item.attr.xmlns = nil; -- Clear the pubsub namespace
       
   207 	local message = st.message({ from = module.host, type = "headline" })
       
   208 		:tag("event", { xmlns = xmlns_adminsub .. "#event" })
       
   209 			:tag("items", { node = node })
       
   210 				:add_child(item);
       
   211 	for jid in pairs(jids) do
       
   212 		module:log("debug", "Sending notification to %s", jid);
       
   213 		message.attr.to = jid;
       
   214 		core_post_stanza(hosts[host], message);
       
   215 	end
       
   216 end
       
   217 
       
   218 function get_affiliation(jid)
       
   219 	local bare_jid = jid_bare(jid);
       
   220 	if is_admin(bare_jid, host) then
       
   221 		return "member";
       
   222 	else
       
   223 		return "none";
       
   224 	end
       
   225 end
       
   226 
       
   227 module:hook("iq/host/http://prosody.im/adminsub:adminsub", function(event)
       
   228 	local origin, stanza = event.origin, event.stanza;
       
   229 	local adminsub = stanza.tags[1];
       
   230 	local action = adminsub.tags[1];
       
   231 	local reply;
       
   232 	if action.name == "subscribe" then
       
   233 		local ok, ret = service:add_subscription(action.attr.node, stanza.attr.from, stanza.attr.from);
       
   234 		if ok then
       
   235 			reply = st.reply(stanza)
       
   236 				:tag("adminsub", { xmlns = xmlns_adminsub });
       
   237 		else
       
   238 			reply = st.error_reply(stanza, "cancel", ret);
       
   239 		end
       
   240 	elseif action.name == "items" then
       
   241 		local node = action.attr.node;
       
   242 		local ok, ret = service:get_items(node, stanza.attr.from);
       
   243 		if not ok then
       
   244 			return origin.send(st.error_reply(stanza, "cancel", ret));
       
   245 		end
       
   246 
       
   247 		local data = st.stanza("items", { node = node });
       
   248 		for _, entry in pairs(ret) do
       
   249 			data:add_child(entry);
       
   250 		end
       
   251 		if data then
       
   252 			reply = st.reply(stanza)
       
   253 				:tag("adminsub", { xmlns = xmlns_adminsub })
       
   254 					:add_child(data);
       
   255 		else
       
   256 			reply = st.error_reply(stanza, "cancel", "item-not-found");
       
   257 		end
       
   258 	else
       
   259 		reply = st.error_reply(stanza, "feature-not-implemented");
       
   260 	end
       
   261 	return origin.send(reply);
       
   262 end);
       
   263 
   196 module:hook("resource-bind", function(event)
   264 module:hook("resource-bind", function(event)
   197 	add_client(event.session);
   265 	add_client(event.session);
   198 end);
   266 end);
   199 
   267 
   200 module:hook("resource-unbind", function(event)
   268 module:hook("resource-unbind", function(event)
   201 	del_client(event.session);
   269 	del_client(event.session);
       
   270 	service:remove_subscription(xmlns_c2s_session, host, event.session.full_jid);
       
   271 	service:remove_subscription(xmlns_s2s_session, host, event.session.full_jid);
   202 end);
   272 end);
   203 
   273 
   204 module:hook("s2sout-established", function(event)
   274 module:hook("s2sout-established", function(event)
   205 	add_host(event.session, "out");
   275 	add_host(event.session, "out");
   206 end);
   276 end);
   214 end);
   284 end);
   215 
   285 
   216 module:hook("s2sin-destroyed", function(event)
   286 module:hook("s2sin-destroyed", function(event)
   217 	del_host(event.session, "in");
   287 	del_host(event.session, "in");
   218 end);
   288 end);
       
   289 
       
   290 service = pubsub.new({
       
   291 	broadcaster = simple_broadcast;
       
   292 	normalize_jid = jid_bare;
       
   293 	get_affiliation = get_affiliation;
       
   294 	capabilities = {
       
   295 		member = {
       
   296 			create = false;
       
   297 			publish = false;
       
   298 			retract = false;
       
   299 			get_nodes = true;
       
   300 
       
   301 			subscribe = true;
       
   302 			unsubscribe = true;
       
   303 			get_subscription = true;
       
   304 			get_subscriptions = true;
       
   305 			get_items = true;
       
   306 
       
   307 			subscribe_other = false;
       
   308 			unsubscribe_other = false;
       
   309 			get_subscription_other = false;
       
   310 			get_subscriptions_other = false;
       
   311 
       
   312 			be_subscribed = true;
       
   313 			be_unsubscribed = true;
       
   314 
       
   315 			set_affiliation = false;
       
   316 		};
       
   317 
       
   318 		owner = {
       
   319 			create = true;
       
   320 			publish = true;
       
   321 			retract = true;
       
   322 			get_nodes = true;
       
   323 
       
   324 			subscribe = true;
       
   325 			unsubscribe = true;
       
   326 			get_subscription = true;
       
   327 			get_subscriptions = true;
       
   328 			get_items = true;
       
   329 
       
   330 			subscribe_other = true;
       
   331 			unsubscribe_other = true;
       
   332 			get_subscription_other = true;
       
   333 			get_subscriptions_other = true;
       
   334 
       
   335 			be_subscribed = true;
       
   336 			be_unsubscribed = true;
       
   337 
       
   338 			set_affiliation = true;
       
   339 		};
       
   340 	};
       
   341 });
       
   342