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, "0.7"}]). |
20 |
|
-behaviour(gen_mod). |
21 |
|
|
22 |
|
%% gen_mod callbacks. |
23 |
|
-export([start/2, stop/1, config_spec/0]). |
24 |
|
|
25 |
|
-export([process_iq/4]). |
26 |
|
|
27 |
|
-ignore_xref([process_iq/4]). |
28 |
|
|
29 |
|
-include("jlib.hrl"). |
30 |
|
-include("mongoose.hrl"). |
31 |
|
-include("mongoose_config_spec.hrl"). |
32 |
|
|
33 |
|
-spec start(jid:server(), list()) -> ok. |
34 |
|
start(Host, Opts) -> |
35 |
:-( |
IQDisc = gen_mod:get_opt(iqdisc, Opts, no_queue), |
36 |
:-( |
gen_iq_handler:add_iq_handler(ejabberd_local, Host, |
37 |
|
?NS_EXTDISCO, ?MODULE, process_iq, IQDisc). |
38 |
|
|
39 |
|
-spec stop(jid:server()) -> ok. |
40 |
|
stop(Host) -> |
41 |
:-( |
gen_iq_handler:remove_iq_handler(ejabberd_local, Host, ?NS_EXTDISCO). |
42 |
|
|
43 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
44 |
|
config_spec() -> |
45 |
146 |
#section{ |
46 |
|
items = #{<<"service">> => #list{items = service_config_spec(), |
47 |
|
wrap = none}} |
48 |
|
}. |
49 |
|
|
50 |
|
service_config_spec() -> |
51 |
146 |
#section{ |
52 |
|
items = #{<<"type">> => #option{type = atom, |
53 |
|
validate = non_empty}, |
54 |
|
<<"host">> => #option{type = string, |
55 |
|
validate = non_empty}, |
56 |
|
<<"port">> => #option{type = integer, |
57 |
|
validate = port}, |
58 |
|
<<"transport">> => #option{type = string, |
59 |
|
validate = {enum, ["udp", "tcp"]}}, |
60 |
|
<<"username">> => #option{type = string, |
61 |
|
validate = non_empty}, |
62 |
|
<<"password">> => #option{type = string, |
63 |
|
validate = non_empty} |
64 |
|
}, |
65 |
|
required = [<<"type">>, <<"host">>] |
66 |
|
}. |
67 |
|
|
68 |
|
-spec process_iq(jid:jid(), jid:jid(), mongoose_acc:t(), jlib:iq()) -> |
69 |
|
{mongoose_acc:t(), jlib:iq()}. |
70 |
|
process_iq(_From, _To = #jid{lserver = LServer}, Acc, #iq{type = get, sub_el = SubEl} = IQ) -> |
71 |
:-( |
{ResponseType, Response} = case request_type(SubEl) of |
72 |
|
all_services -> |
73 |
:-( |
RequestedServices = get_external_services(LServer), |
74 |
:-( |
{result, create_iq_response(RequestedServices)}; |
75 |
|
{credentials, {Type, Host}} -> |
76 |
:-( |
Services = get_external_services(LServer, Type), |
77 |
:-( |
RequestedServices = lists:filter(fun(Opts) -> |
78 |
:-( |
gen_mod:get_opt(host, Opts, undefined) == binary_to_list(Host) |
79 |
|
end, Services), |
80 |
:-( |
{result, create_iq_response_credentials(RequestedServices)}; |
81 |
|
{selected_services, Type} -> |
82 |
:-( |
RequestedServices = get_external_services(LServer, Type), |
83 |
:-( |
{result, create_iq_response_services(RequestedServices, Type)}; |
84 |
|
_ -> |
85 |
:-( |
{error, [mongoose_xmpp_errors:bad_request()]} |
86 |
|
end, |
87 |
:-( |
{Acc, IQ#iq{type = ResponseType, sub_el = Response}}. |
88 |
|
|
89 |
|
request_type(#xmlel{name = <<"services">>} = Element) -> |
90 |
:-( |
case exml_query:attr(Element, <<"type">>) of |
91 |
:-( |
undefined -> all_services; |
92 |
|
ServiceType -> |
93 |
:-( |
case catch binary_to_existing_atom(ServiceType, utf8) of |
94 |
:-( |
{'EXIT', _} -> {error, bad_request}; |
95 |
:-( |
Type -> {selected_services, Type} |
96 |
|
end |
97 |
|
end; |
98 |
|
request_type(#xmlel{name = <<"credentials">>, children = [Children]}) -> |
99 |
:-( |
Host = exml_query:attr(Children, <<"host">>), |
100 |
:-( |
case exml_query:attr(Children, <<"type">>) of |
101 |
:-( |
undefined -> {error, bad_request}; |
102 |
|
ServiceType -> |
103 |
:-( |
case catch binary_to_existing_atom(ServiceType, utf8) of |
104 |
:-( |
{'EXIT', _} -> {error, bad_request}; |
105 |
:-( |
Type -> {credentials, {Type, Host}} |
106 |
|
end |
107 |
|
end; |
108 |
|
request_type(_) -> |
109 |
:-( |
{error, bad_request}. |
110 |
|
|
111 |
|
create_iq_response(Services) -> |
112 |
:-( |
#xmlel{name = <<"services">>, |
113 |
|
attrs = [{<<"xmlns">>, ?NS_EXTDISCO}], |
114 |
|
children = prepare_services_element(Services)}. |
115 |
|
|
116 |
|
create_iq_response_services(Services, Type) -> |
117 |
:-( |
#xmlel{name = <<"services">>, |
118 |
|
attrs = [{<<"xmlns">>, ?NS_EXTDISCO}, {<<"type">>, atom_to_binary(Type, utf8)}], |
119 |
|
children = prepare_services_element(Services)}. |
120 |
|
|
121 |
|
create_iq_response_credentials(Services) -> |
122 |
:-( |
#xmlel{name = <<"credentials">>, |
123 |
|
attrs = [{<<"xmlns">>, ?NS_EXTDISCO}], |
124 |
|
children = prepare_services_element(Services)}. |
125 |
|
|
126 |
|
get_external_services(LServer) -> |
127 |
:-( |
case gen_mod:get_module_opts(LServer, ?MODULE) of |
128 |
:-( |
[] -> []; |
129 |
:-( |
ServicesWithOpts -> ServicesWithOpts |
130 |
|
end. |
131 |
|
|
132 |
|
get_external_services(LServer, Type) -> |
133 |
:-( |
[Opts || Opts <- get_external_services(LServer), gen_mod:get_opt(type, Opts) == Type]. |
134 |
|
|
135 |
|
prepare_services_element(Services) -> |
136 |
:-( |
lists:reverse( |
137 |
|
lists:foldl( |
138 |
|
fun(Opts, Acc) -> |
139 |
:-( |
RequiredElements = required_elements(Opts), |
140 |
:-( |
OptionalElements = optional_elements(Opts), |
141 |
:-( |
NewResult = #xmlel{name = <<"service">>, |
142 |
|
attrs = RequiredElements ++ OptionalElements}, |
143 |
:-( |
[NewResult | Acc] |
144 |
|
end, [], Services)). |
145 |
|
|
146 |
|
required_elements(Opts) -> |
147 |
:-( |
Host = gen_mod:get_opt(host, Opts, <<"">>), |
148 |
:-( |
Type = gen_mod:get_opt(type, Opts), |
149 |
:-( |
[{<<"type">>, atom_to_binary(Type, utf8)}, {<<"host">>, Host}]. |
150 |
|
|
151 |
|
optional_elements(Opts) -> |
152 |
:-( |
Port = gen_mod:get_opt(port, Opts, undefined), |
153 |
:-( |
Transport = gen_mod:get_opt(transport, Opts, undefined), |
154 |
:-( |
Password = gen_mod:get_opt(password, Opts, undefined), |
155 |
:-( |
Username = gen_mod:get_opt(username, Opts, undefined), |
156 |
:-( |
Elements = [{<<"port">>, i2b(Port)}, |
157 |
|
{<<"transport">>, Transport}, |
158 |
|
{<<"password">>, Password}, |
159 |
|
{<<"username">>, Username}], |
160 |
:-( |
filter_undefined_elements(Elements). |
161 |
|
|
162 |
|
filter_undefined_elements(Elements) -> |
163 |
:-( |
lists:filter(fun({_, undefined}) -> false; |
164 |
:-( |
(_) -> true |
165 |
|
end, Elements). |
166 |
|
|
167 |
:-( |
i2b(X) when is_integer(X) -> integer_to_binary(X); |
168 |
:-( |
i2b(X) -> X. |