Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added tests, cleaned up code

  • Loading branch information...
commit 93a2a97f1d731966d911f0a0a2d7163679ab8a6f 1 parent 7b6a034
Bip Thelin authored
View
2  .gitignore
@@ -1,2 +1,4 @@
ebin
+.eunit
+deps
View
7 include/oauth2.hrl
@@ -12,8 +12,13 @@
grant_type :: string(),
access_token :: string(),
refresh_token :: string(),
- expires_in :: non_neg_integer(),
+ expires :: non_neg_integer(),
token_type :: string(),
error :: atom()
}).
+-record(oauth2_db, {client_id :: string(),
+ expires :: non_neg_integer(),
+ scope :: list(string())
+ }).
+
View
BIN  rebar
Binary file not shown
View
2  rebar.config
@@ -1,3 +1,5 @@
{erl_opts, [warnings_as_errors]}.
{cover_enabled, true}.
+{deps, [ {mochiweb_util, ".*", {git, "git://github.com/bipthelin/mochiweb_util.git", "master"}}]}.
+
View
121 src/oauth2.erl
@@ -1,45 +1,66 @@
-module(oauth2).
--export([is_authenticated/1, verify_token/2, is_perm_granted/2, get_auth_code/1]).
+-export([authorize/6, authorize/7]).
+-export([verify_token/4]).
--include_lib("oauth2/include/oauth2.hrl").
+-include_lib("include/oauth2.hrl").
-is_authenticated(Req) when is_list(Req) ->
- Request = proplist_to_rec(Req, #oauth2{}),
- validate_client_id(Request#oauth2.client_id),
- validate_redirect_uri(Request#oauth2.redirect_uri),
- %{error, authorize_invalid_header};
- case Request#oauth2.error of
- undefined -> ok;
- "access_denied" -> {error, access_denied};
- _ -> {error, authorize_invalid_header}
- end;
-is_authenticated(_) ->
- {error, bad_arg}.
+-define(DEF_AUTH_TOKEN_EXPIRE, 30).
-is_perm_granted(_, _) ->
- true.
+authorize(ResponseType, D, C, R, Sc, St) ->
+ authorize(ResponseType, D, C, R, Sc, St, online).
-get_auth_code(_ClientId) ->
+authorize(ResponseType, Db, ClientId, RedirectUri, Scope, State, _AccessType) ->
AuthCode = generate_auth_code(),
- %%Save Auth Code for ClientId
- AuthCode.
+ Data = #oauth2_db{client_id=ClientId,
+ expires=seconds_since_epoch(?DEF_AUTH_TOKEN_EXPIRE),
+ scope=Scope},
+ Key = generate_key(ClientId, AuthCode),
+ Db:set(Key, Data),
+ NewRedirectUri = get_redirect_uri(ResponseType, AuthCode, RedirectUri, State),
+ {ok, AuthCode, NewRedirectUri, Data#oauth2_db.expires}.
-verify_token(access_token, Token) ->
- %% TODO check token
- case Token of
- "hej" ->
- {ok, [{audience, "Client ID"}, {scope, "Which scope"},
- {expires_in, "secs"}]};
+verify_token(access_token, Db, Token, ClientId) ->
+ case Db:get(generate_key(ClientId, Token)) of
+ {ok, Data} ->
+ ClientId = Data#oauth2_db.client_id,
+ Expires = Data#oauth2_db.expires,
+ Scope = Data#oauth2_db.scope,
+ {ok, [{audience, ClientId}, {scope, Scope},
+ {expires, Expires}]};
_ ->
{error, invalid_token}
end.
%% Internal API
%%
+get_redirect_uri(Type, Code, Uri, State) ->
+ {S, N, P, Q, _} = mochiweb_util:urlsplit(Uri),
+ State2 = case State of
+ undefined -> [];
+ "" -> [];
+ StateVal -> [{state, StateVal}]
+ end,
+ Q2 = mochiweb_util:parse_qs(Q),
+ CF = [{code, Code}],
+ case Type of
+ token ->
+ Q3 = lists:append([State2, Q2]),
+ CF2 = mochiweb_util:urlencode(CF),
+ Query = mochiweb_util:urlencode(Q3),
+ mochiweb_util:urlunsplit({S, N, P, Query, CF2});
+ code ->
+ Q3 = lists:append([CF, State2, Q2]),
+ Query = mochiweb_util:urlencode(Q3),
+ mochiweb_util:urlunsplit({S, N, P, Query, ""})
+ end.
+
+generate_key(ClientId, AuthCode) ->
+ lists:flatten([ClientId, "#", AuthCode]).
generate_auth_code() ->
- Chars = list_to_tuple("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-&/"),
+ Chars = list_to_tuple("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"),
+ random:seed(now()),
rnd_auth(30, Chars).
rnd_auth(0, _) ->
@@ -49,49 +70,7 @@ rnd_auth(Len, C) ->
rnd_auth(C) ->
element(random:uniform(tuple_size(C)), C).
-validate_client_id(undefined) ->
- {error, wrong_client_id};
-validate_client_id(_ClientId) ->
- true.
-
-validate_redirect_uri(undefined) ->
- {error, wrong_redirect_uri};
-validate_redirect_uri(_RedirectUri) ->
- true.
-
-proplist_to_rec([], Acc) ->
- Acc;
-proplist_to_rec([{HH, HT}|T], Acc) ->
- proplist_to_rec(T, set_rec(list_to_existing_atom(HH), HT, Acc)).
-
-set_rec(response_type, Value, Rec) ->
- Rec#oauth2{response_type=Value};
-set_rec(client_id, Value, Rec) ->
- Rec#oauth2{client_id=Value};
-set_rec(redirect_uri, Value, Rec) ->
- Rec#oauth2{redirect_uri=Value};
-set_rec(scope, Value, Rec) ->
- Rec#oauth2{scope=Value};
-set_rec(state, Value, Rec) ->
- Rec#oauth2{state=Value};
-set_rec(access_type, Value, Rec) ->
- Rec#oauth2{access_type=Value};
-set_rec(approval_prompt, Value, Rec) ->
- Rec#oauth2{approval_prompt=Value};
-set_rec(code, Value, Rec) ->
- Rec#oauth2{code=Value};
-set_rec(client_secret, Value, Rec) ->
- Rec#oauth2{client_secret=Value};
-set_rec(grant_type, Value, Rec) ->
- Rec#oauth2{grant_type=Value};
-set_rec(access_token, Value, Rec) ->
- Rec#oauth2{access_token=Value};
-set_rec(refresh_token, Value, Rec) ->
- Rec#oauth2{refresh_token=Value};
-set_rec(expires_in, Value, Rec) ->
- Rec#oauth2{expires_in=Value};
-set_rec(error, Value, Rec) ->
- Rec#oauth2{error=Value};
-set_rec(token_type, Value, Rec) ->
- Rec#oauth2{token_type=Value}.
+seconds_since_epoch(Diff) ->
+ {Mega, Secs, _Micro} = now(),
+ Mega * 1000000 + Secs + Diff.
View
12 src/oauth2_db.erl
@@ -0,0 +1,12 @@
+-module(oauth2_db).
+
+-export([behaviour_info/1]).
+
+behaviour_info(callbacks) ->
+ [{get, 1},
+ {set, 2},
+ {delete, 1}];
+behaviour_info(_) ->
+ undefined.
+
+
View
30 src/oauth2_mock_db.erl
@@ -0,0 +1,30 @@
+-module(oauth2_mock_db).
+
+-export([get/1, set/2, delete/1]).
+-export([init/0, delete_table/0]).
+
+-behavior(oauth2_db).
+
+get(Key) ->
+ case ets:lookup(?MODULE, Key) of
+ [] ->
+ undefined;
+ [{_Key, Value}] ->
+ {ok, Value}
+ end.
+
+set(Key, Value) ->
+ ets:insert(?MODULE, {Key, Value}).
+
+delete(Key) ->
+ ets:delete(?MODULE, Key).
+
+%%
+%% Non behavioral functions
+init() ->
+ ?MODULE = ets:new(?MODULE, [named_table, {read_concurrency, true}]),
+ ok.
+
+delete_table() ->
+ ets:delete(?MODULE).
+
View
60 test/oauth2_test.erl
@@ -0,0 +1,60 @@
+-module(oauth2_test).
+
+-include_lib("eunit/include/eunit.hrl").
+
+oauth2_test_() ->
+ {foreach,
+ fun() ->
+ ok
+ end,
+ fun(_) ->
+ ok
+ end,
+ [
+ {"web server (authentication code flow)",
+ fun() ->
+ ok
+ end
+ },
+ {"client side (implicit flow) ",
+ fun() ->
+ oauth2_mock_db:init(),
+
+ RedirectUri = "http://REDIRECT.URL/here?this=that",
+ Scope = "This That",
+ State = "",
+ %State2 = "Just a little state",
+ AccessType = offline,
+ ClientId = "123abcABC",
+
+ %% Standard "online" authorization
+ A = oauth2:authorize(token, oauth2_mock_db, ClientId,
+ RedirectUri, Scope, State),
+ ?assertMatch({ok, _, _, _}, A),
+ {_, Au, Ru, T} = A,
+ {S, N, P, Q, F} = mochiweb_util:urlsplit(Ru),
+ {S2, N2, P2, Q2, _} = mochiweb_util:urlsplit(RedirectUri),
+ F2 = mochiweb_util:parse_qs(F),
+ ?assertEqual(S, S2),
+ ?assertEqual(N, N2),
+ ?assertEqual(P, P2),
+ ?assertEqual(Q, Q2),
+ ?assertEqual(Au, proplists:get_value("code", F2)),
+ ?assert(check_expire(T, 30)),
+ ?assertNot(check_expire(T, 40)),
+
+ %% "offline" authorization
+ oauth2:authorize(token, oauth2_mock_db, ClientId,
+ RedirectUri, Scope, State, AccessType),
+ oauth2_mock_db:delete_table(),
+ ok
+ end
+ }
+ ]
+ }.
+
+check_expire(Base, Diff) ->
+ {Mega, Secs, _Micro} = now(),
+ Diff2 = Mega * 1000000 + Secs + Diff,
+ Diff2 =:= Base.
+
Please sign in to comment.
Something went wrong with that request. Please try again.