Optionally cache extauth users in mnesia (EJAB-641)

This commit is contained in:
Badlop 2010-05-10 16:42:54 +02:00
parent 6ac46c6171
commit 9476d8a2c3
7 changed files with 315 additions and 27 deletions

View File

@ -194,6 +194,9 @@ operation are as follows:
auth:User:Server:Password (check if a username/password pair is correct)
</LI><LI CLASS="li-itemize">isuser:User:Server (check if it&#X2019;s a valid user)
</LI><LI CLASS="li-itemize">setpass:User:Server:Password (set user&#X2019;s password)
</LI><LI CLASS="li-itemize">tryregister:User:Server:Password (try to register an account)
</LI><LI CLASS="li-itemize">removeuser:User:Server (remove this account)
</LI><LI CLASS="li-itemize">removeuser3:User:Server:Password (remove this account if the password is correct)
</LI></UL>
</LI></UL>
</LI><LI CLASS="li-itemize">write to stdout: AABB

View File

@ -176,6 +176,9 @@ That script is supposed to do theses actions, in an infinite loop:
\item auth:User:Server:Password (check if a username/password pair is correct)
\item isuser:User:Server (check if it's a valid user)
\item setpass:User:Server:Password (set user's password)
\item tryregister:User:Server:Password (try to register an account)
\item removeuser:User:Server (remove this account)
\item removeuser3:User:Server:Password (remove this account if the password is correct)
\end{itemize}
\end{itemize}
\item write to stdout: AABB

View File

@ -1030,14 +1030,13 @@ for user authentication. The syntax is:
</P><DL CLASS="description"><DT CLASS="dt-description"><B><TT>{auth_method, [Method, ...]}.</TT></B></DT></DL><P>The following authentication methods are supported by <TT>ejabberd</TT>:
</P><UL CLASS="itemize"><LI CLASS="li-itemize">
internal (default) &#X2014; See section&#XA0;<A HREF="#internalauth">3.1.4</A>.
</LI><LI CLASS="li-itemize">external &#X2014; There are <A HREF="http://www.ejabberd.im/extauth">some
example authentication scripts</A>.
</LI><LI CLASS="li-itemize">external &#X2014; See section&#XA0;<A HREF="#extauth">3.1.4</A>.
</LI><LI CLASS="li-itemize">ldap &#X2014; See section&#XA0;<A HREF="#ldap">3.2.5</A>.
</LI><LI CLASS="li-itemize">odbc &#X2014; See section&#XA0;<A HREF="#mysql">3.2.1</A>, <A HREF="#pgsql">3.2.3</A>,
<A HREF="#mssql">3.2.2</A> and <A HREF="#odbc">3.2.4</A>.
</LI><LI CLASS="li-itemize">anonymous &#X2014; See section&#XA0;<A HREF="#saslanonymous">3.1.4</A>.
</LI><LI CLASS="li-itemize">pam &#X2014; See section&#XA0;<A HREF="#pam">3.1.4</A>.
</LI></UL><P>Account creation is only supported by internal and odbc methods.</P><P> <A NAME="internalauth"></A> </P><!--TOC subsubsection Internal-->
</LI></UL><P>Account creation is only supported by internal, external and odbc methods.</P><P> <A NAME="internalauth"></A> </P><!--TOC subsubsection Internal-->
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A HREF="#internalauth">Internal</A></H4><!--SEC END --><P> <A NAME="internalauth"></A>
</P><P><TT>ejabberd</TT> uses its internal Mnesia database as the default authentication method.
The value <TT>internal</TT> will enable the internal authentication method.</P><P>Examples:
@ -1048,7 +1047,31 @@ authentication on <TT>example.net</TT>:
{host_config, "example.net", [{auth_method, [ldap]}]}.
</PRE></LI><LI CLASS="li-itemize">To use internal authentication on all virtual hosts:
<PRE CLASS="verbatim">{auth_method, internal}.
</PRE></LI></UL><P> <A NAME="saslanonymous"></A> </P><!--TOC subsubsection SASL Anonymous and Anonymous Login-->
</PRE></LI></UL><P> <A NAME="extauth"></A> </P><!--TOC subsubsection External Script-->
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A HREF="#extauth">External Script</A></H4><!--SEC END --><P> <A NAME="extauth"></A>
</P><P>In this authentication method, when <TT>ejabberd</TT> starts,
it start a script, and calls it to perform authentication tasks.</P><P>The server administrator can write the external authentication script
in any language.
The details on the interface between ejabberd and the script are described
in the <TT>ejabberd Developers Guide</TT>.
There are also <A HREF="http://www.ejabberd.im/extauth">several example authentication scripts</A>.</P><P>These are the specific options:
</P><DL CLASS="description"><DT CLASS="dt-description">
<B><TT>{extauth_program, PathToScript}</TT></B></DT><DD CLASS="dd-description">
Indicate in this option the full path to the external authentication script.
The script must be executable by ejabberd.</DD><DT CLASS="dt-description"><B><TT>{extauth_cache, false|CacheTimeInteger}</TT></B></DT><DD CLASS="dd-description">
The value <TT>false</TT> disables the caching feature, this is the default.
The integer <TT>0</TT> (zero) enables caching for statistics, but doesn&#X2019;t use that cached information to authenticate users.
If another integer value is set, caching is enabled both for statistics and for authentication:
the CacheTimeInteger indicates the number of seconds that ejabberd can reuse
the authentication information since the user last disconnected,
to verify again the user authentication without querying again the extauth script.
Note: caching should not be enabled in a host if internal auth is also enabled.
If caching is enabled, <TT>mod_last</TT> or <TT>mod_last_odbc</TT> must be enabled also in that vhost.
</DD></DL><P>This example sets external authentication, the extauth script, and enables caching for 10 minutes:
</P><PRE CLASS="verbatim">{auth_method, [external]}.
{extauth_program, "/etc/ejabberd/JabberAuth.class.php"}.
{extauth_cache, 600}.
</PRE><P> <A NAME="saslanonymous"></A> </P><!--TOC subsubsection SASL Anonymous and Anonymous Login-->
<H4 CLASS="subsubsection"><!--SEC ANCHOR --><A HREF="#saslanonymous">SASL Anonymous and Anonymous Login</A></H4><!--SEC END --><P> <A NAME="saslanonymous"></A>
</P><P>The value <TT>anonymous</TT> will enable the internal authentication method.</P><P>The anonymous authentication method can be configured with the following
options. Remember that you can use the <TT>host_config</TT> option to set virtual

View File

@ -1187,8 +1187,7 @@ for user authentication. The syntax is:
The following authentication methods are supported by \ejabberd{}:
\begin{itemize}
\item internal (default) --- See section~\ref{internalauth}.
\item external --- There are \footahref{http://www.ejabberd.im/extauth}{some
example authentication scripts}.
\item external --- See section~\ref{extauth}.
\item ldap --- See section~\ref{ldap}.
\item odbc --- See section~\ref{mysql}, \ref{pgsql},
\ref{mssql} and \ref{odbc}.
@ -1196,7 +1195,7 @@ The following authentication methods are supported by \ejabberd{}:
\item pam --- See section~\ref{pam}.
\end{itemize}
Account creation is only supported by internal and odbc methods.
Account creation is only supported by internal, external and odbc methods.
\makesubsubsection{internalauth}{Internal}
\ind{internal authentication}\ind{Mnesia}
@ -1218,6 +1217,42 @@ Examples:
\end{verbatim}
\end{itemize}
\makesubsubsection{extauth}{External Script}
\ind{external authentication}
In this authentication method, when \ejabberd{} starts,
it start a script, and calls it to perform authentication tasks.
The server administrator can write the external authentication script
in any language.
The details on the interface between ejabberd and the script are described
in the \term{ejabberd Developers Guide}.
There are also \footahref{http://www.ejabberd.im/extauth}{several example authentication scripts}.
These are the specific options:
\begin{description}
\titem{\{extauth\_program, PathToScript\}}
Indicate in this option the full path to the external authentication script.
The script must be executable by ejabberd.
\titem{\{extauth\_cache, false|CacheTimeInteger\}}
The value \term{false} disables the caching feature, this is the default.
The integer \term{0} (zero) enables caching for statistics, but doesn't use that cached information to authenticate users.
If another integer value is set, caching is enabled both for statistics and for authentication:
the CacheTimeInteger indicates the number of seconds that ejabberd can reuse
the authentication information since the user last disconnected,
to verify again the user authentication without querying again the extauth script.
Note: caching should not be enabled in a host if internal auth is also enabled.
If caching is enabled, \term{mod\_last} or \term{mod\_last\_odbc} must be enabled also in that vhost.
\end{description}
This example sets external authentication, the extauth script, and enables caching for 10 minutes:
\begin{verbatim}
{auth_method, [external]}.
{extauth_program, "/etc/ejabberd/JabberAuth.class.php"}.
{extauth_cache, 600}.
\end{verbatim}
\makesubsubsection{saslanonymous}{SASL Anonymous and Anonymous Login}
\ind{sasl anonymous}\ind{anonymous login}

0
examples/extauth/check_pass_null.pl Normal file → Executable file
View File

View File

@ -35,6 +35,9 @@
try_register/3,
dirty_get_registered_users/0,
get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
get_vh_registered_users_number/2,
get_password/2,
get_password_s/2,
is_user_exists/2,
@ -43,45 +46,87 @@
plain_password_required/0
]).
-include("ejabberd.hrl").
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start(Host) ->
extauth:start(
Host, ejabberd_config:get_local_option({extauth_program, Host})),
ok.
case check_cache_last_options(Host) of
cache ->
ok = ejabberd_auth_internal:start(Host);
no_cache ->
ok
end.
check_cache_last_options(Server) ->
%% if extauth_cache is enabled, then a mod_last module must also be enabled
case get_cache_option(Server) of
false -> no_cache;
{true, _CacheTime} ->
case get_mod_last_enabled(Server) of
no_mod_last ->
?ERROR_MSG("In host ~p extauth is used, extauth_cache is enabled but "
"mod_last is not enabled.", [Server]),
no_cache;
_ -> cache
end
end.
plain_password_required() ->
true.
check_password(User, Server, Password) ->
extauth:check_password(User, Server, Password) andalso Password /= "".
case get_cache_option(Server) of
false -> check_password_extauth(User, Server, Password);
{true, CacheTime} -> check_password_cache(User, Server, Password, CacheTime)
end.
check_password(User, Server, Password, _Digest, _DigestGen) ->
check_password(User, Server, Password).
set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of
true -> ok;
true -> set_password_internal(User, Server, Password),
ok;
_ -> {error, unknown_problem}
end.
try_register(_User, _Server, _Password) ->
{error, not_allowed}.
try_register(User, Server, Password) ->
case get_cache_option(Server) of
false -> try_register_extauth(User, Server, Password);
{true, _CacheTime} -> try_register_external_cache(User, Server, Password)
end.
%% TODO
%% Return the list of all users handled by external
dirty_get_registered_users() ->
[].
ejabberd_auth_internal:dirty_get_registered_users().
get_vh_registered_users(_Server) ->
[].
get_vh_registered_users(Server) ->
ejabberd_auth_internal:get_vh_registered_users(Server).
get_password(_User, _Server) ->
false.
get_vh_registered_users(Server, Data) ->
ejabberd_auth_internal:get_vh_registered_users(Server, Data).
get_password_s(_User, _Server) ->
"".
get_vh_registered_users_number(Server) ->
ejabberd_auth_internal:get_vh_registered_users_number(Server).
get_vh_registered_users_number(Server, Data) ->
ejabberd_auth_internal:get_vh_registered_users_number(Server, Data).
%% The password can only be returned if cache is enabled, cached info exists and is fresh enough.
get_password(User, Server) ->
case get_cache_option(Server) of
false -> false;
{true, CacheTime} -> get_password_cache(User, Server, CacheTime)
end.
get_password_s(User, Server) ->
case get_password(User, Server) of
false -> [];
Other -> Other
end.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
@ -91,9 +136,169 @@ is_user_exists(User, Server) ->
_:Error -> {error, Error}
end.
remove_user(_User, _Server) ->
{error, not_allowed}.
remove_user(User, Server) ->
case extauth:remove_user(User, Server) of
false -> false;
true ->
case get_cache_option(Server) of
false -> false;
{true, _CacheTime} ->
ejabberd_auth_internal:remove_user(User, Server)
end
end.
remove_user(_User, _Server, _Password) ->
not_allowed.
remove_user(User, Server, Password) ->
case extauth:remove_user(User, Server, Password) of
false -> false;
true ->
case get_cache_option(Server) of
false -> false;
{true, _CacheTime} ->
ejabberd_auth_internal:remove_user(User, Server, Password)
end
end.
%%%
%%% Extauth cache management
%%%
%% @spec (Host::string()) -> false | {true, CacheTime::integer()}
get_cache_option(Host) ->
case ejabberd_config:get_local_option({extauth_cache, Host}) of
CacheTime when is_integer(CacheTime) -> {true, CacheTime};
_ -> false
end.
%% @spec (User, Server, Password) -> true | false
check_password_extauth(User, Server, Password) ->
extauth:check_password(User, Server, Password) andalso Password /= "".
%% @spec (User, Server, Password) -> true | false
try_register_extauth(User, Server, Password) ->
extauth:try_register(User, Server, Password).
check_password_cache(User, Server, Password, CacheTime) ->
case get_last_access(User, Server) of
online ->
check_password_internal(User, Server, Password);
never ->
check_password_external_cache(User, Server, Password);
mod_last_required ->
?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []),
check_password_external_cache(User, Server, Password);
TimeStamp ->
%% If last access exists, compare last access with cache refresh time
case is_fresh_enough(TimeStamp, CacheTime) of
%% If no need to refresh, check password against Mnesia
true ->
case check_password_internal(User, Server, Password) of
%% If password valid in Mnesia, accept it
true ->
true;
%% Else (password nonvalid in Mnesia), check in extauth and cache result
false ->
check_password_external_cache(User, Server, Password)
end;
%% Else (need to refresh), check in extauth and cache result
false ->
check_password_external_cache(User, Server, Password)
end
end.
get_password_internal(User, Server) ->
ejabberd_auth_internal:get_password(User, Server).
%% @spec (User, Server, CacheTime) -> false | Password::string()
get_password_cache(User, Server, CacheTime) ->
case get_last_access(User, Server) of
online ->
get_password_internal(User, Server);
never ->
false;
mod_last_required ->
?ERROR_MSG("extauth is used, extauth_cache is enabled but mod_last is not enabled in that host", []),
false;
TimeStamp ->
case is_fresh_enough(TimeStamp, CacheTime) of
true ->
get_password_internal(User, Server);
false ->
false
end
end.
%% Check the password using extauth; if success then cache it
check_password_external_cache(User, Server, Password) ->
case check_password_extauth(User, Server, Password) of
true ->
set_password_internal(User, Server, Password), true;
false ->
false
end.
%% Try to register using extauth; if success then cache it
try_register_external_cache(User, Server, Password) ->
case try_register_extauth(User, Server, Password) of
{atomic, ok} = R ->
set_password_internal(User, Server, Password),
R;
_ -> {error, not_allowed}
end.
%% @spec (User, Server, Password) -> true | false
check_password_internal(User, Server, Password) ->
ejabberd_auth_internal:check_password(User, Server, Password).
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
set_password_internal(User, Server, Password) ->
ejabberd_auth_internal:set_password(User, Server, Password).
%% @spec (TimeLast, CacheTime) -> true | false
%% TimeLast = online | never | integer()
%% CacheTime = integer() | false
is_fresh_enough(online, _CacheTime) ->
true;
is_fresh_enough(never, _CacheTime) ->
false;
is_fresh_enough(TimeStampLast, CacheTime) ->
{MegaSecs, Secs, _MicroSecs} = now(),
Now = MegaSecs * 1000000 + Secs,
(TimeStampLast + CacheTime > Now).
%% @spec (User, Server) -> online | never | mod_last_required | TimeStamp::integer()
%% Code copied from mod_configure.erl
%% Code copied from web/ejabberd_web_admin.erl
%% TODO: Update time format to XEP-0202: Entity Time
get_last_access(User, Server) ->
case ejabberd_sm:get_user_resources(User, Server) of
[] ->
_US = {User, Server},
case get_last_info(User, Server) of
mod_last_required ->
mod_last_required;
not_found ->
never;
{ok, Timestamp, _Status} ->
Timestamp
end;
_ ->
online
end.
%% @spec (User, Server) -> {ok, Timestamp, Status} | not_found | mod_last_required
get_last_info(User, Server) ->
case get_mod_last_enabled(Server) of
mod_last -> mod_last:get_last_info(User, Server);
mod_last_odbc -> mod_last_odbc:get_last_info(User, Server);
mod_mod_last -> mod_last_required
end.
%% @spec (Server) -> mod_last | mod_last_odbc | no_mod_last
get_mod_last_enabled(Server) ->
ML = lists:member(mod_last, gen_mod:loaded_modules(Server)),
MLO = lists:member(mod_last_odbc, gen_mod:loaded_modules(Server)),
case {ML, MLO} of
{true, _} -> mod_last;
{false, true} -> mod_last_odbc;
{false, false} -> no_mod_last
end.

View File

@ -27,8 +27,15 @@
-module(extauth).
-author('leifj@it.su.se').
-export([start/2, stop/1, init/2,
check_password/3, set_password/3, is_user_exists/2]).
-export([start/2,
stop/1,
init/2,
check_password/3,
set_password/3,
try_register/3,
remove_user/2,
remove_user/3,
is_user_exists/2]).
-include("ejabberd.hrl").
@ -56,6 +63,18 @@ is_user_exists(User, Server) ->
set_password(User, Server, Password) ->
call_port(Server, ["setpass", User, Server, Password]).
try_register(User, Server, Password) ->
case call_port(Server, ["tryregister", User, Server, Password]) of
true -> {atomic, ok};
false -> {error, not_allowed}
end.
remove_user(User, Server) ->
call_port(Server, ["removeuser", User, Server]).
remove_user(User, Server, Password) ->
call_port(Server, ["removeuser3", User, Server, Password]).
call_port(Server, Msg) ->
LServer = jlib:nameprep(Server),
gen_mod:get_module_proc(LServer, eauth) ! {call, self(), Msg},