2013-04-15 12:03:14 +02:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
%%% File : mod_profile.erl
|
|
|
|
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
|
|
|
|
%%% Purpose : Store profile stanzas in Mnesia table (XEP-0154 0.5 IQ semantics)
|
|
|
|
%%% Created : 22 Oct 2006 by Magnus Henoch <henoch@dtek.chalmers.se>
|
|
|
|
%%% Id : $Id: mod_profile.erl 1039 2009-11-24 22:47:40Z badlop $
|
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
|
|
|
|
%%%% Documentation
|
|
|
|
|
|
|
|
%%% Protocol implementation (as of XEP-0154 0.6)
|
|
|
|
%%%
|
|
|
|
%%% 4.1. Publishing a Full Profile
|
|
|
|
%%% + Stanza is processed, and a response is returned
|
|
|
|
%%% - But the stanza is stored in a Mnesia table, not in PubSub
|
|
|
|
%%%
|
|
|
|
%%% 4.2. Updating One or More Profile Fields
|
|
|
|
%%% - Not possible because PubSub is not used for storage
|
|
|
|
%%%
|
|
|
|
%%% 5.1. Discovering Support
|
|
|
|
%%% + Fully implemented
|
|
|
|
%%%
|
|
|
|
%%% 5.2. Requesting Full Profile
|
|
|
|
%%% + Stanza is processed, and a response is returned
|
|
|
|
%%% - The information is read from Mnesia table, not from PubSub
|
|
|
|
%%%
|
|
|
|
%%% 5.3. Receiving Profile Updates
|
|
|
|
%%% - Does not work because storage is done on Mnesia, not PubSub
|
|
|
|
%%%
|
|
|
|
%%% 7. Security Considerations
|
|
|
|
%%% - Implement Access option to restrict who is allowed to read/write profiles
|
|
|
|
%%% - any pubsub node it may create for profile data has an access model of "presence" or "roster"
|
|
|
|
%%%
|
|
|
|
%%%
|
|
|
|
%%% The server should include code to forward information of IQ queries into PubSub,
|
|
|
|
%%% so all the information provided with any semantic mentioned in XEP-0154
|
|
|
|
%%% must be stored in PubSub.
|
|
|
|
%%% stpeter: I think the idea was (B), but it's not well-defined
|
|
|
|
%%%
|
|
|
|
%%%
|
|
|
|
%%% Custom IQ semantics for XEP-0154:
|
|
|
|
%%%
|
|
|
|
%%% 5.4. Requesting One or More Profile Fields
|
|
|
|
%%%
|
|
|
|
%%% A user can query some specific fields from his profile:
|
|
|
|
%%%
|
|
|
|
%%% <iq type='get' to='user@localhost'>
|
|
|
|
%%% <profile xmlns='urn:xmpp:tmp:profile'>
|
|
|
|
%%% <x xmlns='jabber:x:data' type='submit'>
|
|
|
|
%%% <field var='FORM_TYPE' type='hidden'><value>urn:xmpp:tmp:profile</value></field>
|
|
|
|
%%% <field var='nickname'/>
|
|
|
|
%%% <field var='country'/>
|
|
|
|
%%% <field var='postal_code'/>
|
|
|
|
%%% </x>
|
|
|
|
%%% </profile>
|
|
|
|
%%% </iq>
|
2015-03-13 11:13:21 +01:00
|
|
|
%%%
|
2013-04-15 12:03:14 +02:00
|
|
|
%%% The server will return only the fields that were requested and the user had defined previously:
|
2015-03-13 11:13:21 +01:00
|
|
|
%%%
|
2013-04-15 12:03:14 +02:00
|
|
|
%%% <iq from='user@localhost' type='result' to='user@localhost/Home'>
|
|
|
|
%%% <profile xmlns='urn:xmpp:tmp:profile'>
|
|
|
|
%%% <x xmlns='jabber:x:data' type='result'>
|
|
|
|
%%% <field var='FORM_TYPE' type='hidden'><value>urn:xmpp:tmp:profile</value></field>
|
|
|
|
%%% <field var='nickname'><value>Drummy</value></field>
|
|
|
|
%%% <field var='country'><value>DK</value></field>
|
|
|
|
%%% </x>
|
|
|
|
%%% </profile>
|
|
|
|
%%% </iq>
|
|
|
|
%%%
|
|
|
|
%%% 4.3. Updating One or More Profile Fields using IQ Semantics
|
|
|
|
%%%
|
|
|
|
%%% - if a field is set empty value, delete field in the stored profile
|
|
|
|
%%%
|
|
|
|
|
|
|
|
%%%=======================
|
|
|
|
%%%% Headers
|
|
|
|
|
|
|
|
-module(mod_profile).
|
2015-03-13 11:13:21 +01:00
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
-author('henoch@dtek.chalmers.se').
|
|
|
|
|
|
|
|
-behaviour(gen_mod).
|
|
|
|
|
2015-03-13 11:13:21 +01:00
|
|
|
-export([start/2, stop/1, process_sm_iq/3,
|
|
|
|
get_sm_features/5, remove_user/2]).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
-include("ejabberd.hrl").
|
2015-03-13 11:13:21 +01:00
|
|
|
|
2013-04-15 12:03:14 +02:00
|
|
|
-include("jlib.hrl").
|
2015-03-13 11:13:21 +01:00
|
|
|
|
2014-06-29 10:15:06 +02:00
|
|
|
-include("logger.hrl").
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
-record(profile, {us, fields}).
|
|
|
|
|
2015-03-13 11:13:21 +01:00
|
|
|
-define(NS_PROFILE, <<"urn:xmpp:tmp:profile">>).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
%%%=======================
|
|
|
|
%%%% gen_mod
|
|
|
|
|
|
|
|
start(Host, Opts) ->
|
2015-03-13 11:13:21 +01:00
|
|
|
mnesia:create_table(profile,
|
|
|
|
[{disc_only_copies, [node()]},
|
|
|
|
{attributes, record_info(fields, profile)}]),
|
|
|
|
ejabberd_hooks:add(remove_user, Host, ?MODULE,
|
|
|
|
remove_user, 50),
|
|
|
|
ejabberd_hooks:add(disco_sm_features, Host, ?MODULE,
|
|
|
|
get_sm_features, 50),
|
2016-03-19 17:56:49 +01:00
|
|
|
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue),
|
2015-03-13 11:13:21 +01:00
|
|
|
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
|
|
|
|
?NS_PROFILE, ?MODULE, process_sm_iq, IQDisc).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
stop(Host) ->
|
2015-03-13 11:13:21 +01:00
|
|
|
ejabberd_hooks:delete(remove_user, Host, ?MODULE,
|
|
|
|
remove_user, 50),
|
|
|
|
ejabberd_hooks:delete(disco_sm_features, Host, ?MODULE,
|
|
|
|
get_sm_features, 50),
|
|
|
|
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host,
|
|
|
|
?NS_PROFILE).
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
%%%=======================
|
|
|
|
%%%% Hooks
|
|
|
|
|
2015-03-13 11:13:21 +01:00
|
|
|
get_sm_features({error, _} = Acc, _From, _To, _Node,
|
|
|
|
_Lang) ->
|
2013-04-15 12:03:14 +02:00
|
|
|
Acc;
|
|
|
|
get_sm_features(Acc, _From, _To, Node, _Lang) ->
|
|
|
|
case Node of
|
2015-03-13 11:13:21 +01:00
|
|
|
[] ->
|
|
|
|
case Acc of
|
|
|
|
{result, Features} ->
|
|
|
|
{result, [?NS_PROFILE | Features]};
|
|
|
|
empty -> {result, [?NS_PROFILE]}
|
|
|
|
end;
|
|
|
|
_ -> Acc
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
remove_user(User, Server) ->
|
|
|
|
LUser = jlib:nodeprep(User),
|
|
|
|
LServer = jlib:nameprep(Server),
|
|
|
|
US = {LUser, LServer},
|
2015-03-13 11:13:21 +01:00
|
|
|
F = fun () -> mnesia:delete({profile, US}) end,
|
2013-04-15 12:03:14 +02:00
|
|
|
mnesia:transaction(F).
|
|
|
|
|
|
|
|
%%%=======================
|
|
|
|
%%%% IQ handler
|
|
|
|
|
2015-03-13 11:13:21 +01:00
|
|
|
process_sm_iq(From, To,
|
|
|
|
#iq{type = Type, sub_el = SubEl} = IQ) ->
|
2013-04-15 12:03:14 +02:00
|
|
|
case Type of
|
2015-03-13 11:13:21 +01:00
|
|
|
set ->
|
|
|
|
#jid{luser = LUser, lserver = LServer} = From,
|
|
|
|
process_sm_iq_set(LUser, LServer, SubEl, IQ);
|
|
|
|
get ->
|
|
|
|
#jid{luser = LUser, lserver = LServer} = To,
|
|
|
|
process_sm_iq_get(LUser, LServer, SubEl, IQ)
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
process_sm_iq_set(LUser, LServer, SubEl, IQ) ->
|
|
|
|
case lists:member(LServer, ?MYHOSTS) of
|
2015-03-13 11:13:21 +01:00
|
|
|
true ->
|
|
|
|
#xmlel{children = SubSubEls} = SubEl,
|
|
|
|
ElsList = [El
|
|
|
|
|| #xmlel{name = Name} = El
|
2016-03-08 00:39:39 +01:00
|
|
|
<- fxml:remove_cdata(SubSubEls),
|
2015-03-13 11:13:21 +01:00
|
|
|
Name == <<"x">>],
|
|
|
|
case ElsList of
|
|
|
|
[XData] ->
|
|
|
|
case set_profile(LUser, LServer, XData) of
|
|
|
|
ok -> IQ#iq{type = result, sub_el = []};
|
|
|
|
{error, Error} ->
|
|
|
|
IQ#iq{type = error, sub_el = [SubEl, Error]}
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
|
|
|
|
end;
|
|
|
|
false ->
|
|
|
|
IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
process_sm_iq_get(LUser, LServer, SubEl, IQ) ->
|
|
|
|
ReqFields = get_requested_fields(SubEl),
|
|
|
|
case get_profile(LUser, LServer, ReqFields) of
|
2015-03-13 11:13:21 +01:00
|
|
|
{ok, Fields} ->
|
|
|
|
XEl = #xmlel{name = <<"x">>,
|
|
|
|
attrs =
|
|
|
|
[{<<"xmlns">>, <<"jabber:x:data">>},
|
|
|
|
{<<"type">>, <<"result">>}],
|
|
|
|
children = Fields},
|
|
|
|
ProfileEl = #xmlel{name = <<"profile">>,
|
|
|
|
attrs = [{<<"xmlns">>, ?NS_PROFILE}],
|
|
|
|
children = [XEl]},
|
|
|
|
IQ#iq{type = result, sub_el = [ProfileEl]};
|
|
|
|
{error, user_not_found} ->
|
|
|
|
IQ#iq{type = error,
|
|
|
|
sub_el = [SubEl, ?ERR_SERVICE_UNAVAILABLE]};
|
|
|
|
{error, OtherError} ->
|
|
|
|
?ERROR_MSG("Problem found when getting profile of "
|
|
|
|
"~p@~p:~n~p",
|
|
|
|
[LUser, LServer, OtherError]),
|
|
|
|
IQ#iq{type = error,
|
|
|
|
sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
%%%=======================
|
|
|
|
%%%% Set profile
|
|
|
|
|
2015-03-13 11:13:21 +01:00
|
|
|
set_profile(LUser, LServer,
|
|
|
|
#xmlel{name = <<"x">>, children = Els}) ->
|
2013-04-15 12:03:14 +02:00
|
|
|
US = {LUser, LServer},
|
2015-03-13 11:13:21 +01:00
|
|
|
F = fun () ->
|
2013-04-15 12:03:14 +02:00
|
|
|
mnesia:write(#profile{us = US, fields = Els})
|
|
|
|
end,
|
|
|
|
case mnesia:transaction(F) of
|
2015-03-13 11:13:21 +01:00
|
|
|
{atomic, _} -> ok;
|
|
|
|
_ -> {error, ?ERR_INTERNAL_SERVER_ERROR}
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
%%%=======================
|
|
|
|
%%%% Get profile
|
|
|
|
|
|
|
|
get_profile(LUser, LServer, []) ->
|
|
|
|
US = {LUser, LServer},
|
2015-03-13 11:13:21 +01:00
|
|
|
F = fun () -> mnesia:read({profile, US}) end,
|
2013-04-15 12:03:14 +02:00
|
|
|
case mnesia:transaction(F) of
|
2015-03-13 11:13:21 +01:00
|
|
|
{atomic, [#profile{fields = Fields}]} -> {ok, Fields};
|
|
|
|
{atomic, []} -> {error, user_not_found};
|
|
|
|
OtherResult -> {error, OtherResult}
|
2013-04-15 12:03:14 +02:00
|
|
|
end;
|
|
|
|
get_profile(LUser, LServer, ReqFields) ->
|
|
|
|
case get_profile(LUser, LServer, []) of
|
2015-03-13 11:13:21 +01:00
|
|
|
{ok, Fields} ->
|
|
|
|
filter_profile_fields(Fields, ReqFields);
|
|
|
|
Other -> Other
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
filter_profile_fields(Fields, ReqFields) ->
|
|
|
|
filter_profile_fields(Fields, ReqFields, []).
|
|
|
|
|
2015-03-13 11:13:21 +01:00
|
|
|
filter_profile_fields(StoredFields,
|
|
|
|
[#xmlel{name = <<"field">>, attrs = Attrs,
|
|
|
|
children = []}
|
|
|
|
| ReqFields],
|
|
|
|
ResFields) ->
|
2013-04-15 12:03:14 +02:00
|
|
|
case lists:keysearch(Attrs, 3, StoredFields) of
|
2015-03-13 11:13:21 +01:00
|
|
|
{value, StoredField} ->
|
|
|
|
filter_profile_fields(StoredFields, ReqFields,
|
|
|
|
[StoredField | ResFields]);
|
|
|
|
false ->
|
|
|
|
filter_profile_fields(StoredFields, ReqFields,
|
|
|
|
ResFields)
|
2013-04-15 12:03:14 +02:00
|
|
|
end;
|
2015-03-13 11:13:21 +01:00
|
|
|
filter_profile_fields(StoredFields,
|
|
|
|
[_FieldForm | ReqFields], ResFields) ->
|
|
|
|
filter_profile_fields(StoredFields, ReqFields,
|
|
|
|
ResFields);
|
2013-04-15 12:03:14 +02:00
|
|
|
filter_profile_fields(_StoredFields, [], ResFields) ->
|
2015-03-13 11:13:21 +01:00
|
|
|
FieldFormType = #xmlel{name = <<"field">>,
|
|
|
|
attrs =
|
|
|
|
[{<<"var">>, <<"FORM_TYPE">>},
|
|
|
|
{<<"type">>, <<"hidden">>}],
|
|
|
|
children =
|
|
|
|
[#xmlel{name = <<"value">>, attrs = [],
|
|
|
|
children =
|
|
|
|
[{xmlcdata,
|
|
|
|
<<"urn:xmpp:tmp:profile">>}]}]},
|
2013-04-15 12:03:14 +02:00
|
|
|
{ok, [FieldFormType | lists:reverse(ResFields)]}.
|
|
|
|
|
|
|
|
%% TODO: la respuesta a una iq query de fields especificos ha de incluir
|
|
|
|
|
|
|
|
%%%=======================
|
|
|
|
%%%% Mnesia storage
|
|
|
|
|
|
|
|
%%%=======================
|
|
|
|
%%%% PubSub storage
|
|
|
|
|
|
|
|
%%%=======================
|
|
|
|
%%%% XML processing
|
|
|
|
|
|
|
|
%% Copied from exmpp_xml.erl, then customized
|
|
|
|
|
|
|
|
get_requested_fields(SubEl) ->
|
2016-03-08 00:39:39 +01:00
|
|
|
case fxml:get_subtag(SubEl, <<"x">>) of
|
2015-03-13 11:13:21 +01:00
|
|
|
false -> [];
|
|
|
|
XEl -> get_elements(XEl, <<"field">>)
|
2013-04-15 12:03:14 +02:00
|
|
|
end.
|
|
|
|
|
2015-03-13 11:13:21 +01:00
|
|
|
get_elements(#xmlel{name = <<"x">>,
|
|
|
|
children = Children},
|
|
|
|
Name) ->
|
2013-04-15 12:03:14 +02:00
|
|
|
get_elements2(Children, Name);
|
2015-03-13 11:13:21 +01:00
|
|
|
get_elements(_, _Name) -> [].
|
2013-04-15 12:03:14 +02:00
|
|
|
|
2015-03-13 11:13:21 +01:00
|
|
|
get_elements2([], _Name) -> [];
|
2013-04-15 12:03:14 +02:00
|
|
|
get_elements2(Children, Name) ->
|
|
|
|
lists:filter(filter_by_name(Name), Children).
|
|
|
|
|
|
|
|
filter_by_name(Searched_Name) ->
|
2015-03-13 11:13:21 +01:00
|
|
|
fun (XML_Element) ->
|
2013-04-15 12:03:14 +02:00
|
|
|
element_matches(XML_Element, Searched_Name)
|
|
|
|
end.
|
|
|
|
|
2015-03-13 11:13:21 +01:00
|
|
|
element_matches(#xmlel{name = Name}, Name) -> true;
|
|
|
|
element_matches(_XML_Element, _Name) -> false.
|
2013-04-15 12:03:14 +02:00
|
|
|
|
|
|
|
%%%================
|
|
|
|
|
|
|
|
%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=:
|
2015-03-13 11:13:21 +01:00
|
|
|
|