Browse files

Changed the way authentication is done, added support for multiple au…

…thentication methods to be used for one directory and changed so that the

401 page can be customized similarly to the 404 page.

1. Renamed yaws_401.erl to yaws_outmod.erl, which is probably a better name considering it's current use(it also displays the crashmsg).
2. Fixed the Makefile accordingly.
2. Changed so that the auth record not only contains the authentication methods but also the headers that should be displayed for the
directory.
3. The headers are added in yaws_config.erl when the config file is parsed, the authmods now has to have a function get_headers/1 that
returns the http headers that are to be displayed.
3. is_auth now goes through the list of directories and when a matching directory is found it calls handle_auth which does the actual
checking.
4. handle_auth checks all the methods specified in the config file, if _one_ returns true, the user is authorized to view the page.
5. Removed deliver_401 and changed so that handle_ut is called with UrlType = unauthorized.
6. Changed handle_ut to handle UrlType = unauthorized, it handles it similarly to when it displays 404. It pulls the errormod_401 from the
GC and calls out401 to display the actual page.
7. Added a function outh_set_auth/1 to yaws.erl which takes a string or a {realm, Realm} tuple and returns the corresponding WWW-Authenticate
header.
8. Fixed so that the headers are displayed when the 401 page is displayed.
9. Removed the special handling of appmod in handle_request. Previously authmods would return appmod when they wanted the 401 page to be
displayed, this is no longer neccessary.
10. Removed the out function from authmod_gssapi.erl.
11. Changed the behaviour of authmod_gssapi so that it simply returns fales when it doesn't find called with the correct headers.
Previously it would crash.

TODO: Add support for multiple authmods and Pam modules for one directory.
Rewrite authmod_gssapi so that it simply returns true or false.
Fix a bug in yaws_ls, it links directories without the final / which means that every time you enter a directory first a 302 page is sent with
a redirect to "dir/".
Properly sort the WWW-authenticate headers, apparently the order mathers. For example Negoiate needs to be above Basic realm for
it to use Negoiate.
  • Loading branch information...
1 parent f01eff3 commit c1cea992847be923ddce1338c115a0f820755f62 @faal faal committed Apr 15, 2009
Showing with 174 additions and 122 deletions.
  1. +5 −3 include/yaws.hrl
  2. +1 −1 src/Makefile
  3. +9 −20 src/authmod_gssapi.erl
  4. +18 −2 src/yaws.erl
  5. +20 −2 src/yaws_config.erl
  6. +23 −2 src/{yaws_404.erl → yaws_outmod.erl}
  7. +98 −92 src/yaws_server.erl
View
8 include/yaws.hrl
@@ -172,8 +172,9 @@
authdirs = [],
partial_post_size = nolimit,
appmods = [], %% list of modules for this app
- errormod_404 = yaws_404, %% the default 404 error module
- errormod_crash = yaws_404, %% use the same module for crashes
+ errormod_401 = yaws_outmod, %% the default 401 error module
+ errormod_404 = yaws_outmod, %% the default 404 error module
+ errormod_crash = yaws_outmod, %% use the same module for crashes
arg_rewrite_mod = yaws,
opaque = [], %% useful in embedded mode
start_mod, %% user provided module to be started
@@ -197,7 +198,8 @@
{dir = [],
realm = "",
type = "Basic",
- users = [],
+ headers = [], %% headers to send on 401
+ users = [], %% list of {User, Password} tuples
mod = [], %% authentication module callback
pam = false %% should we use pam to auth a user
}).
View
2 src/Makefile
@@ -26,7 +26,7 @@ MODULES=yaws \
mime_type_c \
mime_types \
yaws_session_server \
- yaws_404 \
+ yaws_outmod \
yaws_revproxy \
yaws_html \
yaws_log_file_h \
View
29 src/authmod_gssapi.erl
@@ -80,7 +80,7 @@
start/1,
stop/0,
auth/2,
- out/1
+ get_header/0
]).
-include("yaws.hrl").
@@ -133,9 +133,6 @@ auth(Arg, Auth) when is_record(Arg, arg),
?INFO("~p~n", [?MODULE]),
case H#headers.authorization of
- undefined ->
- ?INFO("Request auth~n"),
- {appmod, ?MODULE};
{_, _, "Negotiate " ++ Data} ->
?INFO("Negotiate~n", []),
Bin = base64:decode(Data),
@@ -154,23 +151,15 @@ auth(Arg, Auth) when is_record(Arg, arg),
E ->
?ERROR("spnego error ~p~n", [E]),
throw(error)
- end
+ end;
+ _ ->
+ ?INFO("Request auth~n"),
+ {appmod, ?MODULE}
end.
-
-out(_Arg) ->
- [{status, 401},
- {header, ["WWW-Authenticate:", "Negotiate"]},
- {ehtml,
- [{html,[],
- [
- {body, [],
- [{h1,[], "401 authentication needed"}
- ]
- }
- ]
- }
- ]
- } ].
+
+%% The header that is set when authentication fails
+get_header() ->
+ yaws:make_www_authenticate_header("Negotiate").
start_opaque(Opaque) when is_list(Opaque) ->
View
20 src/yaws.erl
@@ -55,6 +55,7 @@
outh_set_content_length/1,
outh_set_dcc/2,
outh_set_transfer_encoding_off/0,
+ outh_set_auth/1,
outh_fix_doclose/0,
dcc/2]).
@@ -1221,6 +1222,19 @@ outh_set_transfer_encoding_off() ->
make_transfer_encoding_chunked_header(false)},
put(outh, H2).
+outh_set_auth([]) ->
+ ok;
+
+outh_set_auth(Headers) ->
+ H = get(outh),
+ L = H#outh.www_authenticate,
+ H2 = case L of
+ undefined ->
+ H#outh{www_authenticate = Headers};
+ _ ->
+ H#outh{www_authenticate = H#outh.www_authenticate ++ Headers}
+ end,
+ put(outh, H2).
outh_fix_doclose() ->
H = get(outh),
@@ -1380,9 +1394,11 @@ make_transfer_encoding_chunked_header(true) ->
make_transfer_encoding_chunked_header(false) ->
undefined.
-make_www_authenticate_header(Realm) ->
- ["WWW-Authenticate: Basic realm=\"", Realm, ["\"\r\n"]].
+make_www_authenticate_header({realm, Realm}) ->
+ ["WWW-Authenticate: Basic realm=\"", Realm, ["\"\r\n"]];
+make_www_authenticate_header(Method) ->
+ ["WWW-Authenticate: ", Method, ["\r\n"]].
make_date_header() ->
N = element(2, now()),
View
22 src/yaws_config.erl
@@ -808,6 +808,10 @@ fload(FD, server, GC, C, Cs, Lno, Chars) ->
C2 = C#sconf{errormod_crash = list_to_atom(Module)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
+ ["errormod_401", '=' , Module] ->
+ C2 = C#sconf{errormod_401 = list_to_atom(Module)},
+ fload(FD, server, GC, C2, Cs, Lno+1, Next);
+
["arg_rewrite_mod", '=', Module] ->
C2 = C#sconf{arg_rewrite_mod = list_to_atom(Module)},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
@@ -1054,7 +1058,11 @@ fload(FD, server_auth, GC, C, Cs, Lno, Chars, Auth) ->
A2 = Auth#auth{realm = Realm},
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["authmod", '=', Mod] ->
- A2 = Auth#auth{mod = list_to_atom(Mod)},
+ %% Add the auth header for the mod
+ Mod2 = list_to_atom(Mod),
+ code:ensure_loaded(Mod2),
+ H = Mod2:get_header() ++ Auth#auth.headers,
+ A2 = Auth#auth{mod = Mod2, headers = H},
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
["user", '=', User] ->
case (catch string:tokens(User, ":")) of
@@ -1068,7 +1076,17 @@ fload(FD, server_auth, GC, C, Cs, Lno, Chars, Auth) ->
A2 = Auth#auth{pam=Serv},
fload(FD, server_auth, GC, C, Cs, Lno+1, Next, A2);
['<', "/auth", '>'] ->
- C2 = C#sconf{authdirs = [Auth|C#sconf.authdirs]},
+ Pam = Auth#auth.pam,
+ Users = Auth#auth.users,
+ Realm = Auth#auth.realm,
+ A2 = case {Pam, Users} of
+ {false, []} ->
+ Auth;
+ _ ->
+ H = Auth#auth.headers ++ yaws:make_www_authenticate_header({realm, Realm}),
+ Auth#auth{headers = H}
+ end,
+ C2 = C#sconf{authdirs = [A2|C#sconf.authdirs]},
fload(FD, server, GC, C2, Cs, Lno+1, Next);
[H|T] ->
{error, ?F("Unexpected input ~p at line ~w", [[H|T], Lno])}
View
25 src/yaws_404.erl → src/yaws_outmod.erl
@@ -1,11 +1,11 @@
%%%----------------------------------------------------------------------
-%%% File : yaws_404.erl
+%%% File : yaws_outmod.erl
%%% Author : Claes Wikstrom <klacke@hyber.org>
%%% Purpose :
%%% Created : 4 Nov 2002 by Claes Wikstrom <klacke@hyber.org>
%%%----------------------------------------------------------------------
--module(yaws_404).
+-module(yaws_outmod).
-author('klacke@hyber.org').
@@ -14,6 +14,7 @@
-export([out404/3,
out404/1,
+ out401/1,
crashmsg/3]).
@@ -37,6 +38,26 @@ out404(Arg, GC, SC) ->
+%% The default error 401 error delivery module
+%% This function can be used to generate
+%% a special page on 401's (it doesn't even have to be a 401)
+
+
+out401(_Arg) ->
+ [{status, 401},
+ %{header, ["WWW-Authenticate:", "Negotiate\r\n", "WWW-Authenticate: ", "Basic realm=\"\""]},
+ {ehtml,
+ [{html,[],
+ [
+ {body, [],
+ [{h1,[], "401 authentication needed"}
+ ]
+ }
+ ]
+ }
+ ]
+ } ].
+
not_found_body(Path, _GC, _SC) ->
L = ["<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
"<HTML><HEAD>"
View
190 src/yaws_server.erl
@@ -1474,28 +1474,6 @@ handle_request(CliSock, ARG, N) ->
SC#sconf.redirect_map),
case {IsAuth, IsRev, IsRedirect} of
- {{appmod, Mod}, _, _} ->
- %%This isn't the standard 'appmod' branch
- %%- this is an appmod call as optionally
- %% specified by the
- %% output of an auth module.
- UT = #urltype{
- type = appmod,
- data = {Mod, undefined},
- path = DecPath
- },
-
-
- %%arg.prepath ?
- ARG2 = ARG1#arg{
- server_path = DecPath,
- querydata= QueryString,
- prepath=undefined,
- pathinfo=undefined,
- appmod_prepath=undefined,
- appmoddata=undefined
- },
- handle_ut(CliSock, ARG2, UT, N);
{_, _, {true, Redir}} ->
deliver_302_map(CliSock, Req, ARG1, Redir);
{true, false, _} ->
@@ -1547,10 +1525,14 @@ handle_request(CliSock, ARG, N) ->
{true, {true, PP}, _} ->
yaws_revproxy:init(CliSock, ARG1, DecPath,
QueryString, PP, N);
- {false, _, _} ->
+ {false_403, _, _} ->
deliver_403(CliSock, Req);
- {{false, Realm}, _, _} ->
- deliver_401(CliSock, Req, Realm)
+ {false, _, _} ->
+ UT = #urltype{
+ type = unauthorized,
+ path = DecPath
+ },
+ handle_ut(CliSock, ARG, UT, N)
end
end
end;
@@ -1574,58 +1556,85 @@ set_auth_user(ARG, User) ->
H2 = H#headers{authorization = Auth},
ARG#arg{headers = H2}.
-is_auth(_ARG, _Req_dir, _H, [] ) ->
- true;
-is_auth(ARG, Req_dir,H,[{Auth_dir,
- Auth=#auth{realm=Realm, users=Users,
- pam=Pam, mod=Mod}}|T] ) ->
+%% Call is_auth(...)/5 with a default value.
+is_auth(ARG, Req_dir, H, L) ->
+ is_auth(ARG, Req_dir, H, L, {true, []}).
+
+%% Either no authentication was done or all methods returned false
+is_auth(_ARG, _Req_dir, _H, [], {Ret, Auth_headers}) ->
+ yaws:outh_set_auth(Auth_headers),
+ Ret;
+
+is_auth(ARG, Req_dir, H, [{Auth_dir, Auth_methods}|T], {Ret, Auth_headers}) ->
case lists:prefix(Auth_dir, Req_dir) of
- true when Mod /= [] ->
- case catch Mod:auth(ARG, Auth) of
- {'EXIT', Reason} ->
- error_logger:format("authmod crashed: ~p~n", [Reason]),
- {false, ""};
- {appmod, AppMod} ->
- {appmod, AppMod};
- {true, User} ->
- {true, User};
- true ->
- true;
- {false, Realm} ->
- maybe_auth_log({401, Realm}, ARG),
- {false, Realm};
- _ ->
- maybe_auth_log(403, ARG),
- false
- end;
- true ->
- case H#headers.authorization of
- undefined ->
- maybe_auth_log({401, Realm}, ARG),
- {false, Realm};
- {User, Password, _OrigString} ->
- case member({User, Password}, Users) of
- true ->
- maybe_auth_log({ok, User}, ARG),
- true;
- false when Pam == false ->
- maybe_auth_log({401, User, Password}, ARG),
- {false, Realm};
- false when Pam /= false ->
- case yaws_pam:auth(User, Password) of
- {yes, _} ->
- maybe_auth_log({ok, User}, ARG),
- true;
- {no, _Rsn} ->
- maybe_auth_log({401, User, Password}, ARG),
- {false, Realm}
- end
- end
- end;
- false ->
- is_auth(ARG, Req_dir, H, T)
+ true ->
+ Auth_H = H#headers.authorization,
+ case handle_auth(ARG, Auth_H, Auth_methods) of
+
+ %% If we auth using an authmod we need to return User
+ %% so that we can set it in ARG.
+ {true, User} ->
+ {true, User};
+ false ->
+ L = Auth_methods#auth.headers,
+ is_auth(ARG, Req_dir, H, T, {false, L ++ Auth_headers});
+ Is_auth ->
+ Is_auth
+ end;
+ false ->
+ is_auth(ARG, Req_dir, H, T, {Ret, Auth_headers})
end.
+handle_auth(ARG, _Auth_H, #auth{realm = Realm, users=[], pam=false, mod = []}) ->
+ maybe_auth_log({401, Realm}, ARG),
+ false;
+
+handle_auth(ARG, Auth_H, Auth_methods = #auth{mod = Mod}) when Mod /= [] ->
+ case catch Mod:auth(ARG, Auth_methods) of
+ {'EXIT', Reason} ->
+ error_logger:format("authmod crashed: ~p~n", [Reason]),
+ false;
+
+ %% appmod means the auth headers are undefined, i.e. false.
+ %% TODO: change so that authmods simply return true/false
+ {appmod, _AppMod} ->
+ handle_auth(ARG, Auth_H, Auth_methods#auth{mod = []});
+ {true, User} ->
+ {true, User};
+ true ->
+ true;
+ {false, _Realm} ->
+ handle_auth(ARG, Auth_H, Auth_methods#auth{mod = []});
+ _ ->
+ maybe_auth_log(403, ARG),
+ false_403
+ end;
+
+%% if the headers are undefined we do not need to check Pam or Users
+handle_auth(ARG, undefined, Auth_methods) ->
+ handle_auth(ARG, undefined, Auth_methods#auth{pam = false, users= []});
+
+handle_auth(ARG, {User, Password, OrigString}, Auth_methods = #auth{pam = Pam}) when Pam /= false ->
+ case yaws_pam:auth(User, Password) of
+ {yes, _} ->
+ maybe_auth_log({ok, User}, ARG),
+ true;
+ {no, _Rsn} ->
+ handle_auth(ARG, {User, Password, OrigString}, Auth_methods#auth{pam = false})
+ end;
+
+
+
+
+handle_auth(ARG, {User, Password, OrigString}, Auth_methods = #auth{users = Users}) when Users /= [] ->
+ case member({User, Password}, Users) of
+ true ->
+ maybe_auth_log({ok, User}, ARG),
+ true;
+ false ->
+ handle_auth(ARG, {User, Password, OrigString}, Auth_methods#auth{users = []})
+ end.
+
is_revproxy(_,[]) ->
false;
@@ -1650,7 +1659,21 @@ is_redirect_map(Path, [E={Prefix, _URL, _AppendMode}|Tail]) ->
%% Return values:
%% continue, done, {page, Page}
-
+handle_ut(CliSock, ARG, UT = #urltype{type = unauthorized}, N) ->
+ Req = ARG#arg.req,
+ H = ARG#arg.headers,
+ SC = get(sc),
+ yaws:outh_set_dyn_headers(Req, H, UT),
+ deliver_dyn_part(CliSock,
+ 0,
+ "appmod",
+ N,
+ ARG,
+ UT,
+ fun(A)->(SC#sconf.errormod_401):out401(A)
+ end,
+ fun(A)->finish_up_dyn_file(A, CliSock)
+ end);
handle_ut(CliSock, ARG, UT = #urltype{type = error}, N) ->
Req = ARG#arg.req,
@@ -2056,23 +2079,6 @@ deliver_xxx(CliSock, _Req, Code, ExtraHtml) ->
deliver_400(CliSock, Req) ->
deliver_xxx(CliSock, Req, 400).% Bad Request
-deliver_401(CliSock, _Req, Realm) ->
- B = list_to_binary("<html> <h1> 401 authentication needed "
- "</h1></html>"),
- H = #outh{status = 401,
- doclose = true,
- chunked = false,
- server = yaws:make_server_header(),
- connection = yaws:make_connection_close_header(true),
- content_length = yaws:make_content_length_header(size(B)),
- www_authenticate = yaws:make_www_authenticate_header(Realm),
- contlen = size(B),
- content_type = yaws:make_content_type_header("text/html")},
- put(outh, H),
- accumulate_content(B),
- deliver_accumulated(CliSock),
- done.
-
deliver_403(CliSock, Req) ->
deliver_xxx(CliSock, Req, 403). % Forbidden

0 comments on commit c1cea99

Please sign in to comment.