Created
March 18, 2011 23:42
-
-
Save omarkj/877038 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% Getting an access token using the Authorization Request and Response from oAuth2 - Draft 13 | |
%% Important to notice; in this document a the remote service is referred to as 'the client' and the user as 'the user'. | |
%% NOTICE: I'm not implementing scope here but I might. | |
fun(Doc, {[Info|Request]}) -> | |
%% Functions used all over | |
OpenDb = fun (Database) -> | |
{ok, Db} = couch_db:open(Database, [{user_ctx, {user_ctx, undefined, [<<"_admin">>], undefined}}]), | |
Db | |
end, | |
GetDocumentRev = fun | |
(Db, DocumentId) when is_binary(Db) -> | |
Db0 = OpenDb(Db), | |
{ok, {doc_info, DocumentId, _, RevInfos}} = couch_db:get_doc_info(Db0, DocumentId), | |
{rev_info, RevInfo, _, _, _} = hd(RevInfos), | |
StrRev = couch_doc:rev_to_str(RevInfo); | |
(Db, DocumentId) -> | |
{ok, {doc_info, DocumentId, _, RevInfos}} = couch_db:get_doc_info(Db, DocumentId), | |
{rev_info, RevInfo, _, _, _} = hd(RevInfos), | |
StrRev = couch_doc:rev_to_str(RevInfo) | |
end, | |
ActualSave = fun(Document, Database) -> | |
CouchDoc = couch_doc:from_json_obj({Document}), | |
Db = OpenDb(Database), | |
try couch_db:update_doc(Db, CouchDoc, []) of | |
Ok -> ok | |
catch | |
conflict -> | |
conflict | |
end | |
end, | |
SaveDoc = fun(Document, Database) -> | |
case ActualSave(Document, Database) of | |
ok -> saved; | |
conflict -> | |
DocumentId = proplists:get_value(<<"_id">>, Document), | |
OldRev = GetDocumentRev(Database, DocumentId), | |
Document0 = Document ++ [{<<"_rev">>, OldRev}], | |
ActualSave(Document0, Database) | |
end | |
end, | |
%% Check functions etc | |
ValidUser = fun(ExpectedHash, CleanPassword, Salt) -> | |
EncryptedInput = binary:list_to_bin(couch_util:to_hex(crypto:sha(<<CleanPassword/binary, Salt/binary>>))), | |
case EncryptedInput of | |
ExpectedHash -> | |
true; | |
_ -> | |
false | |
end | |
end, | |
CreateError = fun | |
(ErrorType) -> | |
{[{<<"code">>, 400}, | |
{<<"headers">>, {[ | |
{<<"Content-Type">>, <<"application/json; chatset=utf-8">>}, | |
{<<"Cache-Control">>, <<"no-store">>} | |
]}}, | |
{<<"body">>, "{\"error\":\""++atom_to_list(ErrorType)++"\"}"}]} | |
end, | |
CreateToken0 = fun({UserId, ClientId, GeneratedAt, Token1}) -> | |
TokenId = couch_uuids:new(), | |
Token2 = Token1 ++ [{<<"token_id">>, TokenId}, | |
{<<"user_id">>, UserId}, | |
{<<"generated_at">>, GeneratedAt}, | |
{<<"client_id">>, ClientId}], | |
%MaybeDeleteDocument(proplists:get_value(<<"_id">>, Token2)), | |
SaveDoc(Token2, <<"authorization">>), | |
TokenId | |
end, | |
CreateToken = fun | |
%% Access token | |
(access_token, {UserId, ClientId, TokenTime, GeneratedAt}) -> | |
TimeoutAt = GeneratedAt + TokenTime, | |
Token = [{<<"_id">>, <<UserId/binary, "/", ClientId/binary, "/access_token">>}, | |
{<<"type">>, <<"access_token">>}, | |
{<<"timeout">>, TimeoutAt}], | |
CreateToken0({UserId, ClientId, GeneratedAt, Token}); | |
(refresh_token, {UserId, ClientId, GeneratedAt}) -> | |
Token = [{<<"_id">>, <<UserId/binary, "/", ClientId/binary, "/refresh_token">>}, | |
{<<"type">>, <<"refresh_token">>}], | |
CreateToken0({UserId, ClientId, GeneratedAt, Token}) | |
end, | |
%% Create the tokens, one access token and a refresh token. | |
%% These tokens are inserted into the database | |
%% The existance of these tokens also mean access has been granted | |
CreateResponse = fun(UserId, ClientDoc1) -> | |
TokenTime = proplists:get_value(<<"token_time">>, ClientDoc1), | |
ClientId = proplists:get_value(<<"id">>, ClientDoc1), | |
GeneratedAt = calendar:datetime_to_gregorian_seconds(calendar:local_time()), | |
AccessToken = CreateToken(access_token, {UserId, ClientId, TokenTime, GeneratedAt}), | |
Body = "{\"access_token\":\""++binary_to_list(AccessToken)++"\", \"token_type\":\"BEARER\", \"expires_in\":"++integer_to_list(TokenTime), | |
Body0 = case proplists:get_value(<<"refresh_token">>, ClientDoc1) of | |
true -> | |
RefreshToken = CreateToken(refresh_token, {UserId, ClientId, GeneratedAt}), | |
Body ++ ", \"refresh_token\":\""++binary_to_list(RefreshToken)++"\"}"; | |
_ -> | |
Body ++ "}" | |
end, | |
{[{<<"headers">>, {[ | |
{<<"Content-Type">>, <<"application/json; chatset=utf-8">>}, | |
{<<"Cache-Control">>, <<"no-store">>} | |
]}}, | |
{<<"body">>, Body0}]} | |
end, | |
%% Handle different grant types | |
HandleGrantType = fun | |
%% Username and password flow | |
(<<"password">>, RequestDetails0, UserDoc0, ClientDoc0) -> | |
ExpectedHash = couch_util:get_value(<<"password">>, UserDoc0), | |
Salt = couch_util:get_value(<<"salt">>, UserDoc0), | |
InputPassword = proplists:get_value(<<"password">>, RequestDetails0), | |
case ValidUser(ExpectedHash, InputPassword, Salt) of | |
true -> | |
% This is a valid user, let's create a response | |
CreateResponse(couch_util:get_value(<<"_id">>, UserDoc0), ClientDoc0); | |
false -> | |
CreateError(invalid_client) | |
end; | |
%% No more grant types supported | |
(_GrantType, _UserDoc, _UserDetails, _ClientDocument) -> | |
CreateError(unsupported_grant_type) | |
end, | |
%% Get grant document from the database | |
GetGrantDocument = fun (RequestDetails1, UserDoc0) -> | |
% TODO Get this with a view to filter invalid clients from active clients | |
Db = OpenDb(<<"authorization">>), | |
ClientId = proplists:get_value(<<"client_id">>, RequestDetails1), | |
ClientId0 = <<"client:", ClientId/binary>>, | |
try couch_httpd_db:couch_doc_open(Db, ClientId0, nil, []) of | |
{doc, _, _, {ClientDocument0}, _, _, _} -> | |
ClientDocument1 = ClientDocument0 ++ [{<<"id">>, ClientId0}], | |
GrantType = proplists:get_value(<<"grant_type">>, RequestDetails1), | |
AllowedGrantTypes = proplists:get_value(<<"grant_types">>, ClientDocument1), | |
case lists:member(GrantType, AllowedGrantTypes) of | |
true -> | |
HandleGrantType(GrantType, RequestDetails1, UserDoc0, ClientDocument1); | |
_ -> | |
CreateError(invalid_grant) | |
end | |
catch | |
_Else0 -> | |
CreateError(invalid_client) | |
end | |
end, | |
%% | |
case Doc of | |
null -> | |
CreateError(invalid_client); | |
{UserDoc} -> | |
% A user exist | |
{Headers} = proplists:get_value(<<"headers">>, Request), | |
ContentType = proplists:get_value(<<"Content-Type">>, Headers), | |
RequestDetails = case ContentType of | |
<<"application/x-www-form-urlencoded">> -> | |
{UserDetails0} = proplists:get_value(<<"form">>, Request), | |
UserDetails0; | |
<<"application/json">> -> | |
{struct, UserDetails0} = mochijson2:decode(proplists:get_value(<<"body">>, Request)), | |
UserDetails0; | |
_Else -> | |
invalid_request | |
end, | |
case RequestDetails of | |
invalid_request -> | |
CreateError(invalid_request); | |
_Else0 -> | |
GetGrantDocument(RequestDetails, UserDoc) | |
end | |
end | |
end. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is saved as a Erlang show in CouchDB.