%%% ==================================================================== %%% ``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 %%% [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.