25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-10-31 15:21:38 +01:00
xmpp.chapril.org-ejabberd/mod_pubsub_ng/pubsub_tools.erl

686 lines
22 KiB
Erlang

%%% ====================================================================
%%% ``The contents of this file are subject to the Erlang Public License,
%%% Version 1.1, (the "License"); you may not use this file except in
%%% compliance with the License. You should have received a copy of the
%%% Erlang Public License along with this software. If not, it can be
%%% retrieved via the world wide web at http://www.erlang.org/.
%%%
%%% Software distributed under the License is distributed on an "AS IS"
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%%% the License for the specific language governing rights and limitations
%%% under the License.
%%%
%%% The Initial Developer of the Original Code is ProcessOne.
%%% Portions created by ProcessOne are Copyright 2006-2013, ProcessOne
%%% All Rights Reserved.''
%%% This software is copyright 2006-2013, ProcessOne.
%%%
%%% @copyright 2006-2013 ProcessOne
%%% @author Karim Gemayel <karim.gemayel@process-one.net>
%%% [http://www.process-one.net/]
%%% @version {@vsn}, {@date} {@time}
%%% @end
%%% ====================================================================
%%% @headerfile "pubsub_dev.hrl"
-module(pubsub_tools).
-author('karim.gemayel@process-one.net').
-compile(export_all).
-include("pubsub_dev.hrl").
jid_to_string({undefined, S, undefined}) ->
jlib:jid_to_string({<<>>, S, <<>>});
jid_to_string({undefined, S, R}) ->
jlib:jid_to_string({<<>>, S, R});
jid_to_string({U, S, undefined}) ->
jlib:jid_to_string({U, S, <<>>});
jid_to_string(USR) ->
jlib:jid_to_string(USR).
make_jid({undefined, S, undefined}) ->
jlib:make_jid(<<>>, S, <<>>);
make_jid({U, S, undefined}) ->
jlib:make_jid({U, S, <<>>});
make_jid(Entity) ->
jlib:make_jid(Entity).
%% @doc Determine if data is a JID
-spec(is_jid/1 ::
(
Data :: binary() | undefined)
-> xmpp_jid:entity() | {error, 'jid-malformed'}
).
is_jid(Data) ->
try jlib:string_to_jid(Data) of
#jid{} = Jid -> Jid
catch
_Error -> {error, 'jid-malformed'}
end.
%%%
%-spec(get_value/2 ::
%(
% Options :: {Node_Options :: pubsub_options:options_node(),
% Item_Options :: [] | pubsub_options:options_item()}
% | pubsub_options:options_item()
% | pubsub_options:options_node()
% | pubsub_options:options_subscription()
% | [],
% Key :: atom())
% -> Value :: atom()
% | binary()
% | boolean()
% | non_neg_integer()
% | undefined
% | []
% | 'none'
% | [atom(),...]
% | [binary(),...]
% | [boolean(),...]
% | [non_neg_integer(),...]
%).
get_value({Node_Options, [] = _Item_Options}, Key) ->
get_value(Node_Options, Key, 0);
%%
get_value({Node_Options, Item_Options}, Key) ->
case get_value(Item_Options, Key, 'none') of
'none' -> get_value(Node_Options, Key, false);
Value -> Value
end;
%%
get_value([], _Key) -> 'none';
%%
get_value(Options, Key) ->
get_value(Options, Key, 'none').
%%%
%-spec(get_value/3 ::
%(
% Options :: {Node_Options :: pubsub_options:options_node(),
% Item_Options :: [] | pubsub_options:options_item()}
% | pubsub_options:options_item()
% | pubsub_options:options_node()
% | pubsub_options:options_subscription(),
% Key :: atom(),
% Default :: term())
% -> Value :: atom()
% | binary()
% | boolean()
% | non_neg_integer()
% | undefined
% | []
% | [atom(),...]
% | [binary(),...]
% | [boolean(),...]
% | [non_neg_integer(),...]
%).
get_value({Node_Options, [] = _Item_Options}, Key, Default) ->
get_value(Node_Options, Key, Default);
%%
get_value({Node_Options, Item_Options}, Key, Default) ->
case get_value(Item_Options, Key, 'none') of
'none' -> get_value(Node_Options, Key, Default);
Value -> Value
end;
%%
get_value(Options, Key, Default) ->
case lists:keyfind(Key, 1, Options) of
{_Key, Value} -> Value;
false -> Default
end.
%%%
%-spec(set_value/3 ::
%(
% Options :: pubsub_options:options_item()
% | pubsub_options:options_node()
% | pubsub_options:options_subscription(),
% Key :: atom(),
% Value :: atom()
% | binary()
% | boolean()
% | non_neg_integer()
% | undefined
% | []
% | [atom(),...]
% | [binary(),...]
% | [boolean(),...]
% | [non_neg_integer(),...])
% -> Options :: pubsub_options:options_item()
% | pubsub_options:options_node()
% | pubsub_options:options_subscription()
%).
set_value(Options, Key, Value) ->
lists:keyreplace(Key, 1, Options, {Key, Value}).
%%
%-spec(get_option/2 ::
%(
% Options :: {Node_Options :: pubsub_options:options_node(),
% Item_Options :: [] | pubsub_options:options_item()}
% | pubsub_options:options_item()
% | pubsub_options:options_node()
% | pubsub_options:options_subscription()
% | [],
% Key :: atom())
% -> Option :: pubsub_options:option_item()
% | pubsub_options:option_node()
% | pubsub_options:option_subscription()
% | 'none'
%).
get_option({Node_Options, [] = _Item_Options}, Key) ->
get_option(Node_Options, Key, 'none');
%%
get_option({Node_Options, Item_Options}, Key) ->
case get_option(Item_Options, Key, 'none') of
'none' -> get_option(Node_Options, Key, 'none');
Option -> Option
end;
%%
get_option([], _Key) -> 'none';
%%
get_option(Options, Key) ->
get_option(Options, Key, 'none').
%%%
%-spec(get_option/3 ::
%(
% Options :: {Node_Options :: pubsub_options:options_node(),
% Item_Options :: [] | pubsub_options:options_item()}
% | pubsub_options:options_item()
% | pubsub_options:options_node()
% | pubsub_options:options_subscription(),
% Key :: atom(),
% Default :: term())
% -> Option :: pubsub_options:option_item()
% | pubsub_options:option_node()
% | pubsub_options:option_subscription()
% | term()
%).
get_option({Node_Options, [] = _Item_Options}, Key, Default) ->
get_option(Node_Options, Key, Default);
%%
get_option({Node_Options, Item_Options}, Key, Default) ->
case get_option(Item_Options, Key, 'none') of
'none' -> get_option(Node_Options, Key, Default);
Option -> Option
end;
%%
get_option(Options, Key, Default) ->
case lists:keyfind(Key, 1, Options) of
{_Key, Value} -> _Option = {Key, Value};
false -> Default
end.
%%
-spec(get_entity_roster/1 ::
(
Entity :: xmpp_jid:usr_entity()
| xmpp_jid:entity())
-> Roster::[Roster_Item::#roster{}]
).
get_entity_roster({U,S, _R} = _USR_Entity) ->
_Roster = ejabberd_hooks:run_fold(roster_get, S, [], [{U,S}]);
get_entity_roster(#jid{luser = U, lserver = S} = _Jid_Entity) ->
_Roster = get_entity_roster({U,S, undefined}).
%%
-spec(check_access_model/3 ::
(
Host :: xmpp_jid:raw_jid_component_bare(),
Entity :: xmpp_jid:usr_entity(),
Criteria :: {Access_Model :: pubsub_options:access_model(),
Affiliation :: exmpp_pubsub:affiliation(),
Subscriptions :: exmpp_pubsub:subscriptions(),
Node_Owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
Rosters_Groups_Allowed :: pubsub_options:rosters_groups_allowed()})
-> ok
%%%
| {error, 'forbidden'}
).
check_access_model(_Host, _Entity,
{_Access_Model, 'outcast' = _Affiliation, _Subscriptions, _Node_Owners,
_Rosters_Groups_Allowed}) ->
{error, 'forbidden'};
%%
check_access_model(_Host, _Entity,
{'open' = _Access_Model, _Affiliation, _Subscriptions, _Node_Owners,
_Rosters_Groups_Allowed}) ->
ok;
%%
check_access_model(Host, Entity,
{'presence' = _Access_Model, _Affiliation, _Subscriptions, Node_Owners,
_Rosters_Groups_Allowed}) ->
case is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners) of
false ->
{error, 'forbidden'};
_Node_Owner ->
ok
end;
%%
check_access_model(_Host, Entity,
{'roster' = _Access_Model, _Affiliation, _Subscriptions, _Node_Owners,
Rosters_Groups_Allowed}) ->
case is_contact_in_allowed_roster_groups(Entity, Rosters_Groups_Allowed) of
false ->
{error, 'forbidden'};
{_Node_Owner, _Roster_Group_Allowed} ->
ok
end;
%%
check_access_model(_Host, _Entity,
{'whitelist' = _Access_Model, Affiliation, _Subscriptions, _Node_Owners,
_Roster_Groups_Allowed})
when Affiliation == 'member'
%%orelse Affiliation == 'owner'
orelse Affiliation == 'publish-only'
orelse Affiliation == 'publisher' ->
ok;
check_access_model(_Host, _Entity,
{'whitelist' = _Access_Model, _Affiliation, _Subscriptions, _Node_Owners,
_Roster_Groups_Allowed}) ->
{error, 'forbidden'};
%%
check_access_model(_Host, _Entity,
{'authorize' = _Access_Model, _Affiliation, Subscriptions, _Node_Owners,
_Roster_Groups_Allowed}) ->
case has_subscriptions(Subscriptions) of
true -> ok;
false -> {error, 'forbidden'}
end.
%%
-spec(check_access_model/7 ::
(
Host :: xmpp_jid:raw_jid_component_bare(),
Entity :: xmpp_jid:usr_entity(),
Access_Model :: pubsub_options:access_model(),
Affiliation :: exmpp_pubsub:affiliation(),
Subscriptions :: exmpp_pubsub:subscriptions(),
Node_Owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
Rosters_Groups_Allowed :: pubsub_options:rosters_groups_allowed())
-> ok
%%%
| {error, 'forbidden'}
| {error, 'item-not-found'}
).
check_access_model(_Host, _Entity, _Access_Model, 'owner' = _Affiliation,
_Subscriptions, _Node_Owners, _Rosters_Groups_Allowed) ->
ok;
%%
check_access_model(Host, Entity, Access_Model, 'outcast' = _Affiliation,
_Subscriptions, Node_Owners, Rosters_Groups_Allowed) ->
case Access_Model of
%%
'open'->
{error, 'forbidden'};
%%
'presence' ->
case
is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners)
of
false ->
{error, 'item-not-found'};
_Node_Owner ->
{error, 'forbidden'}
end;
%%
'roster' ->
case
is_contact_in_allowed_roster_groups(Entity,
Rosters_Groups_Allowed)
of
false ->
{error, 'item-not-found'};
{_Node_Owner, _Roster_Group_Allowed} ->
{error, 'forbidden'}
end;
%%
'authorize' ->
{error, 'forbidden'};
%%
'whitelist' ->
{error, 'item-not-found'}
end;
%%
check_access_model(_Host, _Entity, 'open' = _Access_Model, _Affiliation,
_Subscriptions, _Node_Owners, _Rosters_Groups_Allowed) ->
ok;
%%
check_access_model(Host, Entity, 'presence' = _Access_Model, _Affiliation,
_Subscriptions, Node_Owners, _Rosters_Groups_Allowed) ->
case is_contact_subscribed_to_node_owners(Host, Entity, Node_Owners) of
false ->
{error, 'item-not-found'};
_Node_Owner ->
ok
end;
%%
check_access_model(_Host, Entity, 'roster' = _Access_Model, _Affiliation,
_Subscriptions, _Node_Owners, Rosters_Groups_Allowed) ->
case is_contact_in_allowed_roster_groups(Entity, Rosters_Groups_Allowed) of
false ->
{error, 'item-not-found'};
{_Node_Owner, _Roster_Group_Allowed} ->
ok
end;
%%
check_access_model(_Host, _Entity, 'whitelist' = _Access_Model, Affiliation,
_Subscriptions, _Node_Owners, _Roster_Groups_Allowed) ->
case Affiliation of
Affiliation
when Affiliation == 'member'
orelse Affiliation == 'publish-only'
orelse Affiliation == 'publisher' ->
ok;
_Affiliation ->
{error, 'item-not-found'}
end;
%%
check_access_model(_Host, _Entity, 'authorize' = _Access_Model, _Affiliation,
Subscriptions, _Node_Owners, _Roster_Groups_Allowed) ->
case has_subscriptions(Subscriptions) of
true ->
ok;
false ->
{error, 'item-not-found'}
end.
-spec(check_publish_model/3 ::
(
Publish_Model :: pubsub_options:publish_model(),
Affiliation :: exmpp_pubsub:affiliation(),
Subscriptions :: exmpp_pubsub:subscriptions())
-> ok
%%%
| {error, 'forbidden'}
).
check_publish_model('open' = _Publish_Model, _Affiliation, _Subscriptions) ->
ok;
%%
check_publish_model('publishers' = _Publish_Model, Affiliation, _Subscriptions) ->
case Affiliation of
Affiliation
when Affiliation == 'owner'
orelse Affiliation == 'publish-only'
orelse Affiliation == 'publisher' ->
ok;
_Affiliation ->
{error, 'forbidden'}
end;
%%
check_publish_model('subscribers' = _Publish_Model, Affiliation, Subscriptions) ->
case Affiliation of
'owner' ->
ok;
_Affiliation ->
case has_subscriptions(Subscriptions) of
true ->
ok;
false ->
{error, 'forbidden'}
end
end.
%%
-spec(has_subscriptions/1 ::
(
Subscriptions :: exmpp_pubsub:subscriptions())
-> Has_Subscriptions::boolean()
).
has_subscriptions([] = _Subscriptions) ->
false;
has_subscriptions(
[{'pending' = _Subscription_State, _SubId, _Resource, _Subscription_Options}]) ->
false;
has_subscriptions(_Subscriptions) ->
true.
%%
%% @doc Check if a contact is subscribed to at least one local node owner
%% amongst a list of (remote and local) node owners
-spec(is_contact_subscribed_to_node_owners/3 ::
(
Host :: xmpp_jid:raw_jid_component_bare(),
Contact :: xmpp_jid:usr_entity(),
Node_Owners :: [] | [Node_Owner::xmpp_jid:usr_bare(),...])
-> Is_Contact_Subscribed_To_Node_Owners :: false | xmpp_jid:usr_bare()
).
is_contact_subscribed_to_node_owners(_Host, _Contact, [] = _Node_Owners) ->
false;
%% The node owner is a local entity, check if the contact is subscribed to it
is_contact_subscribed_to_node_owners(Host, Contact,
[{_U, Host, _R} = Local_Node_Owner | Node_Owners] = _Node_Owners) ->
_Is_Contact_Subscribed_To_Node_Owners = case
is_contact_subscribed_to_entity(Contact,
_Local_Node_Owner_Roster = get_entity_roster(Local_Node_Owner))
of
true ->
Local_Node_Owner;
false ->
is_contact_subscribed_to_node_owners(Host, Contact, Node_Owners)
end;
%% The node owner is a remote entity, don't check if the contact is subscribed to it
is_contact_subscribed_to_node_owners(Host, Contact,
[_Remote_Node_Owner | Node_Owners] = _Node_Owners) ->
is_contact_subscribed_to_node_owners(Host, Contact, Node_Owners).
%%
%% @doc Check if an entity is in a #roster{}
%% with #roster.subscription == 'from' or
%% with #roster.subscription == 'both'
-spec(is_contact_subscribed_to_entity/2 ::
(
Contact :: xmpp_jid:usr_entity(),
Entity_Roster :: [] | [Roster_Item::#roster{},...])
-> Is_Contact_Subscribed::boolean()
).
is_contact_subscribed_to_entity(_Contact, [] = _Entity_Roster) ->
_Is_Contact_Subscribed = false;
%% Contact is included in a #roster{} item with
%% a subscription of type 'from' and 'both' (i.e. is subscribed to it)
is_contact_subscribed_to_entity({U, S, _R} = _Contact,
[#roster{jid = {U, S, _}, subscription = Subscription} = _Roster_Item
| _Roster_Items] = _Entity_Roster)
when Subscription == 'from' orelse Subscription == 'both' ->
_Is_Contact_Subscribed = true;
%% Contact is not included in a #roster{} item
%% or has a subscription of type different than 'from' and 'both'
%% ( i.e. is not subscribed to it),
%% check next roster items
is_contact_subscribed_to_entity(Contact,
[_Roster_Item | Roster_Items] = _Entity_Roster) ->
_Is_Contact_Subscribed = is_contact_subscribed_to_entity(Contact,
Roster_Items).
%%
-spec(is_contact_in_allowed_roster_groups/2 ::
(
Contact :: xmpp_jid:usr_entity(),
Rosters_Groups_Allowed :: pubsub_options:rosters_groups_allowed())
-> Roster_Group_Allowed :: false
| {Entity :: xmpp_jid:usr_bare(),
Roster_Group :: pubsub_options:roster_group()}
).
is_contact_in_allowed_roster_groups(_Contact, [] = _Rosters_Groups_Allowed) ->
false;
%%
is_contact_in_allowed_roster_groups(Contact,
[{Entity, Entity_Roster_Groups_Allowed} | Rosters_Groups_Allowed]
= _Rosters_Groups_Allowed) ->
case
is_contact_in_allowed_roster_group(Contact,
_Entity_Roster = get_entity_roster(Entity),
Entity_Roster_Groups_Allowed)
of
false ->
is_contact_in_allowed_roster_groups(Contact, Rosters_Groups_Allowed);
Roster_Group ->
{Entity, Roster_Group}
end.
%%
-spec(is_contact_in_allowed_roster_group/3 ::
(
Contact :: xmpp_jid:usr_entity(),
Entity_Roster :: [] | [Roster_Item::#roster{groups::pubsub_options:roster_groups()}],
Roster_Groups_Allowed :: [Roster_Group_Allowed::pubsub_options:roster_group(),...])
-> Roster_Group :: false | pubsub_options:roster_group()
).
is_contact_in_allowed_roster_group(_Contact, [] = _Entity_Roster,
_Roster_Groups_Allowed) ->
false;
%%
is_contact_in_allowed_roster_group({U, S, _R} = _Contact,
[#roster{jid = {U, S, _}, subscription = Subscription, groups = Roster_Groups}
| _Other_Roster_Items] = _Entity_Roster, Roster_Groups_Allowed)
when Subscription == 'from' orelse Subscription == 'both' ->
is_contact_in_allowed_roster_group(Roster_Groups, Roster_Groups_Allowed);
%%
is_contact_in_allowed_roster_group(Contact,
[_Roster_Item | Roster_Items] = _Entity_Roster, Roster_Groups_Allowed) ->
is_contact_in_allowed_roster_group(Contact, Roster_Items,
Roster_Groups_Allowed).
%%
-spec(is_contact_in_allowed_roster_group/2 ::
(
Roster_Groups :: [] | [Roster_Group::pubsub_options:roster_group()],
Roster_Groups_Allowed :: [Roster_Group_Allowed::pubsub_options:roster_group(),...])
-> Roster_Group :: false | pubsub_options:roster_group()
).
is_contact_in_allowed_roster_group([] = _Roster_Groups, _Roster_Groups_Allowed) ->
false;
%%
is_contact_in_allowed_roster_group([Roster_Group | Roster_Groups]
= _Roster_Groups, Roster_Groups_Allowed) ->
case lists:member(Roster_Group, Roster_Groups_Allowed) of
true ->
Roster_Group;
false ->
is_contact_in_allowed_roster_group(Roster_Groups,
Roster_Groups_Allowed)
end.
%%
-spec(get_user_resources/2 ::
(
User :: binary(),
Server :: binary())
-> Resources::[Resource::xmpp_jid:resource_jid()]
).
get_user_resources(User, Server) ->
ejabberd_sm:get_user_resources(User, Server).
%%
-spec(get_resources_show/2 ::
(
User :: binary(),
Server :: binary())
-> Resources_Show :: [{Resource :: xmpp_jid:resource_jid(),
Show :: 'away' | 'chat' | 'dnd' | 'online' | 'xa' }]
).
get_resources_show(User, Server) ->
_Resources_Show = lists:foldl(fun
(Resource, Resources_Show) ->
case ejabberd_sm:get_session_pid(User, Server, Resource) of
C2SPid when is_pid(C2SPid) ->
case ejabberd_c2s:get_presence(C2SPid) of
{_User, _Resource, Show, _} ->
[{Resource, list_to_atom(Show)} | Resources_Show];
_ ->
Resources_Show
end;
_ ->
Resources_Show
end
end, [], _Resources = ejabberd_sm:get_user_resources(User, Server)).
%%
-spec(rosters_groups_allowed_cache/2 ::
(
Rosters_Groups_Allowed :: pubsub_options:rosters_groups_allowed(),
Rosters_Groups_Allowed_Cache :: pubsub_options:rosters_groups_allowed())
-> Is_Roster_Groups_Cached::boolean()
).
rosters_groups_allowed_cache(_Rosters_Groups_Allowed,
[] = _Rosters_Groups_Allowed_Cache) ->
false;
%%
rosters_groups_allowed_cache([] = _Rosters_Groups_Allowed,
_Rosters_Groups_Allowed_Cache) ->
false;
%%
rosters_groups_allowed_cache(
[{Entity, Roster_Groups_Allowed} | Rosters_Groups_Allowed],
Rosters_Groups_Allowed_Cache) ->
case lists:keyfind(Entity, 1, Rosters_Groups_Allowed_Cache) of
{_Entity, Roster_Groups_Allowed_Cache} ->
case
roster_groups_allowed_cache(Roster_Groups_Allowed,
Roster_Groups_Allowed_Cache)
of
true ->
true;
false ->
rosters_groups_allowed_cache(Rosters_Groups_Allowed,
Rosters_Groups_Allowed_Cache)
end;
false ->
rosters_groups_allowed_cache(Rosters_Groups_Allowed,
Rosters_Groups_Allowed_Cache)
end.
%%
-spec(roster_groups_allowed_cache/2 ::
(
Roster_Groups_Allowed :: [Roster_Group_Allowed::pubsub_options:roster_group()],
Roster_Groups_Allowed_Cache :: [Roster_Group_Allowed_Cache::pubsub_options:roster_group()])
-> Is_Roster_Groups_Cached :: boolean()
).
roster_groups_allowed_cache(_Roster_Groups_Allowed,
[] = _Roster_Groups_Allowed_Cache) ->
false;
%%
roster_groups_allowed_cache([] = _Roster_Groups_Allowed,
_Roster_Groups_Allowed_Cache) ->
false;
%%
roster_groups_allowed_cache([Roster_Group_Allowed | Roster_Groups_Allowed],
Roster_Groups_Allowed_Cache) ->
case lists:member(Roster_Group_Allowed, Roster_Groups_Allowed_Cache) of
true ->
true;
false ->
roster_groups_allowed_cache(Roster_Groups_Allowed,
Roster_Groups_Allowed_Cache)
end.