Guest User

Untitled

a guest
Jul 25th, 2018
101
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.86 KB | None | 0 0
  1. %% Getting an access token using the Authorization Request and Response from oAuth2 - Draft 13
  2. %% Important to notice; in this document a the remote service is referred to as 'the client' and the user as 'the user'.
  3. %% NOTICE: I'm not implementing scope here but I might.
  4. fun(Doc, {[Info|Request]}) ->
  5. %% Functions used all over
  6.  
  7. OpenDb = fun (Database) ->
  8. {ok, Db} = couch_db:open(Database, [{user_ctx, {user_ctx, undefined, [<<"_admin">>], undefined}}]),
  9. Db
  10. end,
  11.  
  12. GetDocumentRev = fun
  13. (Db, DocumentId) when is_binary(Db) ->
  14. Db0 = OpenDb(Db),
  15. {ok, {doc_info, DocumentId, _, RevInfos}} = couch_db:get_doc_info(Db0, DocumentId),
  16. {rev_info, RevInfo, _, _, _} = hd(RevInfos),
  17. StrRev = couch_doc:rev_to_str(RevInfo);
  18. (Db, DocumentId) ->
  19. {ok, {doc_info, DocumentId, _, RevInfos}} = couch_db:get_doc_info(Db, DocumentId),
  20. {rev_info, RevInfo, _, _, _} = hd(RevInfos),
  21. StrRev = couch_doc:rev_to_str(RevInfo)
  22. end,
  23.  
  24. ActualSave = fun(Document, Database) ->
  25. CouchDoc = couch_doc:from_json_obj({Document}),
  26. Db = OpenDb(Database),
  27. try couch_db:update_doc(Db, CouchDoc, []) of
  28. Ok -> ok
  29. catch
  30. conflict ->
  31. conflict
  32. end
  33. end,
  34.  
  35. SaveDoc = fun(Document, Database) ->
  36. case ActualSave(Document, Database) of
  37. ok -> saved;
  38. conflict ->
  39. DocumentId = proplists:get_value(<<"_id">>, Document),
  40. OldRev = GetDocumentRev(Database, DocumentId),
  41. Document0 = Document ++ [{<<"_rev">>, OldRev}],
  42. ActualSave(Document0, Database)
  43. end
  44. end,
  45.  
  46. %% Check functions etc
  47. ValidUser = fun(ExpectedHash, CleanPassword, Salt) ->
  48. EncryptedInput = binary:list_to_bin(couch_util:to_hex(crypto:sha(<<CleanPassword/binary, Salt/binary>>))),
  49. case EncryptedInput of
  50. ExpectedHash ->
  51. true;
  52. _ ->
  53. false
  54. end
  55. end,
  56.  
  57. CreateError = fun
  58. (ErrorType) ->
  59. {[{<<"code">>, 400},
  60. {<<"headers">>, {[
  61. {<<"Content-Type">>, <<"application/json; chatset=utf-8">>},
  62. {<<"Cache-Control">>, <<"no-store">>}
  63. ]}},
  64. {<<"body">>, "{\"error\":\""++atom_to_list(ErrorType)++"\"}"}]}
  65. end,
  66.  
  67. CreateToken0 = fun({UserId, ClientId, GeneratedAt, Token1}) ->
  68. TokenId = couch_uuids:new(),
  69. Token2 = Token1 ++ [{<<"token_id">>, TokenId},
  70. {<<"user_id">>, UserId},
  71. {<<"generated_at">>, GeneratedAt},
  72. {<<"client_id">>, ClientId}],
  73. %MaybeDeleteDocument(proplists:get_value(<<"_id">>, Token2)),
  74. SaveDoc(Token2, <<"authorization">>),
  75. TokenId
  76. end,
  77.  
  78. CreateToken = fun
  79. %% Access token
  80. (access_token, {UserId, ClientId, TokenTime, GeneratedAt}) ->
  81. TimeoutAt = GeneratedAt + TokenTime,
  82. Token = [{<<"_id">>, <<UserId/binary, "/", ClientId/binary, "/access_token">>},
  83. {<<"type">>, <<"access_token">>},
  84. {<<"timeout">>, TimeoutAt}],
  85. CreateToken0({UserId, ClientId, GeneratedAt, Token});
  86. (refresh_token, {UserId, ClientId, GeneratedAt}) ->
  87. Token = [{<<"_id">>, <<UserId/binary, "/", ClientId/binary, "/refresh_token">>},
  88. {<<"type">>, <<"refresh_token">>}],
  89. CreateToken0({UserId, ClientId, GeneratedAt, Token})
  90. end,
  91.  
  92. %% Create the tokens, one access token and a refresh token.
  93. %% These tokens are inserted into the database
  94. %% The existance of these tokens also mean access has been granted
  95. CreateResponse = fun(UserId, ClientDoc1) ->
  96. TokenTime = proplists:get_value(<<"token_time">>, ClientDoc1),
  97. ClientId = proplists:get_value(<<"id">>, ClientDoc1),
  98. GeneratedAt = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
  99. AccessToken = CreateToken(access_token, {UserId, ClientId, TokenTime, GeneratedAt}),
  100. Body = "{\"access_token\":\""++binary_to_list(AccessToken)++"\", \"token_type\":\"BEARER\", \"expires_in\":"++integer_to_list(TokenTime),
  101. Body0 = case proplists:get_value(<<"refresh_token">>, ClientDoc1) of
  102. true ->
  103. RefreshToken = CreateToken(refresh_token, {UserId, ClientId, GeneratedAt}),
  104. Body ++ ", \"refresh_token\":\""++binary_to_list(RefreshToken)++"\"}";
  105. _ ->
  106. Body ++ "}"
  107. end,
  108. {[{<<"headers">>, {[
  109. {<<"Content-Type">>, <<"application/json; chatset=utf-8">>},
  110. {<<"Cache-Control">>, <<"no-store">>}
  111. ]}},
  112. {<<"body">>, Body0}]}
  113. end,
  114.  
  115. %% Handle different grant types
  116. HandleGrantType = fun
  117. %% Username and password flow
  118. (<<"password">>, RequestDetails0, UserDoc0, ClientDoc0) ->
  119. ExpectedHash = couch_util:get_value(<<"password">>, UserDoc0),
  120. Salt = couch_util:get_value(<<"salt">>, UserDoc0),
  121. InputPassword = proplists:get_value(<<"password">>, RequestDetails0),
  122. case ValidUser(ExpectedHash, InputPassword, Salt) of
  123. true ->
  124. % This is a valid user, let's create a response
  125. CreateResponse(couch_util:get_value(<<"_id">>, UserDoc0), ClientDoc0);
  126. false ->
  127. CreateError(invalid_client)
  128. end;
  129. %% No more grant types supported
  130. (_GrantType, _UserDoc, _UserDetails, _ClientDocument) ->
  131. CreateError(unsupported_grant_type)
  132. end,
  133.  
  134.  
  135. %% Get grant document from the database
  136. GetGrantDocument = fun (RequestDetails1, UserDoc0) ->
  137. % TODO Get this with a view to filter invalid clients from active clients
  138. Db = OpenDb(<<"authorization">>),
  139. ClientId = proplists:get_value(<<"client_id">>, RequestDetails1),
  140. ClientId0 = <<"client:", ClientId/binary>>,
  141. try couch_httpd_db:couch_doc_open(Db, ClientId0, nil, []) of
  142. {doc, _, _, {ClientDocument0}, _, _, _} ->
  143. ClientDocument1 = ClientDocument0 ++ [{<<"id">>, ClientId0}],
  144. GrantType = proplists:get_value(<<"grant_type">>, RequestDetails1),
  145. AllowedGrantTypes = proplists:get_value(<<"grant_types">>, ClientDocument1),
  146. case lists:member(GrantType, AllowedGrantTypes) of
  147. true ->
  148. HandleGrantType(GrantType, RequestDetails1, UserDoc0, ClientDocument1);
  149. _ ->
  150. CreateError(invalid_grant)
  151. end
  152. catch
  153. _Else0 ->
  154. CreateError(invalid_client)
  155. end
  156. end,
  157. %%
  158.  
  159. case Doc of
  160. null ->
  161. CreateError(invalid_client);
  162. {UserDoc} ->
  163. % A user exist
  164. {Headers} = proplists:get_value(<<"headers">>, Request),
  165. ContentType = proplists:get_value(<<"Content-Type">>, Headers),
  166. RequestDetails = case ContentType of
  167. <<"application/x-www-form-urlencoded">> ->
  168. {UserDetails0} = proplists:get_value(<<"form">>, Request),
  169. UserDetails0;
  170. <<"application/json">> ->
  171. {struct, UserDetails0} = mochijson2:decode(proplists:get_value(<<"body">>, Request)),
  172. UserDetails0;
  173. _Else ->
  174. invalid_request
  175. end,
  176. case RequestDetails of
  177. invalid_request ->
  178. CreateError(invalid_request);
  179. _Else0 ->
  180. GetGrantDocument(RequestDetails, UserDoc)
  181. end
  182. end
  183. end.
Add Comment
Please, Sign In to add comment