aboutsummaryrefslogtreecommitdiff
path: root/mod_pubsub_ng/pubsub_db.erl
diff options
context:
space:
mode:
Diffstat (limited to 'mod_pubsub_ng/pubsub_db.erl')
-rw-r--r--mod_pubsub_ng/pubsub_db.erl1575
1 files changed, 1575 insertions, 0 deletions
diff --git a/mod_pubsub_ng/pubsub_db.erl b/mod_pubsub_ng/pubsub_db.erl
new file mode 100644
index 000000000..37e8e4473
--- /dev/null
+++ b/mod_pubsub_ng/pubsub_db.erl
@@ -0,0 +1,1575 @@
+%%% ====================================================================
+%%% ``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_db).
+-author('karim.gemayel@process-one.net').
+
+-compile(export_all).
+
+-include("pubsub_dev.hrl").
+-include("pubsub_api.hrl").
+
+
+-import(pubsub_tools,
+[
+ get_option/2,
+ get_option/3,
+ get_value/2,
+ get_value/3,
+ set_value/3,
+ %%
+ check_access_model/3,
+ check_access_model/7,
+ check_publish_model/3,
+ is_contact_subscribed_to_node_owners/3,
+ is_contact_in_allowed_roster_groups/2,
+ has_subscriptions/1
+]).
+
+
+
+%% @doc Intialize mnesia
+
+-spec(init/2 ::
+(
+ Backend :: 'mnesia' | 'odbc',
+ Suffix :: atom())
+ -> 'ok'
+).
+
+init('mnesia', Suffix) ->
+ pubsub_db_mnesia:init(Suffix);
+init('odbc', _Suffix) ->
+ ok.
+
+
+transaction('mnesia', Module, Function, Arguments) ->
+ pubsub_db_mnesia:transaction(Module, Function, Arguments).
+
+
+%%
+db_transaction(Module, Function, Arguments) ->
+ case mnesia:transaction(
+ fun() ->
+ apply(Module, Function, Arguments)
+ end)
+ of
+ {atomic, Result} ->
+ Result;
+ {aborted, Reason} ->
+ ?INFO_MSG("DB TRANSACTION ERROR ~p", [Reason]),
+ {error, 'internal-server-error'}
+ end.
+
+%-- Create_Node --%
+-spec(create_node/5 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host_pubsub(),
+ Entity :: xmpp_jid:usr_bare(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Node_Options :: pubsub_options:options_node()})
+ -> %%
+ {result,
+ NodeId :: exmpp_pubsub:nodeId(),
+ Notify :: []
+ | [Notify_Subscription :: {
+ Notification_Type :: pubsub_options:notification_type(),
+ Subscription :: exmpp_pubsub:subscription_subscribed()
+ },...]
+ }
+ %
+ %%%
+ | {error, 'conflict'}
+).
+
+create_node(Host, Pubsub_Host, {_U,S,_R} = Entity, Pubsub_Features,
+ _Parameters = {undefined = _NodeId, Node_Options}) ->
+ case
+ pubsub_db_mnesia:read_node('dev',
+ Pubsub_Host, NodeId = exmpp_pubsub:nodeId())
+ of
+ undefined ->
+ {result,
+ NodeId,
+ _Notify = case
+ pubsub_db_mnesia:create_node('dev', Pubsub_Host, Entity, NodeId,
+ _Level = 1, Node_Options, Pubsub_Features)
+ of
+ undefined ->
+ [];
+ Subscription ->
+ [_Notify_Subscription = {
+ _Notification_Type = get_value(Node_Options,
+ 'notification_type', 'headline'),
+ Subscription
+ }]
+ end};
+ _Pubsub_Node ->
+ create_node(Host, Pubsub_Host, Entity, Pubsub_Features,
+ {_NodeId = undefined, Node_Options})
+ end;
+%%
+create_node(Host, Pubsub_Host, {_U,S,_R} = Entity, Pubsub_Features,
+ _Parameters = {NodeId, Node_Options}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {result,
+ NodeId,
+ _Notify = case
+ pubsub_db_mnesia:create_node('dev', Pubsub_Host, Entity, NodeId,
+ _Level = 1, Node_Options, Pubsub_Features)
+ of
+ undefined ->
+ [];
+ Subscription ->
+ [_Notify_Subscription = {
+ _Notification_Type = get_value(Node_Options,
+ 'notification_type', 'headline'),
+ Subscription
+ }]
+ end};
+ _Pubsub_Node ->
+ {error, 'conflict'}
+ end.
+
+%-- Delete_Node --%
+-spec(delete_node/3 ::
+(
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> %%
+ {result,
+ Notify :: []
+ | [Notify_Delete :: {
+ Notification_Type::pubsub_options:notification_type(),
+ Recipients::[Entity::xmpp_jid:usr_bare()]
+ }]
+ }
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+delete_node(_Pubsub_Host, _Entity, {undefined = _NodeId}) ->
+ {error, 'item-not-found'};
+%%
+delete_node(Pubsub_Host, Entity, {NodeId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ Pubsub_Node ->
+ case
+ lists:member(Entity, Owners = Pubsub_Node#pubsub_node_dev.owners)
+ of
+ true ->
+ {result,
+ _Notify = case
+ pubsub_db_mnesia:delete_node('dev', Pubsub_Node)
+ of
+ undefined ->
+ [];
+ Subscribers ->
+ [_Notify_Delete = {
+ _Notification_Type = get_value(
+ Pubsub_Node#pubsub_node_dev.options,
+ 'notification_type', 'headline'),
+ _Recipients = lists:append(Owners, Subscribers)
+ }]
+ end};
+ false ->
+ {error, 'forbidden'}
+ end
+ end.
+
+%-- Purge_Node --%
+-spec(purge_node/3 ::
+(
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> %%
+ {result,
+ Notify :: []
+ | [Notify_Purge :: {
+ Notification_Type::pubsub_options:notification_type(),
+ Recipients::[Entity::xmpp_jid:usr_bare()]
+ }]
+ }
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+ %
+ | {error, 'feature-not-implemented', 'persistent-items'}
+).
+
+purge_node(_Pubsub_Host, _Entity, {undefined = _NodeId}) ->
+ {error, 'item-not-found'};
+%%
+purge_node(Pubsub_Host, Entity, {NodeId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ Pubsub_Node ->
+ case lists:member(Entity, Pubsub_Node#pubsub_node_dev.owners) of
+ true ->
+ case
+ _Persist_Items = get_value(
+ Node_Options = Pubsub_Node#pubsub_node_dev.options,
+ 'persist_items', true)
+ of
+ true ->
+ {result,
+ _Notify = case
+ pubsub_db_mnesia:purge_node('dev', Pubsub_Node)
+ of
+ undefined ->
+ [];
+ Subscribers ->
+ [_Notify_Purge = {
+ _Notification_Type = get_value(
+ Node_Options,
+ 'notification_type', 'headline'),
+ _Recipients = Subscribers
+ }]
+ end};
+ false ->
+ {error, 'feature-not-implemented', 'persistent-items'}
+ end;
+ false ->
+ {error, 'forbidden'}
+ end
+ end.
+
+%-- Publish_Item --%
+-spec(publish_item/6 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host_pubsub(),
+ Entity :: xmpp_jid:usr_entity(),
+ Plugin :: exmpp_pubsub:plugin(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Parameters :: {
+ NodeId :: undefined | exmpp_pubsub:nodeId(),
+ Item :: 0 | 1,
+ ItemId :: undefined | exmpp_pubsub:itemId(),
+ Payload :: exmpp_pubsub:payload() | [Xmlel::xmlel(),...],
+ Options :: {Node_Options :: [] | pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()}
+ })
+ -> {result,
+ NodeId :: exmpp_pubsub:nodeId(),
+ ItemId :: exmpp_pubsub:itemId(),
+ Node_Options :: pubsub_options:options_node(),
+ Node_Owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Subscription :: undefined | exmpp_pubsub:subscription_subscribed(),
+ Broadcasted_Items :: {
+ Published_Items :: [Published_Item::{
+ ItemId :: exmpp_pubsub:itemId(),
+ Item_Options :: [] | pubsub_options:options_item(),
+ Payload :: exmpp_pubsub:payload(),
+ Publisher :: xmpp_jid:usr_entity()
+ }],
+ %
+ Retracted_Items :: [Retracted_Item::{
+ ItemId :: exmpp_pubsub:itemId(),
+ Item_Options :: [] | pubsub_options:options_item()
+ }]
+ },
+ Pubsub_States::[Pubsub_State::mod_pubsub_dev:pubsub_state(),...]}
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'item-not-found'}
+ %
+ | {error, 'bad-request', 'invalid-payload'}
+ | {error, 'bad-request', 'item-required'}
+ | {error, 'bad-request', 'item-forbidden'}
+ | {error, 'not-acceptable', 'payload-too-big'}
+).
+
+%%
+publish_item(Host, Pubsub_Host, {U,S,R} = Entity, Plugin, Pubsub_Features,
+ {undefined = _NodeId, Item, _ItemId, Payload,
+ {[] = _Node_Options, Item_Options}}) ->
+ case
+ pubsub_db_mnesia:read_node('dev',
+ Pubsub_Host, NodeId = exmpp_pubsub:nodeId())
+ of
+ undefined ->
+ case
+ options_on_publish_item(none, {Item, Payload,
+ Options = {
+ Node_Options = Plugin:node_options('leaf'),
+ Item_Options
+ }})
+ of
+ ok ->
+ {ItemId, Pubsub_State} = pubsub_db_mnesia:publish_item1('dev',
+ Pubsub_Host, Entity, NodeId, _Level = 1, _ItemId, Payload,
+ Options, Pubsub_Features),
+ Subscription = case
+ Pubsub_State#pubsub_state_dev.subscriptions
+ of
+ [] -> undefined;
+ [_Subscription] -> _Subscription
+ end,
+ {result, NodeId, ItemId, Node_Options,
+ _Node_Owners = [{U,S,undefined}],
+ _SubId = case Subscription of
+ undefined ->
+ undefined;
+ {_Subscription_State, SubId, _Resource,
+ _Subscription_Options} ->
+ SubId
+ end,
+ Subscription,
+ {_Published_Items = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_notifications', false)
+ of
+ true ->
+ [{ItemId,
+ Item_Options,
+ _Payload = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_payloads', false)
+ of
+ true ->
+ case Payload of
+ [Xmlel] -> Xmlel;
+ [] -> []
+ end;
+ false -> []
+ end,
+ _Publisher = {U,S,R}
+ }];
+ false ->
+ []
+ end,
+ _Retracted_Items = []},
+ _Pubsub_States = [Pubsub_State]};
+ Error ->
+ Error
+ end;
+ _Pusbub_Node ->
+ publish_item(Host, Pubsub_Host, {U,S,R}, Plugin, Pubsub_Features,
+ {undefined, Item, _ItemId, Payload, {[], Item_Options}})
+ end;
+
+%%
+publish_item(Host, Pubsub_Host, {U,S,R} = Entity, Plugin, Pubsub_Features,
+ {NodeId, Item, _ItemId, Payload, {[] = _Node_Options, Item_Options}}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ case
+ checks_on_publish_item(Host, {U,S,undefined}, Plugin,
+ {Item, Payload, _Node_Options = [], Item_Options})
+ of
+ {ok, Pubsub_Features, Node_Options} ->
+ Options = {Node_Options, Item_Options},
+ {ItemId, Pubsub_State} = pubsub_db_mnesia:publish_item1('dev',
+ Pubsub_Host, Entity, NodeId, _Level = 1, _ItemId, Payload,
+ Options, Pubsub_Features),
+ Subscription = case
+ Pubsub_State#pubsub_state_dev.subscriptions
+ of
+ [] -> undefined;
+ [_Subscription] -> _Subscription
+ end,
+ {result, NodeId, ItemId, Node_Options,
+ _Node_Owners = [{U,S,undefined}],
+ _SubId = case Subscription of
+ undefined ->
+ undefined;
+ {_Subscription_State, SubId, _Resource,
+ _Subscription_Options} ->
+ SubId
+ end,
+ Subscription,
+ {_Published_Items = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_notifications', false)
+ of
+ true ->
+ [{ItemId,
+ Item_Options,
+ _Payload = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_payloads', false)
+ of
+ true ->
+ case Payload of
+ [Xmlel] -> Xmlel;
+ [] -> []
+ end;
+ false -> []
+ end,
+ _Publisher = {U,S,R}
+ }];
+ false ->
+ []
+ end,
+ _Retracted_Items = []},
+ _Pubsub_States = [Pubsub_State]};
+ Error ->
+ Error
+ end;
+ Pubsub_Node ->
+ NodeIdx = Pubsub_Node#pubsub_node_dev.idx,
+ Pubsub_State = pubsub_db_mnesia:read_state('dev',
+ {U,S,undefined}, NodeIdx),
+ case
+ options_on_publish_item(Host, Entity,
+ _Affiliation = case Pubsub_State of
+ undefined -> 'none';
+ _ -> Pubsub_State#pubsub_state_dev.affiliation
+ end,
+ _Subscriptions = case Pubsub_State of
+ undefined -> [];
+ _ -> Pubsub_State#pubsub_state_dev.subscriptions
+ end,
+ _Options = {
+ Node_Options = Pubsub_Node#pubsub_node_dev.options,
+ Item_Options
+ },
+ Node_Owners = Pubsub_Node#pubsub_node_dev.owners,
+ Item,
+ Payload)
+ of
+ ok ->
+ {ItemId, Retracted_Items, Pubsub_States} =
+ pubsub_db_mnesia:publish_item2('dev', Pubsub_Host,
+ {U,S,R}, Pubsub_State, Pubsub_Node,
+ case _ItemId of
+ undefined ->
+ undefined;
+ _ ->
+ case
+ pubsub_db_mnesia:read_item('dev',
+ NodeIdx, _ItemId)
+ of
+ undefined -> _ItemId;
+ Pubsub_Item -> Pubsub_Item
+ end
+ end,
+ Payload, Item_Options, Pubsub_Features),
+ {result, NodeId, ItemId, Node_Options, Node_Owners,
+ _SubId = undefined, _Subscription = undefined,
+ {_Published_Items = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_notifications', false)
+ of
+ true ->
+ [{ItemId,
+ Item_Options,
+ _Payload = case
+ get_value({Node_Options, Item_Options},
+ 'deliver_payloads', false)
+ of
+ true ->
+ case Payload of
+ [Xmlel] -> Xmlel;
+ [] -> []
+ end;
+ false -> []
+ end,
+ _Publisher = {U,S,R}
+ }];
+ false ->
+ []
+ end,
+ Retracted_Items},
+ Pubsub_States};
+ Error ->
+ Error
+ end
+ end.
+
+
+%%
+-spec(options_on_publish_item/8 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_bare(),
+ Affiliation :: exmpp_pubsub:affiliation(),
+ Subscriptions :: exmpp_pubsub:subscriptions(),
+ Options :: {Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()},
+ Node_Owners :: [Node_Owner::xmpp_jid:usr_bare(),...],
+ Item :: 0 | 1,
+ Payload :: exmpp_pubsub:payload() | [Xmlel::xmlel(),...])
+ -> ok
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'item-not-found'}
+ %
+ | {error, 'bad-request', 'invalid-payload'}
+ | {error, 'bad-request', 'item-required'}
+ | {error, 'bad-request', 'item-forbidden'}
+ | {error, 'not-acceptable', 'payload-too-big'}
+).
+
+options_on_publish_item(_Host, _Entity, 'owner' = _Affiliation, _Subscriptions,
+ Options, _Node_Owners, Item, Payload) ->
+ options_on_publish_item(none, {Item, Payload, Options});
+%%
+options_on_publish_item(Host, Entity, 'outcast' = _Affiliation, _Subscriptions,
+ {Node_Options, _Item_Options}, Node_Owners, _Item, _Payload) ->
+ case get_value(Node_Options, '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 = get_value(Node_Options,
+ 'roster_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;
+%%
+options_on_publish_item(Host, Entity, Affiliation, Subscriptions,
+ {Node_Options, Item_Options} = _Options, Node_Owners, Item, Payload) ->
+ %% 'pubsub#access_model'
+ case
+ check_access_model(Host, Entity,
+ _Access_Model = get_value(Node_Options, 'access_model'),
+ Affiliation, Subscriptions, Node_Owners,
+ _Rosters_Groups_Allowed = get_value(Node_Options,
+ 'roster_groups_allowed'))
+ of
+ ok ->
+ %% 'pubsub#publish_model'
+ case
+ check_publish_model(
+ _Publish_Model = get_value(Node_Options, 'publish_model'),
+ Affiliation, Subscriptions)
+ of
+ ok ->
+ options_on_publish_item(none,
+ {Item, Payload, {Node_Options, Item_Options}});
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+
+ end.
+
+%%
+-spec(options_on_publish_item/2 ::
+(
+
+ Option :: none
+ | {pubsub_options:option_node_deliver_payloads(),
+ pubsub_options:option_node_persist_items()}
+ | pubsub_options:option_node_max_payload_size()
+ | pubsub_options:option_node_type(),
+ %%
+ Criteria :: {Item :: 0 | 1,
+ Payload :: exmpp_pubsub:payload() | [Xmlel::xmlel(),...],
+ Options :: {Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()}}
+ | {Item :: 0 | 1,
+ Payload :: exmpp_pubsub:payload(),
+ Options :: {Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()}}
+ %
+ | {Payload :: exmpp_pubsub:payload(),
+ Options :: {Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item()}}
+ %
+ | {Payload :: exmpp_pubsub:payload()})
+ -> %%
+ ok
+ %%%
+ | {error, 'bad-request', 'invalid-payload'}
+ | {error, 'bad-request', 'item-required'}
+ | {error, 'bad-request', 'payload-required'}
+ | {error, 'bad-request', 'item-forbidden'}
+ | {error, 'not-acceptable', 'payload-too-big'}
+).
+
+%% Payloads count
+options_on_publish_item(none, {Item, [] = Payload, Options}) ->
+ options_on_publish_item(
+ {get_option(Options, 'deliver_payloads', false),
+ get_option(Options, 'persist_items', false)},
+ {Item, Payload, Options});
+%%
+options_on_publish_item(none, {Item, [Payload], Options}) ->
+ options_on_publish_item(
+ {get_option(Options, 'deliver_payloads', false),
+ get_option(Options, 'persist_items', false)},
+ {Item, Payload, Options});
+%%
+options_on_publish_item(none, _Parameters) ->
+ {error, 'bad-request', 'invalid-payload'};
+%%
+%% 'pubsub#deliver_payloads'
+%% 'pubsub#persist_items'
+options_on_publish_item(
+ {{'deliver_payloads', _Deliver_Payloads}, {'persist_items', true}},
+ {0 = _Item, _Payload, _Options}) ->
+ {error, 'bad-request', 'item-required'};
+%%
+options_on_publish_item(
+ {{'deliver_payloads', true}, {'persist_items', _Persist_Items}},
+ {_Item, [] = _Payload, _Options}) ->
+ {error, 'bad-request', 'payload-required'};
+%%
+options_on_publish_item(
+ {{'deliver_payloads', false}, {'persist_items', false}},
+ {1 = _Item, _Payload, _Options}) ->
+ {error, 'bad-request', 'item-forbidden'};
+%%
+options_on_publish_item(
+ {{'deliver_payloads', Deliver_Payloads}, {'persist_items', Persist_Items}},
+ {_Item, [] = _Payload, Options}) ->
+ ok;
+%%
+options_on_publish_item(
+ {{'deliver_payloads', Deliver_Payloads}, {'persist_items', Persist_Items}},
+ {_Item, Payload, {Node_Options, Item_Options} = _Options}) ->
+ options_on_publish_item(
+ _Max_Payload_Size = get_option(Node_Options, 'max_payload_size', 0),
+ {Payload, {Node_Options, Item_Options}});
+%% 'pubsub#max_payload_size'
+options_on_publish_item({'max_payload_size', Max_Payload_Size},
+ {Payload, Options}) ->
+ case byte_size(term_to_binary(Payload)) =< Max_Payload_Size of
+ true ->
+ options_on_publish_item(get_option(Options, 'type', undefined),
+ {Payload});
+ false ->
+ {error, 'not-acceptable', 'payload-too-big'}
+ end;
+%% 'pubsub#type'
+options_on_publish_item({'type', Type}, {Payload})
+ when Type == undefined ->
+ %%orelse Type == Payload#xmlel.ns ->
+ ok;
+%%
+options_on_publish_item(_, _) ->
+ {error, 'bad-request', 'invalid-payload'}.
+
+
+
+%%
+-spec(checks_on_publish_item/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Entity :: xmpp_jid:usr_entity(),
+ Plugin :: exmpp_pubsub:plugin(),
+ Parameters :: {Item :: 0 | 1,
+ Payload :: [Xmlel::xmlel()],
+ Node_Options :: [],
+ Item_Options :: [] | pubsub_options:options_item()})
+ -> {ok,
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Node_Options :: pubsub_options:options_node()}
+ %%%
+ | {error, 'forbidden'}
+ | {error, 'item-not-found'}
+ %
+ | {error, 'bad-request', 'invalid-payload'}
+ | {error, 'bad-request', 'item-required'}
+ | {error, 'bad-request', 'item-forbidden'}
+ | {error, 'not-acceptable', 'payload-too-big'}
+).
+
+
+checks_on_publish_item({U,S,_} = _Host, {U,S,_} = _Entity, Plugin,
+ {Item, Payload, [] = _Node_Options, Item_Options}) ->
+ case
+ lists:member(<<"auto-create">>, Pubsub_Features = Plugin:pubsub_features())
+ of
+ true ->
+ case
+ options_on_publish_item(none,
+ {Item,
+ Payload,
+ _Options = {
+ Node_Options = Plugin:node_options('leaf'),
+ Item_Options
+ }})
+ of
+ ok -> {ok, Pubsub_Features, Node_Options};
+ Error -> Error
+ end;
+ false ->
+ {error, 'item-not-found'}
+ end;
+%%
+checks_on_publish_item(_Host, _Entity, _Plugin, _Parameters)
+ when is_tuple(_Host) ->
+ {error, 'foribdden'};
+%%
+checks_on_publish_item(Host, {U,S,_R} = _Entity, Plugin,
+ {Item, Payload, [] = _Node_Options, Item_Options}) ->
+ case acl:match_rule(Host, pubsub_createnode, jlib:jid_to_string({U,S, <<>>})) of
+ allow ->
+ case
+ lists:member(<<"auto-create">>,
+ Pubsub_Features = Plugin:pubsub_features())
+ of
+ true ->
+ case
+ options_on_publish_item(none,
+ {Item, Payload,
+ _Options = {
+ Node_Options = Plugin:node_options('leaf'),
+ Item_Options
+ }})
+ of
+ ok -> {ok, Pubsub_Features, Node_Options};
+ Error -> Error
+ end;
+ false ->
+ {error, 'item-not-found'}
+ end;
+ _Deny ->
+ {error, 'forbidden'}
+ end.
+
+%-- Retract_Item --%
+-spec(retract_item/3 ::
+(
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ ItemId :: undefined | exmpp_pubsub:itemId(),
+ Adhoc_Notify_Retract :: undefined | boolean()})
+ -> {result,
+ Notify :: []
+ | [Notify_Retract :: {
+ Node_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Node_Options :: pubsub_options:options_node(),
+ Item_Options :: [] | pubsub_options:options_item(),
+ Pubsub_States :: mod_pubsub_dev:pubsub_states()
+ }]}
+ %%%
+ | {error, 'bad-request', 'nodeid-required'}
+ | {error, 'bad-request', 'item-required'}
+ %
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+retract_item(_Pubsub_Host, _Entity,
+ {undefined = _NodeId, _ItemId, _Adhoc_Notify_Retract}) ->
+ {error, 'bad-request', 'nodeid-required'};
+%%
+retract_item(_Pubsub_Host, _Entity,
+ {_NodeId, undefined = _ItemId, _Adhoc_Notify_Retract}) ->
+ {error, 'bad-request', 'item-required'};
+%%
+retract_item(Pubsub_Host, Entity, {NodeId, ItemId, Adhoc_Notify_Retract}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ Pubsub_Node ->
+ case
+ pubsub_db_mnesia:read_state('dev', Entity,
+ Pubsub_Node#pubsub_node_dev.idx)
+ of
+ Pubsub_State
+ when Pubsub_State == undefined
+ orelse Pubsub_State#pubsub_state_dev.affiliation == 'outcast' ->
+ {error, 'forbidden'};
+ Pubsub_State ->
+ case
+ pubsub_db_mnesia:retract_item('dev', Pubsub_Node,
+ Pubsub_State, ItemId, Adhoc_Notify_Retract)
+ of
+ {ok, undefined} ->
+ {result, _Notify = []};
+ {ok, Item_Options, Pubsub_States} ->
+ {result,
+ _Notify = [_Notify_Retract = {
+ _Node_Owners = Pubsub_Node#pubsub_node_dev.owners,
+ _Node_Options = Pubsub_Node#pubsub_node_dev.options,
+ Item_Options,
+ Pubsub_States
+ }]
+ };
+ Error ->
+ Error
+ end
+ end
+ end.
+
+
+%-- Subscribe_Node --%
+-spec(subscribe_node/7 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Plugin :: exmpp_pubsub:plugin(),
+ Options_Module :: module(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Parameters :: {NodeId :: exmpp_pubsub:nodeId(),
+ Resource :: undefined | xmpp_jid:resource_jid(),
+ Subscribe_Options :: [Xmlel::xmlel(),...]
+ | [Xmlel::xmlel()]})
+ -> {result,
+ Subscription_Subscribed :: exmpp_pubsub:subscription_subscribed(),
+ Affiliation :: 'member' | 'owner' | 'publisher',
+ Node_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Node_Options :: pubsub_options:options_node(),
+ Pubsub_Last_Item :: undefined | mod_pubsub_dev:pubsub_last_item()}
+ %
+ | {result,
+ Subscription_Pending :: exmpp_pubsub:subscription_pending(),
+ Affiliation :: 'member',
+ Node_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Node_Options :: pubsub_options:options_node(),
+ Pubsub_Last_Item :: undefined}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+ %
+ | {error, 'invalid-options'}
+ | {error, 'not-acceptable'}
+ %
+ | {error, 'not-authorized', 'presence-subscription-required'}
+ | {error, 'not-authorized', 'not-in-roster-group'}
+ | {error, 'not-authorized', 'pending-subscription'}
+ | {error, 'not-allowed', 'closed-node'}
+).
+
+subscribe_node(_Host, _Pubsub_Host, _Entity, _Plugin, _Options_Module,
+ _Pubsub_Features, {undefined = _NodeId, _Resource, _Subscribe_Options}) ->
+ {error, 'item-not-found'};
+%%
+subscribe_node(Host, Pubsub_Host, {_, S,_} = Entity, Plugin, Options_Module,
+ Pubsub_Features, {NodeId, Resource, Subscribe_Options}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners, options = Node_Options} ->
+ case
+ Options_Module:parse_xmlel_x(Plugin, Pubsub_Features, Entity,
+ _Entity_Type = case S == Host of
+ true -> 'local';
+ false -> 'remote'
+ end,
+ 'subscribe_options',
+ _Node_Type = get_value(Node_Options, 'node_type', 'leaf'),
+ Subscribe_Options)
+ of
+ {ok, Subscription_Options} ->
+ Pubsub_State = case
+ pubsub_db_mnesia:read_state('dev', Entity, NodeIdx)
+ of
+ undefined ->
+ #pubsub_state_dev{
+ id = {Entity, NodeIdx},
+ nodeidx = NodeIdx
+ };
+ _Pubsub_State ->
+ _Pubsub_State
+ end,
+ case
+ pubsub_db_mnesia:subscribe_node('dev', Host, Node_Options,
+ Node_Owners, Pubsub_State, Resource,
+ Subscription_Options, Pubsub_Features)
+ of
+ {result, Subscription, Pubsub_Last_Item} ->
+ {result,
+ Subscription,
+ case Pubsub_State#pubsub_state_dev.affiliation of
+ 'none' -> 'member';
+ Affiliation -> Affiliation
+ end,
+ Node_Owners, Node_Options, Pubsub_Last_Item};
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end
+ end.
+
+%-- Unsubscribe_Node --%
+-spec(unsubscribe_node/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: exmpp_pubsub:nodeId(),
+ _Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ _SubId :: exmpp_pubsub:subId()})
+ -> {result,
+ SubId :: exmpp_pubsub:subId()}
+ |
+ {result,
+ SubId :: exmpp_pubsub:subId(),
+ Node_Owners :: [Entity::xmpp_jid:usr_bare(),...],
+ Notification_Type :: pubsub_options:notification_type()}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+ | {error, 'unexpected', 'not-subscribed'}
+ %
+ | {error, 'unexpected', 'not-subscribed'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+unsubscribe_node(_Host, _Pubsub_Host, _Entity,
+ {undefined = _NodeId, _Resource, _SubId}) ->
+ {error, 'item-not-found'};
+%%
+unsubscribe_node(Host, Pubsub_Host, Entity, {NodeId, _Resource, _SubId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners, options = Node_Options} ->
+ case pubsub_db_mnesia:read_state('dev', Entity, NodeIdx) of
+ Pubsub_State
+ when Pubsub_State == undefined
+ orelse Pubsub_State#pubsub_state_dev.affiliation == 'none'
+ orelse Pubsub_State#pubsub_state_dev.subscriptions == [] ->
+ {error, 'unexpected', 'not-subscribed'};
+ Pubsub_State
+ when Pubsub_State#pubsub_state_dev.affiliation == 'publish-only'->
+ {error, 'forbidden'};
+ Pubsub_State ->
+ case
+ pubsub_db_mnesia:unsubscribe_node('dev', Host,
+ _Node_Access_Model = get_value(Node_Options, 'access_model'),
+ Pubsub_State, _Resource, _SubId)
+ of
+ {ok, Resource, SubId} ->
+ case get_value(Node_Options, 'notify_sub', false) of
+ true ->
+ {result, SubId, Node_Owners,
+ get_value(Node_Options, 'notification_type',
+ 'headline')};
+ false ->
+ {result, SubId}
+ end;
+ Error ->
+ Error
+ end
+ end
+ end.
+
+%-- Set_Configure_Subscription --%
+-spec(set_configure_subscription/7 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Plugin :: exmpp_pubsub:plugin(),
+ Options_Module :: module(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()},
+ Subscribe_Options :: [Xmlel::xmlel(),...]
+ | [Xmlel::xmlel()]})
+ -> {result, []}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'forbidden'}
+ | {error, 'bad-request', 'invalid-options'}
+ %
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'forbidden'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+set_configure_subscription(_Host, _Pubsub_Host, _Entity, _Plugin, _Options_Module,
+ _Pubsub_Features, {undefined = _NodeId, _SubId, _Resource, _Subscribe_Options}) ->
+ {error, 'item-not-found'};
+%%
+set_configure_subscription(Host, Pubsub_Host, {_,S,_} = Entity, Plugin, Options_Module,
+ Pubsub_Features, {NodeId, SubId, Resource, Subscribe_Options}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners, options = Node_Options} ->
+ case pubsub_db_mnesia:read_state('dev', Entity, NodeIdx) of
+ undefined ->
+ {error, 'unexpected-request', 'not-subscribed'};
+ #pubsub_state_dev{affiliation = Affiliation}
+ when Affiliation == 'publish-only'
+ orelse Affiliation == 'outcast' ->
+ {error, 'forbidden'};
+ #pubsub_state_dev{subscriptions = []} ->
+ {error, 'unexpected-request', 'not-subscribed'};
+ Pubsub_State ->
+ case
+ Options_Module:parse_xmlel_x(Plugin, Pubsub_Features, Entity,
+ _Entity_Type = case S == Host of
+ true -> 'local';
+ false -> 'remote'
+ end,
+ 'subscribe_options',
+ _Node_Type = get_value(Node_Options, 'node_type', 'leaf'),
+ Subscribe_Options)
+ of
+ {ok, Subscription_Options} ->
+ case
+ pubsub_db_mnesia:set_configure_subscription('dev',
+ Pubsub_State, SubId, Resource,
+ Subscription_Options)
+ of
+ ok ->
+ {result, []};
+ Error ->
+ Error
+ end;
+ _Error ->
+ {error, 'bad-request', 'invalid-options'}
+ end
+ end
+ end.
+
+%-- Get_Items --%
+-spec(get_items/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pusbub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ _SubId :: undefined | exmpp_pubsub:subId(),
+ Max_Items :: non_neg_integer(),
+ ItemIds :: undefined}
+ | {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ _SubId :: undefined | exmpp_pubsub:subId(),
+ Max_Items :: undefined,
+ ItemIds :: [] | exmpp_pubsub:itemIds()})
+ -> {result,
+ Pubsub_Items :: [] | mod_pubsub_dev:pubsub_items(),
+ Cache :: {Presence_Cache :: undefined,
+ Groups_Cache :: undefined}
+ | {Presence_Cache :: true,
+ Groups_Cache :: undefined}
+ | {Presence_Cache :: undefined,
+ Groups_Cache :: [{Node_Owner :: xmpp_jid:usr_bare(),
+ Groups :: [Group::binary()]}]},
+ Node_Owners :: mod_pubsub_dev:node_owners(),
+ Node_Access_Model :: pubsub_options:access_model(),
+ Node_Groups :: pubsub_options:rosters_groups_allowed(),
+ Affiliation :: exmpp_pubsub:affiliation(),
+ Access :: undefined | 'presence' | 'roster' | 'pending' | 'authorize'}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+ | {error, 'not-authorized', 'presence-subscription-required'}
+ | {error, 'not-authorized', 'not-in-roster-group'}
+ | {error, 'not-allowed', 'closed-node'}
+).
+
+get_items(_Host, _Pubsub_Host, _Entity,
+ {undefined = _NodeId, _SubId, _Max_Items, _ItemIds}) ->
+ {error, 'item-not-found'};
+%%
+get_items(Host, Pubsub_Host, Entity, {NodeId, _SubId, Max_Items, ItemIds}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners,
+ options = Node_Options, itemids = Node_ItemIds} ->
+ {Affiliation, Access} = case lists:member(Entity, Node_Owners) of
+ true ->
+ {'owner', undefined};
+ false ->
+ case pubsub_db_mnesia:read_state('dev', Entity, NodeIdx) of
+ undefined ->
+ {'none', undefined};
+ #pubsub_state_dev{affiliation = _Affiliation,
+ access = _Access} ->
+ {_Affiliation, _Access}
+ end
+ end,
+ Node_Access_Model = get_value(Node_Options, 'access_model'),
+ Node_Groups = get_value(Node_Options, 'roster_groups_allowed', []),
+ case
+ pubsub_db_mnesia:get_items('dev', Host,
+ {NodeIdx, Node_Owners, Node_ItemIds, Node_Access_Model, Node_Groups},
+ {Entity, Affiliation, Access},
+ {Max_Items, ItemIds, _SubId})
+ of
+ {ok, Pubsub_Items, Cache} ->
+ {result, Pubsub_Items, Cache, Node_Owners, Node_Access_Model,
+ Node_Groups, Affiliation, Access};
+ Error ->
+ Error
+ end
+ end.
+
+%-- Get_Entity_Affiliations --%
+-spec(get_entity_affiliations/3 ::
+(
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> Entity_Affiliations :: [Entity_Affiliation :: {
+ NodeId :: exmpp_pubsub:nodeId(),
+ Affiliation :: exmpp_pubsub:affiliation()
+ }]
+).
+
+get_entity_affiliations(Pubsub_Host, Entity, {NodeId}) ->
+ pubsub_db_mnesia:get_entity_affiliations('dev', Pubsub_Host, Entity, NodeId).
+
+%-- Get_Entity_Subscriptions --%
+-spec(get_entity_subscriptions/3 ::
+(
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> Entity_Subscriptions :: [{
+ NodeId :: exmpp_pubsub:nodeId(),
+ Subscriptions :: _%exmpp_pubsub:subscriptions()
+ }]
+).
+
+get_entity_subscriptions(Pubsub_Host, Entity, {NodeId}) ->
+ pubsub_db_mnesia:get_entity_subscriptions('dev', Pubsub_Host, Entity, NodeId).
+
+
+%-- Get_Node_Affiliations --%
+-spec(get_node_affiliations/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> {result, Pubsub_States::[Pubsub_State::mod_pubsub_dev:pubsub_state(),...]}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+
+).
+
+get_node_affiliations(_Host, _Pubsub_Host, _Entity, {undefined = _NodeId}) ->
+ {error, 'item-not-found'};
+%%
+get_node_affiliations(Host, Pubsub_Host, Entity, {NodeId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Owners, options = Options} ->
+ case lists:member(Entity, Owners) of
+ true ->
+ {result,
+ _Pubsub_States = pubsub_db_mnesia:index_read_state('dev',
+ NodeIdx)};
+ false ->
+ %% Depending on the node access_model, the returned error
+ %% can be 'forbidden' or 'item-not-found' to not disclose
+ %% private nodes
+ case get_value(Options, 'access_model') of
+ %%
+ 'open' ->
+ {error, 'forbidden'};
+ %%
+ 'presence' ->
+ case
+ is_contact_subscribed_to_node_owners(Host,
+ Entity, Owners)
+ of
+ false ->
+ {error, 'item-not-found'};
+ _Node_Owner ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'roster' ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ _Rosters_Groups_Allowed = get_value(
+ Options, 'roster_groups_allowed'))
+ of
+ false ->
+ {error, 'item-not-found'};
+ {_Node_Owner, _Roster_Group_Allowed} ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'authorize' ->
+ {error, 'forbidden'};
+ %%
+ 'whitelist' ->
+ case
+ pubsub_db_mnesia:read_state('dev', Entity, NodeIdx)
+ of
+ #pubsub_state_dev{affiliation = Affiliation}
+ when Affiliation == 'member'
+ orelse Affiliation == 'publish-only'
+ orelse Affiliation == 'publisher' ->
+ {error, 'forbidden'};
+ _Pubsub_State ->
+ {error, 'item-not-found'}
+ end
+ end
+ end
+ end.
+
+%-- Get_Node_Subscriptions --%
+-spec(get_node_subscriptions/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> {result, Pubsub_States::[Pubsub_State::mod_pubsub_dev:pubsub_state(),...]}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+
+).
+
+get_node_subscriptions(_Host, _Pubsub_Host, _Entity, {undefined = _NodeId}) ->
+ {error, 'item-not-found'};
+%%
+get_node_subscriptions(Host, Pubsub_Host, Entity, {NodeId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Owners, options = Options} ->
+ case lists:member(Entity, Owners) of
+ true ->
+ {result,
+ _Pubsub_States = pubsub_db_mnesia:index_read_state('dev',
+ NodeIdx)};
+ false ->
+ %% Depending on the node access_model, the returned error
+ %% can be 'forbidden' or 'item-not-found' to not disclose
+ %% private nodes
+ case get_value(Options, 'access_model') of
+ %%
+ 'open' ->
+ {error, 'forbidden'};
+ %%
+ 'presence' ->
+ case
+ is_contact_subscribed_to_node_owners(Host,
+ Entity, Owners)
+ of
+ false ->
+ {error, 'item-not-found'};
+ _Node_Owner ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'roster' ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ _Rosters_Groups_Allowed = get_value(Options,
+ 'roster_groups_allowed'))
+ of
+ false ->
+ {error, 'item-not-found'};
+ {_Node_Owner, _Roster_Group_Allowed} ->
+ {error, 'forbidden'}
+ end;
+ %%
+ 'authorize' ->
+ {error, 'forbidden'};
+ %%
+ 'whitelist' ->
+ case
+ pubsub_db_mnesia:read_state('dev', Entity, NodeIdx)
+ of
+ #pubsub_state_dev{affiliation = Affiliation}
+ when Affiliation == 'member'
+ orelse Affiliation == 'publish-only'
+ orelse Affiliation == 'publisher' ->
+ {error, 'forbidden'};
+ _Pubsub_State ->
+ {error, 'item-not-found'}
+ end
+ end
+ end
+ end.
+
+%% Get_Configure_Subscription_Default
+-spec(get_configure_subscription_default/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ NodeId :: exmpp_pubsub:nodeId())
+ -> ok
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+get_configure_subscription_default(Host, Pubsub_Host, Entity, NodeId) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners, options = Node_Options} ->
+ case lists:member(Entity, Node_Owners) of
+ true ->
+ ok;
+ false ->
+ Node_Access_Model = get_value(Node_Options, 'access_model'),
+ case pubsub_db_mnesia:read_state('dev', Entity, NodeIdx) of
+ undefined
+ when Node_Access_Model == 'open' ->
+ ok;
+ undefined
+ when Node_Access_Model == 'presence' ->
+ case
+ is_contact_subscribed_to_node_owners(Host,
+ Entity, Node_Owners)
+ of
+ false -> {error, 'forbidden'};
+ _Node_Owner -> ok
+ end;
+ undefined
+ when Node_Access_Model == 'roster' ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ _Rosters_Groups_Allowed =
+ get_value(Node_Options,
+ 'roster_groups_allowed', []))
+ of
+ false -> {error, 'forbidden'};
+ _ -> ok
+ end;
+ undefined ->
+ {error, 'forbidden'};
+ #pubsub_state_dev{affiliation = Affiliation,
+ access = Access}
+ when Affiliation == 'publisher'
+ orelse Affiliation == 'member'
+ orelse (Affiliation == 'none'
+ andalso
+ Node_Access_Model == 'open')
+ orelse Access == 'presence'
+ orelse Access == 'roster' ->
+ ok;
+ #pubsub_state_dev{affiliation = 'none'}
+ when Node_Access_Model == 'presence' ->
+ case
+ is_contact_subscribed_to_node_owners(Host,
+ Entity, Node_Owners)
+ of
+ false ->
+ {error, 'forbidden'};
+ _Node_Owner ->
+ ok
+ end;
+ #pubsub_state_dev{affiliation = 'none'}
+ when Node_Access_Model == 'roster' ->
+ case
+ is_contact_in_allowed_roster_groups(Entity,
+ _Rosters_Groups_Allowed =
+ get_value(Node_Options,
+ 'roster_groups_allowed', []))
+ of
+ false ->
+ {error, 'forbidden'};
+ _ ->
+ ok
+ end;
+ _ ->
+ {error, 'forbidden'}
+
+ end
+ end
+ end.
+
+%-- Get_Configure_Subscription --%
+-spec(get_configure_subscription/6 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Plugin :: exmpp_pubsub:plugin(),
+ Pubsub_Features :: exmpp_pubsub:pubsub_features(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId(),
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()}
+ })
+ -> {result,
+ Subscription :: exmpp_pubsub:subscription_subscribed()
+ | exmpp_pubsub:subscription_unconfigured()}
+ %%%
+ | {error, 'item-not-found'}
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'forbidden'}
+ %
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'forbidden'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+get_configure_subscription(_Host, _Pubsub_Host, _Entity, _Plugin,
+ _Pubsub_Features, {undefined = _NodeId, _SubId, _Resource}) ->
+ {error, 'item-not-found'};
+%%
+get_configure_subscription(_Host, Pubsub_Host, Entity, Plugin,
+ Pubsub_Features, {NodeId, SubId, Resource}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{idx = NodeIdx, owners = Node_Owners, options = Node_Options} ->
+ case pubsub_db_mnesia:read_state('dev', Entity, NodeIdx) of
+ undefined ->
+ {error, 'unexpected-request', 'not-subscribed'};
+ #pubsub_state_dev{affiliation = Affiliation}
+ when Affiliation == 'publish-only'
+ orelse Affiliation == 'outcast' ->
+ {error, 'forbidden'};
+ #pubsub_state_dev{subscriptions = []} ->
+ {error, 'unexpected-request', 'not-subscribed'};
+ #pubsub_state_dev{subscriptions = Subscriptions} ->
+ case filter_subscription(Subscriptions, SubId, Resource) of
+ {ok, Subscription} -> {result, Subscription};
+ Error -> Error
+ end
+ end
+ end.
+
+-spec(filter_subscription/3 ::
+(
+ Subscriptions :: exmpp_pubsub:subscriptions(),
+ SubId :: undefined | exmpp_pubsub:subId(),
+ Resource :: undefined
+ | xmpp_jid:resource_jid()
+ | {'caps', xmpp_jid:resource_jid()})
+ -> {ok,
+ Subscription :: exmpp_pubsub:subscription_subscribed()
+ | exmpp_pubsub:subscription_unconfigured()}
+ %%%
+ | {error, 'unexpected-request', 'not-subscribed'}
+ | {error, 'bad-request', 'subid-required'}
+ | {error, 'forbidden'}
+ | {error, 'not-acceptable', 'invalid-subid'}
+).
+
+filter_subscription(Subscriptions, undefined = _SubId, Resource) ->
+ case lists:keytake(Resource, 3, Subscriptions) of
+ {value, {Subscription_State, SubId, Resource, Subscription_Options},
+ _Subscriptions} ->
+ case lists:keymember(Resource, 3, _Subscriptions) of
+ false
+ when Subscription_State == 'pending' ->
+ {error, 'forbidden'};
+ false ->
+ {ok,
+ {Subscription_State, SubId, Resource, Subscription_Options}};
+ true ->
+ {error, 'bad-request', 'subid-required'}
+ end;
+ false ->
+ {error, 'unexpected-request', 'not-subscribed'}
+ end;
+%%
+filter_subscription(Subscriptions, SubId, Resource) ->
+ case lists:keyfind(SubId, 2, Subscriptions) of
+ %%
+ {'pending', _, Resource, _} ->
+ {error, 'forbidden'};
+ %%
+ {Subscription_State, SubId, Resource, Subscription_Options} ->
+ {ok, {Subscription_State, SubId, Resource, Subscription_Options}};
+ %%
+ {_, SubId, _, _} ->
+ {error, 'unexpected-request', 'not-subscribed'};
+ false ->
+ case lists:keyfind(Resource, 3, Subscriptions) of
+ {'pending', _, Resource, _} ->
+ {error, 'forbidden'};
+ {_, _, Resource, _} ->
+ {error, 'not-acceptable', 'invalid-subid'};
+ _ ->
+ {error, 'unexpected-request', 'not-subscribed'}
+ end
+ end.
+
+%-- Get_Configure_Node --%
+-spec(get_configure_node/4 ::
+(
+ Host :: xmpp_jid:raw_jid_component_bare(),
+ Pubsub_Host :: exmpp_pubsub:host(),
+ Entity :: xmpp_jid:usr_bare(),
+ Parameters :: {NodeId :: undefined | exmpp_pubsub:nodeId()})
+ -> {result, Node_Options :: pubsub_options:options_node()}
+ %%%
+ | {error, 'bad-request', 'nodeid-required'}
+ | {error, 'item-not-found'}
+ | {error, 'forbidden'}
+).
+
+get_configure_node(_Host, _Pubsub_Host, _Entity, {undefined = _NodeId}) ->
+ {error, 'bad-request', 'nodeid-required'};
+%%
+get_configure_node(_Host, Pubsub_Host, Entity, {NodeId}) ->
+ case pubsub_db_mnesia:read_node('dev', Pubsub_Host, NodeId) of
+ undefined ->
+ {error, 'item-not-found'};
+ #pubsub_node_dev{owners = Node_Owners, options = Node_Options} ->
+ case lists:member(Entity, Node_Owners) of
+ true -> {result, Node_Options};
+ false -> {error, 'forbidden'}
+ end
+ end.