./ct_report/coverage/mongoose_commands.COVER.html

1 %% @doc Mongoose version of command management
2 %% The following is loosely based on old ejabberd_commands implementation,
3 %% with some modification related to type check, permission control
4 %% and the likes.
5 %%
6 %% This is a central registry of commands which can be exposed via
7 %% REST, XMPP as ad-hoc commands or in any other way. Any module can
8 %% define its commands and register them here.
9 %%
10 %% ==== Usage ====
11 %%
12 %% A module defines a list of commands; a command definition is a proplist
13 %% with the following keys:
14 %% name :: atom()
15 %% name of the command by which we refer to it
16 %% category :: binary()
17 %% this defines what group the command belongs to, like user, chatroom etc
18 %% desc :: binary()
19 %% long description
20 %% module :: module()
21 %% module to call
22 %% function :: atom()
23 %% function to call
24 %% action :: command_action()
25 %% so that the HTTP side can decide which verb to require
26 %% args = [] :: [argspec()]
27 %% Type spec - see below; this is both for introspection and type check on call. Args spec is more
28 %% limited then return, it has to be a list of named arguments, like [{id, integer}, {msg, binary}]
29 %% security_policy = [atom()] (optional)
30 %% permissions required to run this command, defaults to [admin]
31 %% result :: argspec()
32 %% Type spec of return value of the function to call; execute/3 eventually returns {ok, result}
33 %% identifiers :: [atom()] (optional, required in 'update' commands)
34 %% optargs :: [{atom(), type(), term()] (optional args with type and default value.
35 %% Then a command is called, it fills missing arguments with values from here.
36 %% We have then two arities: arity of a command, which is only its required arguments,
37 %% and arity of the function to be called, which is required args + optional args.
38 %%
39 %% You can ignore return value of the target func by specifying return value as {result, ok}. The
40 %% execute/3 will then always return just 'ok' (or error).
41 %%
42 %% If action is 'update' then it MUST specify which args are to be used as identifiers of object
43 %% to update. It has no effect on how the engine does its job, but may be used by client code
44 %% to enforce proper structure of request. (this is bad programming practice but we didn't have
45 %% a better idea, we had to solve it for REST API)
46 %%
47 %% Commands are registered here upon the module's initialisation
48 %% (the module has to explicitly call mongoose_commands:register_commands/1
49 %% func, it doesn't happen automagically), also should be unregistered when module
50 %% terminates.
51 %%
52 %% Commands are executed by calling mongoose_commands:execute/3 method. This
53 %% can return:
54 %% {ok, Result}
55 %% {error, denied, Msg} if user has no permission
56 %% {error, not_implemented, Msg}
57 %% {error, type_error, Msg} if either arguments or return value does not match
58 %% {error, internal, Msg} if an exception was caught
59 %%
60 %% ==== Type check ====
61 %%
62 %% A command's definition includes specification of it arguments; when
63 %% it is called, arguments are check for compatibility. Examples of specs
64 %% and compliant arguments:
65 %%
66 %% ```
67 %% a single type spec
68 %% integer 2
69 %% binary <<"zzz">>
70 %% atom brrr
71 %% a list of arbitrary length, of a given type
72 %% [integer] []
73 %% [integer] [1]
74 %% [integer] [1, 2, 3, 4]
75 %% a list of anything
76 %% []
77 %% a named argument (name is only for clarity)
78 %% {msg, binary} <<"zzz">>
79 %% a tuple of args
80 %% {integer, binary, float} {1, <<"2">>, 3.0}
81 %% a tuple of named args
82 %% {{x, integer}, {y, binary}} {1, <<"bbb">>}
83 %% '''
84 %%
85 %% Arg specification is used at call-time for control, and also for introspection
86 %% (see list/1, list/2, mongoose_commands:get_command/2 and args/1)
87 %%
88 %% Return value is also specified, and this is a bit tricky: command definition
89 %% contains spec of return value - what the target func returns should comply to it.
90 %% The registry, namely execute/3, returns a tuple {ok, ValueReturnedByTheFunction}
91 %% If return value is defined as 'ok' then whatever target func returns is ignored.
92 %% This is mostly to make a distinction between 'create' actions which actually create something
93 %% and return its identifier and those 'lame creators' which cause some action to be done and
94 %% something written to dbase (exemplum: sending a message), but there is no accessible resource.
95 %%
96 %% Called function may also return a tuple {error, term()}, this is returned by the registry
97 %% as {error, internal, Msg::binary()}
98 %%
99 %% ==== Permission control ====
100 %%
101 %% First argument to every function exported from this module is always
102 %% a user. If you call it from trusted place, you can pass 'admin' here and
103 %% the whole permission check is skipped. Otherwise, pass #jid record.
104 %%
105 %% A command MAY define a security policy to be applied
106 %% (and this is not yet designed)
107 %% If it doesn't, then the command is accessible to 'admin' calls only.
108 %%
109
110 -module(mongoose_commands).
111 -author("bartlomiej.gorny@erlang-solutions.com").
112 -include("mongoose.hrl").
113 -include("jlib.hrl").
114
115 -record(mongoose_command,
116 {
117 %% name of the command by which we refer to it
118 name :: atom(),
119 %% groups commands related to the same functionality (user managment, messages/archive)
120 category :: binary(),
121 %% optimal subcategory
122 subcategory = undefined :: undefined | binary(),
123 %% long description
124 desc :: binary(),
125 %% module to call
126 module :: module(),
127 %% function to call
128 function :: atom(),
129 %% so that the HTTP side can decide which verb to require
130 action :: action(),
131 %% this is both for introspection and type check on call
132 args = [] :: [argspec()],
133 %% arg which has a default value and is optional
134 optargs = [] :: [optargspec()],
135 %% internal use
136 caller_pos :: integer(),
137 %% resource identifiers, a subset of args
138 identifiers = [] :: [atom()],
139 %% permissions required to run this command
140 security_policy = [admin] :: security(),
141 %% what the called func should return; if ok then return of called function is ignored
142 result :: argspec()|ok
143 }).
144
145 -opaque t() :: #mongoose_command{}.
146 -type caller() :: admin | binary() | user.
147 -type action() :: create | read | update | delete. %% just basic CRUD; sending a mesage is 'create'
148
149 -type typedef() :: [typedef_basic()] | typedef_basic().
150
151 -type typedef_basic() :: boolean | integer | binary | float. %% most basic primitives, string is a binary
152
153 -type argspec() :: typedef()
154 | {atom(), typedef()} %% a named argument
155 | {argspec()} % a tuple of a few args (can be of any size)
156 | [typedef()]. % a list, but one element
157
158 -type optargspec() :: {atom(), typedef(), term()}. % name, type, default value
159
160 -type security() :: [admin | user]. %% later acl option will be added
161
162 -type errortype() :: denied | not_implemented | bad_request | type_error | not_found | internal.
163 -type errorreason() :: term().
164
165 -type args() :: [{atom(), term()}] | map().
166 -type failure() :: {error, errortype(), errorreason()}.
167 -type success() :: ok | {ok, term()}.
168
169 -export_type([t/0]).
170 -export_type([caller/0]).
171 -export_type([action/0]).
172 -export_type([argspec/0]).
173 -export_type([optargspec/0]).
174 -export_type([errortype/0]).
175 -export_type([errorreason/0]).
176 -export_type([failure/0]).
177
178 -type command_properties() :: [{atom(), term()}].
179
180 %%%% API
181
182 -export([check_type/3]).
183 -export([init/0]).
184
185 -export([register/1,
186 unregister/1,
187 list/1,
188 list/2,
189 list/3,
190 list/4,
191 register_commands/1,
192 unregister_commands/1,
193 new/1,
194 get_command/2,
195 execute/3,
196 name/1,
197 category/1,
198 subcategory/1,
199 desc/1,
200 args/1,
201 optargs/1,
202 arity/1,
203 func_arity/1,
204 identifiers/1,
205 action/1,
206 result/1
207 ]).
208
209 -ignore_xref([check_type/3, func_arity/1, get_command/2, list/3, new/1,
210 register_commands/1, unregister_commands/1, result/1]).
211
212 %% @doc creates new command object based on provided proplist
213 -spec new(command_properties()) -> t().
214 new(Props) ->
215 16670 Fields = record_info(fields, mongoose_command),
216 16670 Lst = check_command([], Props, Fields),
217 16670 RLst = lists:reverse(Lst),
218 16670 Cmd = list_to_tuple([mongoose_command|RLst]),
219 16670 check_identifiers(Cmd#mongoose_command.action,
220 Cmd#mongoose_command.identifiers,
221 Cmd#mongoose_command.args),
222 % store position of caller in args (if present)
223 16670 Cmd#mongoose_command{caller_pos = locate_caller(Cmd#mongoose_command.args)}.
224
225
226 %% @doc Register mongoose commands. This can be run by any module that wants its commands exposed.
227 -spec register([command_properties()]) -> ok.
228 register(Cmds) ->
229 926 Commands = [new(C) || C <- Cmds],
230 926 register_commands(Commands).
231
232 %% @doc Unregister mongoose commands. Should be run when module is unloaded.
233 -spec unregister([command_properties()]) -> ok.
234 unregister(Cmds) ->
235 926 Commands = [new(C) || C <- Cmds],
236 926 unregister_commands(Commands).
237
238 %% @doc List commands, available for this user.
239 -spec list(caller()) -> [t()].
240 list(U) ->
241 230 list(U, any, any, any).
242
243 %% @doc List commands, available for this user, filtered by category.
244 -spec list(caller(), binary() | any) -> [t()].
245 list(U, C) ->
246 2281 list(U, C, any, any).
247
248 %% @doc List commands, available for this user, filtered by category and action.
249 -spec list(caller(), binary() | any, atom()) -> [t()].
250 list(U, Category, Action) ->
251
:-(
list(U, Category, Action, any).
252
253 %% @doc List commands, available for this user, filtered by category, action and subcategory
254 -spec list(caller(), binary() | any, atom(), binary() | any | undefined) -> [t()].
255 list(U, Category, Action, SubCategory) ->
256 2612 CL = command_list(Category, Action, SubCategory),
257 2612 lists:filter(fun(C) -> is_available_for(U, C) end, CL).
258
259 %% @doc Get command definition, if allowed for this user.
260 -spec get_command(caller(), atom()) -> t().
261 get_command(Caller, Name) ->
262
:-(
case ets:lookup(mongoose_commands, Name) of
263 [C] ->
264
:-(
case is_available_for(Caller, C) of
265 true ->
266
:-(
C;
267 false ->
268
:-(
{error, denied, <<"Command not available">>}
269 end;
270
:-(
[] -> {error, not_implemented, <<"Command not implemented">>}
271 end.
272
273 %% accessors
274 -spec name(t()) -> atom().
275 name(Cmd) ->
276 8596 Cmd#mongoose_command.name.
277
278 -spec category(t()) -> binary().
279 category(Cmd) ->
280 14677 Cmd#mongoose_command.category.
281
282 -spec subcategory(t()) -> binary() | undefined.
283 subcategory(Cmd) ->
284 15318 Cmd#mongoose_command.subcategory.
285
286 -spec desc(t()) -> binary().
287 desc(Cmd) ->
288 162 Cmd#mongoose_command.desc.
289
290 -spec args(t()) -> term().
291 args(Cmd) ->
292 3433 Cmd#mongoose_command.args.
293
294 -spec optargs(t()) -> term().
295 optargs(Cmd) ->
296 100 Cmd#mongoose_command.optargs.
297
298 -spec identifiers(t()) -> [atom()].
299 identifiers(Cmd) ->
300 1805 Cmd#mongoose_command.identifiers.
301
302 -spec action(t()) -> action().
303 action(Cmd) ->
304 18790 Cmd#mongoose_command.action.
305
306 -spec result(t()) -> term().
307 result(Cmd) ->
308
:-(
Cmd#mongoose_command.result.
309
310 -spec arity(t()) -> integer().
311 arity(Cmd) ->
312 8517 length(Cmd#mongoose_command.args).
313
314 -spec func_arity(t()) -> integer().
315 func_arity(Cmd) ->
316
:-(
length(Cmd#mongoose_command.args) + length(Cmd#mongoose_command.optargs).
317
318 %% @doc Command execution.
319 -spec execute(caller(), atom() | t(), args()) ->
320 success() | failure().
321 execute(Caller, Name, Args) when is_atom(Name) ->
322 124 case ets:lookup(mongoose_commands, Name) of
323 124 [Command] -> execute_command(Caller, Command, Args);
324
:-(
[] -> {error, not_implemented, {command_not_supported, Name, sizeof(Args)}}
325 end;
326 execute(Caller, #mongoose_command{name = Name}, Args) ->
327
:-(
execute(Caller, Name, Args).
328
329 init() ->
330 80 ets:new(mongoose_commands, [named_table, set, public,
331 {keypos, #mongoose_command.name}]).
332
333 %%%% end of API
334 -spec register_commands([t()]) -> ok.
335 register_commands(Commands) ->
336 926 lists:foreach(
337 fun(Command) ->
338 8335 check_registration(Command), %% may throw
339 8335 ets:insert_new(mongoose_commands, Command),
340 8335 mongoose_hooks:register_command(Command),
341 8335 ok
342 end,
343 Commands).
344
345 -spec unregister_commands([t()]) -> ok.
346 unregister_commands(Commands) ->
347 926 lists:foreach(
348 fun(Command) ->
349 8335 ets:delete_object(mongoose_commands, Command),
350 8335 mongoose_hooks:unregister_command(Command)
351 end,
352 Commands).
353
354 -spec execute_command(caller(), atom() | t(), args()) ->
355 success() | failure().
356 execute_command(Caller, Command, Args) ->
357 124 try check_and_execute(Caller, Command, Args) of
358 ignore_result ->
359 38 ok;
360 {error, Type, Reason} ->
361 29 {error, Type, Reason};
362 {ok, Res} ->
363 56 {ok, Res}
364 catch
365 % admittedly, not the best style of coding, in Erlang at least. But we have to do plenty
366 % of various checks, and in absence of something like Elixir's "with" construct we are
367 % facing a choice between throwing stuff or using some more or less tortured syntax
368 % to chain these checks.
369 throw:{Type, Reason} ->
370 1 {error, Type, Reason};
371 Class:Reason:Stacktrace ->
372
:-(
Err = #{what => command_failed,
373 command_name => Command#mongoose_command.name,
374 caller => Caller, args => Args,
375 class => Class, reason => Reason, stacktrace => Stacktrace},
376
:-(
?LOG_ERROR(Err),
377
:-(
{error, internal, mongoose_lib:term_to_readable_binary(Err)}
378 end.
379
380 add_defaults(Args, Opts) when is_map(Args) ->
381 124 COpts = [{K, V} || {K, _, V} <- Opts],
382 124 Missing = lists:subtract(proplists:get_keys(Opts), maps:keys(Args)),
383 124 lists:foldl(fun(K, Ar) -> maps:put(K, proplists:get_value(K, COpts), Ar) end,
384 Args, Missing).
385
386 % @doc This performs many checks - types, permissions etc, may throw one of many exceptions
387 %% returns what the func returned or just ok if command spec tells so
388 -spec check_and_execute(caller(), t(), args()) -> success() | failure() | ignore_result.
389 check_and_execute(Caller, Command, Args) when is_map(Args) ->
390 124 Args1 = add_defaults(Args, Command#mongoose_command.optargs),
391 124 ArgList = maps_to_list(Args1, Command#mongoose_command.args, Command#mongoose_command.optargs),
392 124 check_and_execute(Caller, Command, ArgList);
393 check_and_execute(Caller, Command, Args) ->
394 % check permissions
395 124 case is_available_for(Caller, Command) of
396 true ->
397 124 ok;
398 false ->
399
:-(
throw({denied, "Command not available for this user"})
400 end,
401 % check caller (if it is given in args, and the engine is called by a 'real' user, then it
402 % must match
403 124 check_caller(Caller, Command, Args),
404 % check args
405 % this is the 'real' spec of command - optional args included
406 124 FullSpec = Command#mongoose_command.args
407
:-(
++ [{K, T} || {K, T, _} <- Command#mongoose_command.optargs],
408 124 SpecLen = length(FullSpec),
409 124 ALen = length(Args),
410 124 case SpecLen =/= ALen of
411 true ->
412
:-(
type_error(argument, "Invalid number of arguments: should be ~p, got ~p", [SpecLen, ALen]);
413 124 _ -> ok
414 end,
415 124 [check_type(argument, S, A) || {S, A} <- lists:zip(FullSpec, Args)],
416 % run command
417 124 Res = apply(Command#mongoose_command.module, Command#mongoose_command.function, Args),
418 124 case Res of
419 {error, Type, Reason} ->
420 29 {error, Type, Reason};
421 _ ->
422 95 case Command#mongoose_command.result of
423 ok ->
424 38 ignore_result;
425 ResSpec ->
426 57 check_type(return, ResSpec, Res),
427 56 {ok, Res}
428 end
429 end.
430
431 check_type(_, ok, _) ->
432
:-(
ok;
433 check_type(_, A, A) ->
434 14 true;
435 check_type(_, {_Name, boolean}, Value) when is_boolean(Value) ->
436 1 true;
437 check_type(Mode, {Name, boolean}, Value) ->
438
:-(
type_error(Mode, "For ~p expected boolean, got ~p", [Name, Value]);
439 check_type(_, {_Name, binary}, Value) when is_binary(Value) ->
440 299 true;
441 check_type(Mode, {Name, binary}, Value) ->
442 1 type_error(Mode, "For ~p expected binary, got ~p", [Name, Value]);
443 check_type(_, {_Name, integer}, Value) when is_integer(Value) ->
444
:-(
true;
445 check_type(Mode, {Name, integer}, Value) ->
446
:-(
type_error(Mode, "For ~p expected integer, got ~p", [Name, Value]);
447 check_type(Mode, {_Name, [_] = LSpec}, Value) when is_list(Value) ->
448 2 check_type(Mode, LSpec, Value);
449 check_type(Mode, Spec, Value) when is_tuple(Spec) and not is_tuple(Value) ->
450
:-(
type_error(Mode, "~p is not a tuple", [Value]);
451 check_type(Mode, Spec, Value) when is_tuple(Spec) ->
452
:-(
compare_tuples(Mode, Spec, Value);
453 check_type(_, [_Spec], []) ->
454 2 true;
455 check_type(Mode, [Spec], [H|T]) ->
456 5 check_type(Mode, {none, Spec}, H),
457 5 check_type(Mode, [Spec], T);
458 check_type(_, [], [_|_]) ->
459 31 true;
460 check_type(_, [], []) ->
461
:-(
true;
462 check_type(Mode, Spec, Value) ->
463
:-(
type_error(Mode, "Catch-all: ~p vs ~p", [Spec, Value]).
464
465 compare_tuples(Mode, Spec, Val) ->
466
:-(
Ssize = tuple_size(Spec),
467
:-(
Vsize = tuple_size(Val),
468
:-(
case Ssize of
469 Vsize ->
470
:-(
compare_lists(Mode, tuple_to_list(Spec), tuple_to_list(Val));
471 _ ->
472
:-(
type_error(Mode, "Tuples of different size: ~p and ~p", [Spec, Val])
473 end.
474
475 compare_lists(_, [], []) ->
476
:-(
true;
477 compare_lists(Mode, [S|Sp], [V|Val]) ->
478
:-(
check_type(Mode, S, V),
479
:-(
compare_lists(Mode, Sp, Val).
480
481 type_error(argument, Fmt, V) ->
482
:-(
throw({type_error, io_lib:format(Fmt, V)});
483 type_error(return, Fmt, V) ->
484 1 throw({internal, io_lib:format(Fmt, V)}).
485
486 check_identifiers(update, [], _) ->
487
:-(
baddef(identifiers, empty);
488 check_identifiers(update, Ids, Args) ->
489 3086 check_identifiers(Ids, Args);
490 check_identifiers(_, _, _) ->
491 13584 ok.
492
493 check_identifiers([], _) ->
494 3086 ok;
495 check_identifiers([H|T], Args) ->
496 4940 case proplists:get_value(H, Args) of
497
:-(
undefined -> baddef(H, missing);
498 4940 _ -> check_identifiers(T, Args)
499 end.
500
501 check_command(Cmd, _PL, []) ->
502 16670 Cmd;
503 check_command(Cmd, PL, [N|Tail]) ->
504 216710 V = proplists:get_value(N, PL),
505 216710 Val = check_value(N, V),
506 216710 check_command([Val|Cmd], PL, Tail).
507
508 check_value(name, V) when is_atom(V) ->
509 16670 V;
510 check_value(category, V) when is_binary(V) ->
511 16670 V;
512 check_value(subcategory, V) when is_binary(V) ->
513 4932 V;
514 check_value(subcategory, undefined) ->
515 11738 undefined;
516 check_value(desc, V) when is_binary(V) ->
517 16670 V;
518 check_value(module, V) when is_atom(V) ->
519 16670 V;
520 check_value(function, V) when is_atom(V) ->
521 16670 V;
522 check_value(action, read) ->
523 3708 read;
524 check_value(action, send) ->
525
:-(
send;
526 check_value(action, create) ->
527 6172 create;
528 check_value(action, update) ->
529 3086 update;
530 check_value(action, delete) ->
531 3704 delete;
532 check_value(args, V) when is_list(V) ->
533 16670 Filtered = [C || {C, _} <- V],
534 16670 ArgCount = length(V),
535 16670 case length(Filtered) of
536 16670 ArgCount -> V;
537
:-(
_ -> baddef(args, V)
538 end;
539 check_value(security_policy, undefined) ->
540 11108 [admin];
541 check_value(security_policy, []) ->
542
:-(
baddef(security_policy, empty);
543 check_value(security_policy, V) when is_list(V) ->
544 5562 lists:map(fun check_security_policy/1, V),
545 5562 V;
546 check_value(result, undefined) ->
547
:-(
baddef(result, undefined);
548 check_value(result, V) ->
549 16670 V;
550 check_value(identifiers, undefined) ->
551 8656 [];
552 check_value(identifiers, V) ->
553 8014 V;
554 check_value(optargs, undefined) ->
555 15434 [];
556 check_value(optargs, V) ->
557 1236 V;
558 check_value(caller_pos, _) ->
559 16670 0;
560 check_value(K, V) ->
561
:-(
baddef(K, V).
562
563 %% @doc Known security policies
564 check_security_policy(user) ->
565 5562 ok;
566 check_security_policy(admin) ->
567
:-(
ok;
568 check_security_policy(Other) ->
569
:-(
baddef(security_policy, Other).
570
571 baddef(K, V) ->
572
:-(
throw({invalid_command_definition, io_lib:format("~p=~p", [K, V])}).
573
574 command_list(Category, Action, SubCategory) ->
575 2612 Cmds = [C || [C] <- ets:match(mongoose_commands, '$1')],
576 2612 filter_commands(Category, Action, SubCategory, Cmds).
577
578 filter_commands(any, any, _, Cmds) ->
579 230 Cmds;
580 filter_commands(Cat, any, _, Cmds) ->
581 2281 [C || C <- Cmds, C#mongoose_command.category == Cat];
582 filter_commands(any, _, _, _) ->
583
:-(
throw({invalid_filter, ""});
584 filter_commands(Cat, Action, any, Cmds) ->
585
:-(
[C || C <- Cmds, C#mongoose_command.category == Cat,
586
:-(
C#mongoose_command.action == Action];
587 filter_commands(Cat, Action, SubCategory, Cmds) ->
588 101 [C || C <- Cmds, C#mongoose_command.category == Cat,
589 459 C#mongoose_command.action == Action,
590 158 C#mongoose_command.subcategory == SubCategory].
591
592
593 %% @doc make sure the command may be registered
594 %% it may not if either (a) command of that name is already registered,
595 %% (b) there is a command in the same category and subcategory with the same action and arity
596 check_registration(Command) ->
597 8335 Name = name(Command),
598 8335 Cat = category(Command),
599 8335 Act = action(Command),
600 8335 Arity = arity(Command),
601 8335 SubCat = subcategory(Command),
602 8335 case ets:lookup(mongoose_commands, Name) of
603 [] ->
604 2179 CatLst = list(admin, Cat),
605 2179 FCatLst = [C || C <- CatLst, action(C) == Act,
606 803 subcategory(C) == SubCat,
607 81 arity(C) == Arity],
608 2179 case FCatLst of
609 2179 [] -> ok;
610 [C] ->
611
:-(
baddef("There is a command ~p in category ~p and subcategory ~p, action ~p",
612 [name(C), Cat, SubCat, Act])
613 end;
614 Other ->
615 6156 ?LOG_DEBUG(#{what => command_conflict,
616 text => <<"This command is already defined">>,
617 6156 command_name => Name, registered => Other}),
618 6156 ok
619 end.
620
621 mapget(K, Map) ->
622 286 try maps:get(K, Map) of
623 286 V -> V
624 catch
625 error:{badkey, K} ->
626
:-(
type_error(argument, "Missing argument: ~p", [K]);
627 error:bad_key ->
628
:-(
type_error(argument, "Missing argument: ~p", [K])
629 end.
630
631 maps_to_list(Map, Args, Optargs) ->
632 124 SpecLen = length(Args) + length(Optargs),
633 124 ALen = maps:size(Map),
634 124 case SpecLen of
635 124 ALen -> ok;
636
:-(
_ -> type_error(argument, "Invalid number of arguments: should be ~p, got ~p", [SpecLen, ALen])
637 end,
638 124 [mapget(K, Map) || {K, _} <- Args] ++ [mapget(K, Map) || {K, _, _} <- Optargs].
639
640 %% @doc Main entry point for permission control - is this command available for this user
641 is_available_for(User, C) when is_binary(User) ->
642 25 is_available_for(jid:from_binary(User), C);
643 is_available_for(admin, _C) ->
644 10331 true;
645 is_available_for(Jid, #mongoose_command{security_policy = Policies}) ->
646 25 apply_policies(Policies, Jid).
647
648 %% @doc Check all security policies defined in the command - passes if any of them returns true
649 apply_policies([], _) ->
650
:-(
false;
651 apply_policies([P|Policies], Jid) ->
652 25 case apply_policy(P, Jid) of
653 true ->
654 25 true;
655 false ->
656
:-(
apply_policies(Policies, Jid)
657 end.
658
659 %% @doc This is the only policy we know so far, but there will be others (like roles/acl control)
660 apply_policy(user, _) ->
661 25 true;
662 apply_policy(_, _) ->
663
:-(
false.
664
665 locate_caller(L) ->
666 16670 locate_caller(1, L).
667
668 locate_caller(_I, []) ->
669 11108 0;
670 locate_caller(I, [{caller, _}|_]) ->
671 5562 I;
672 locate_caller(I, [_|T]) ->
673 33912 locate_caller(I + 1, T).
674
675 check_caller(admin, _Command, _Args) ->
676 99 ok;
677 check_caller(_Caller, #mongoose_command{caller_pos = 0}, _Args) ->
678 % no caller in args
679
:-(
ok;
680 check_caller(Caller, #mongoose_command{caller_pos = CallerPos}, Args) ->
681 % check that server and user match (we don't care about resource)
682 25 ACaller = lists:nth(CallerPos, Args),
683 25 CallerJid = jid:from_binary(Caller),
684 25 ACallerJid = jid:from_binary(ACaller),
685 25 ACal = {ACallerJid#jid.user, ACallerJid#jid.server},
686 25 case {CallerJid#jid.user, CallerJid#jid.server} of
687 ACal ->
688 25 ok;
689 _ ->
690
:-(
throw({denied, "Caller ids do not match"})
691 end.
692
693
:-(
sizeof(#{} = M) -> maps:size(M);
694
:-(
sizeof([_|_] = L) -> length(L).
Line Hits Source