Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- %% 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.
Add Comment
Please, Sign In to add comment