Permalink
Browse files

implement actual Basic Authentication

  • Loading branch information...
1 parent 900312a commit 2ad2362c8a28d5f82de446807b76e4290dfd7d5d @martinrehfeld committed Apr 23, 2012
Showing with 135 additions and 3 deletions.
  1. +64 −3 src/elli_basicauth.erl
  2. +71 −0 test/elli_basicauth_tests.erl
View
@@ -1,15 +1,29 @@
%% @doc Elli basicauth overview
%%
%% This middleware provides basic authentication to protect
-%% Reqs based on a user-configured Predicate
+%% Reqs based on a user-configured authentication function
-module(elli_basicauth).
-behaviour(elli_handler).
-export([handle/2, handle_event/3]).
-handle(_Req, _Config) ->
- ignore.
+handle(Req, Config) ->
+ {User, Password} = credentials(Req),
+
+ case apply(auth_fun(Config), [Req, User, Password]) of
+ unauthorized ->
+ throw({401,
+ [{<<"WWW-Authenticate">>,
+ <<"Basic realm=\"Secure Area\"">>}],
+ <<"Unauthorized">>});
+
+ forbidden ->
+ throw({403, [], <<"Forbidden">>});
+
+ _ ->
+ ignore
+ end.
handle_event(request_complete, [_Req, _ResponseCode, _ResponseHeaders,
@@ -36,3 +50,50 @@ handle_event(client_timeout, [_When], _Config) ->
handle_event(elli_startup, [], _Config) ->
ok.
+
+
+%%
+%% INTERNAL HELPERS
+%%
+
+auth_fun(Config) ->
+ proplists:get_value(auth_fun, Config,
+ %% default to forbidden in case of missing auth_fun config
+ fun (_Req, _User, _Password) ->
+ forbidden
+ end).
+
+
+credentials(Req) ->
+ case authorization_header(Req) of
+ undefined ->
+ {undefined, undefined};
+
+ AuthorizationHeader ->
+ credentials_from_header(AuthorizationHeader)
+ end.
+
+
+authorization_header(Req) ->
+ elli:get_header(<<"Authorization">>, Req).
+
+
+credentials_from_header(AuthorizationHeader) ->
+ case binary:split(AuthorizationHeader, <<$ >>) of
+ [<<"Basic">>, EncodedCredentials] ->
+ decoded_credentials(EncodedCredentials);
+
+ _ ->
+ {undefined, undefined}
+ end.
+
+
+decoded_credentials(EncodedCredentials) ->
+ DecodedCredentials = base64:decode(EncodedCredentials),
+ case binary:split(DecodedCredentials, <<$:>>) of
+ [User, Password] ->
+ {User, Password};
+
+ _ ->
+ {undefined, undefined}
+ end.
@@ -0,0 +1,71 @@
+-module(elli_basicauth_tests).
+-include_lib("eunit/include/eunit.hrl").
+
+-define(USER, <<"Aladdin">>).
+-define(PASSWORD, <<"open sesame">>).
+-define(VALID_CREDENTIALS, <<"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>).
+-define(INVALID_CREDENTIALS, <<"Basic cGxhaW46d3Jvbmc=">>).
+
+%%
+%% TESTS
+%%
+
+no_credentials_test() ->
+ meck:new(elli),
+ meck:expect(elli, get_header,
+ fun (<<"Authorization">>, mock_request) ->
+ undefined
+ end),
+
+ Result = (catch elli_basicauth:handle(mock_request,
+ basicauth_config())),
+
+ ?assertEqual({401,
+ [{<<"WWW-Authenticate">>,
+ <<"Basic realm=\"Secure Area\"">>}],
+ <<"Unauthorized">>}, Result),
+ ?assert(meck:validate(elli)),
+ meck:unload(elli).
+
+
+valid_credentials_test() ->
+ meck:new(elli),
+ meck:expect(elli, get_header,
+ fun (<<"Authorization">>, mock_request) ->
+ ?VALID_CREDENTIALS
+ end),
+
+ Result = elli_basicauth:handle(mock_request,
+ basicauth_config()),
+
+ ?assertEqual(ignore, Result),
+ ?assert(meck:validate(elli)),
+ meck:unload(elli).
+
+
+invalid_credentials_test() ->
+ meck:new(elli),
+ meck:expect(elli, get_header,
+ fun (<<"Authorization">>, mock_request) ->
+ ?INVALID_CREDENTIALS
+ end),
+
+ Result = (catch elli_basicauth:handle(mock_request,
+ basicauth_config())),
+
+ ?assertEqual({403, [], <<"Forbidden">>}, Result),
+ ?assert(meck:validate(elli)),
+ meck:unload(elli).
+
+
+%%
+%% HELPERS
+%%
+
+basicauth_config() ->
+ [{auth_fun, fun test_predicate/3}].
+
+
+test_predicate(_Req, undefined, undefined) -> unauthorized;
+test_predicate(_Req, ?USER, ?PASSWORD) -> ok;
+test_predicate(_Req, _User, _Password) -> forbidden.

0 comments on commit 2ad2362

Please sign in to comment.