1 |
|
%%============================================================================== |
2 |
|
%% Copyright 2020 Erlang Solutions Ltd. |
3 |
|
%% |
4 |
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
|
%% you may not use this file except in compliance with the License. |
6 |
|
%% You may obtain a copy of the License at |
7 |
|
%% |
8 |
|
%% http://www.apache.org/licenses/LICENSE-2.0 |
9 |
|
%% |
10 |
|
%% Unless required by applicable law or agreed to in writing, software |
11 |
|
%% distributed under the License is distributed on an "AS IS" BASIS, |
12 |
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
|
%% See the License for the specific language governing permissions and |
14 |
|
%% limitations under the License. |
15 |
|
%%============================================================================== |
16 |
|
-module (mod_extdisco). |
17 |
|
-author('jan.ciesla@erlang-solutions.com'). |
18 |
|
|
19 |
|
-xep([{xep, 215}, {version, "1.0.0"}]). |
20 |
|
-behaviour(gen_mod). |
21 |
|
|
22 |
|
%% gen_mod callbacks. |
23 |
|
-export([start/2, stop/1, supported_features/0, config_spec/0]). |
24 |
|
|
25 |
|
-export([process_iq/5]). |
26 |
|
|
27 |
|
-ignore_xref([process_iq/5]). |
28 |
|
|
29 |
|
-include("jlib.hrl"). |
30 |
|
-include("mongoose_config_spec.hrl"). |
31 |
|
|
32 |
|
-spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok. |
33 |
|
start(HostType, #{iqdisc := IQDisc}) -> |
34 |
4 |
gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_EXTDISCO, ejabberd_local, |
35 |
|
fun ?MODULE:process_iq/5, #{}, IQDisc). |
36 |
|
|
37 |
|
-spec stop(mongooseim:host_type()) -> ok. |
38 |
|
stop(HostType) -> |
39 |
4 |
gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_EXTDISCO, ejabberd_local). |
40 |
|
|
41 |
|
supported_features() -> |
42 |
4 |
[dynamic_domains]. |
43 |
|
|
44 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
45 |
|
config_spec() -> |
46 |
186 |
#section{items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc(), |
47 |
|
<<"service">> => #list{items = service_config_spec()}}, |
48 |
|
defaults = #{<<"iqdisc">> => no_queue, |
49 |
|
<<"service">> => []}}. |
50 |
|
|
51 |
|
service_config_spec() -> |
52 |
186 |
#section{items = #{<<"type">> => #option{type = atom, |
53 |
|
validate = non_empty}, |
54 |
|
<<"host">> => #option{type = binary, |
55 |
|
validate = non_empty}, |
56 |
|
<<"port">> => #option{type = integer, |
57 |
|
validate = port}, |
58 |
|
<<"transport">> => #option{type = binary, |
59 |
|
validate = {enum, [<<"udp">>, <<"tcp">>]}}, |
60 |
|
<<"username">> => #option{type = binary, |
61 |
|
validate = non_empty}, |
62 |
|
<<"password">> => #option{type = binary, |
63 |
|
validate = non_empty} |
64 |
|
}, |
65 |
|
required = [<<"type">>, <<"host">>]}. |
66 |
|
|
67 |
|
-spec process_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) -> |
68 |
|
{mongoose_acc:t(), jlib:iq()}. |
69 |
|
process_iq(Acc, _From, _To, #iq{type = get, sub_el = SubEl} = IQ, _Extra) -> |
70 |
16 |
HostType = mongoose_acc:host_type(Acc), |
71 |
16 |
{ResponseType, Response} = case request_type(SubEl) of |
72 |
|
all_services -> |
73 |
4 |
RequestedServices = get_external_services(HostType), |
74 |
4 |
{result, create_iq_response(RequestedServices)}; |
75 |
|
{credentials, {Type, Host}} -> |
76 |
2 |
Services = get_external_services(HostType, Type), |
77 |
2 |
RequestedServices = lists:filter(fun(#{host := H}) -> H =:= Host end, Services), |
78 |
2 |
{result, create_iq_response_credentials(RequestedServices)}; |
79 |
|
{selected_services, Type} -> |
80 |
2 |
RequestedServices = get_external_services(HostType, Type), |
81 |
2 |
{result, create_iq_response_services(RequestedServices, Type)}; |
82 |
|
_ -> |
83 |
8 |
{error, [mongoose_xmpp_errors:bad_request()]} |
84 |
|
end, |
85 |
16 |
{Acc, IQ#iq{type = ResponseType, sub_el = Response}}. |
86 |
|
|
87 |
|
request_type(#xmlel{name = <<"services">>} = Element) -> |
88 |
8 |
case exml_query:attr(Element, <<"type">>) of |
89 |
4 |
undefined -> all_services; |
90 |
|
ServiceType -> |
91 |
4 |
case catch binary_to_existing_atom(ServiceType, utf8) of |
92 |
2 |
{'EXIT', _} -> {error, bad_request}; |
93 |
2 |
Type -> {selected_services, Type} |
94 |
|
end |
95 |
|
end; |
96 |
|
request_type(#xmlel{name = <<"credentials">>, children = [Children]}) -> |
97 |
6 |
Host = exml_query:attr(Children, <<"host">>), |
98 |
6 |
case exml_query:attr(Children, <<"type">>) of |
99 |
2 |
undefined -> {error, bad_request}; |
100 |
|
ServiceType -> |
101 |
4 |
case catch binary_to_existing_atom(ServiceType, utf8) of |
102 |
2 |
{'EXIT', _} -> {error, bad_request}; |
103 |
2 |
Type -> {credentials, {Type, Host}} |
104 |
|
end |
105 |
|
end; |
106 |
|
request_type(_) -> |
107 |
2 |
{error, bad_request}. |
108 |
|
|
109 |
|
create_iq_response(Services) -> |
110 |
4 |
#xmlel{name = <<"services">>, |
111 |
|
attrs = [{<<"xmlns">>, ?NS_EXTDISCO}], |
112 |
|
children = prepare_services_element(Services)}. |
113 |
|
|
114 |
|
create_iq_response_services(Services, Type) -> |
115 |
2 |
#xmlel{name = <<"services">>, |
116 |
|
attrs = [{<<"xmlns">>, ?NS_EXTDISCO}, {<<"type">>, atom_to_binary(Type, utf8)}], |
117 |
|
children = prepare_services_element(Services)}. |
118 |
|
|
119 |
|
create_iq_response_credentials(Services) -> |
120 |
2 |
#xmlel{name = <<"credentials">>, |
121 |
|
attrs = [{<<"xmlns">>, ?NS_EXTDISCO}], |
122 |
|
children = prepare_services_element(Services)}. |
123 |
|
|
124 |
|
get_external_services(HostType) -> |
125 |
8 |
gen_mod:get_module_opt(HostType, ?MODULE, service). |
126 |
|
|
127 |
|
get_external_services(HostType, Type) -> |
128 |
4 |
[Service || Service = #{type := T} <- get_external_services(HostType), T =:= Type]. |
129 |
|
|
130 |
|
prepare_services_element(Services) -> |
131 |
8 |
[#xmlel{name = <<"service">>, attrs = make_attrs(Service)} || Service <- Services]. |
132 |
|
|
133 |
|
make_attrs(Service) -> |
134 |
11 |
[{atom_to_binary(Key), format_value(Key, Value)} || {Key, Value} <- maps:to_list(Service)]. |
135 |
|
|
136 |
10 |
format_value(port, Port) -> integer_to_binary(Port); |
137 |
11 |
format_value(type, Type) -> atom_to_binary(Type); |
138 |
41 |
format_value(_, Value) when is_binary(Value) -> Value. |