Quick.OAuth.pas 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. unit Quick.OAuth;
  2. interface
  3. uses
  4. Quick.HttpClient,
  5. Quick.HttpServer.Request,
  6. Quick.HttpServer.Response,
  7. Quick.HttpServer,
  8. Quick.Threads,
  9. Quick.OAuth.Utils,
  10. SysUtils;
  11. type
  12. TOAuthToken = class
  13. private
  14. fAccessTokenExpiration: integer;
  15. fAccessToken: string;
  16. fRefreshToken: string;
  17. fRetrieveDateTime: TDateTime;
  18. public
  19. property AccessToken: string read fAccessToken write fAccessToken;
  20. property AccessTokenExpiration: integer read fAccessTokenExpiration write
  21. fAccessTokenExpiration;
  22. property RefreshToken: string read fRefreshToken write fRefreshToken;
  23. property RetrieveDateTime: TDateTime read fRetrieveDateTime write
  24. fRetrieveDateTime;
  25. end;
  26. TOnSaveToken = procedure (const aToken: TOAuthToken) of object;
  27. TOnLoadToken = procedure (var aToken: TOAuthToken) of object;
  28. TOnAuthorizationCompleted = reference to procedure (const aToken: TOAuthToken);
  29. TOnRefreshCompleted = reference to procedure (const aToken: TOAuthToken);
  30. {$M+}
  31. TOAuthBase = class
  32. private
  33. fToken: TOAuthToken;
  34. fOnAuthorizationCompleted: TOnAuthorizationCompleted;
  35. fOnRefreshCompleted: TOnRefreshCompleted;
  36. fAccessTokenParam: string;
  37. fAuthCodeParam: string;
  38. fAuthErrorParam: string;
  39. fExpirationParam: string;
  40. fRefreshTokenParam: string;
  41. fCallbackURL: string;
  42. fServer: THttpServer;
  43. fClient: TJsonHttpClient;
  44. fOnSaveToken: TOnSaveToken;
  45. fOnLoadToken: TOnLoadToken;
  46. procedure ExchangeAuthForAccessToken (const aAuthToken: string);
  47. procedure RefreshAccessToken (const aRefreshToken: string);
  48. function GetAccessToken: string;
  49. function IsTokenValid: boolean;
  50. protected
  51. // Abstract
  52. function CreateAuthorizationRequest: string; virtual; abstract;
  53. function CreateAuthToAccessRequest (const aAuthToken: string): string; virtual; abstract;
  54. function CreateRefreshRequest (const aRefreshToken: string): string; virtual; abstract;
  55. // Available
  56. function CreateAuthorizationHTMLPage (const aAuthorised: boolean): string; virtual;
  57. procedure OnProcessRequest(aRequest: IHttpRequest; aResponse: IHttpResponse); virtual;
  58. public
  59. constructor Create;
  60. destructor Destroy; override;
  61. // Methods
  62. procedure Authorize(const aOnAuthorizationCompleted: TOnAuthorizationCompleted = nil);
  63. procedure RefreshToken (const aOnRefreshCompleted: TOnRefreshCompleted = nil);
  64. // Properties
  65. property AccessTokenParam: string read fAccessTokenParam write
  66. fAccessTokenParam;
  67. property AuthCodeParam: string read fAuthCodeParam write fAuthCodeParam;
  68. property AuthErrorParam: string read fAuthErrorParam write fAuthErrorParam;
  69. property ExpirationParam: string read fExpirationParam write fExpirationParam;
  70. property RefreshTokenParam: string read fRefreshTokenParam write
  71. fRefreshTokenParam;
  72. property CallbackURL: string read fCallbackURL write fCallbackURL;
  73. property AccessToken: string read GetAccessToken;
  74. published
  75. // Events
  76. property OnSaveToken: TOnSaveToken read fOnSaveToken write fOnSaveToken;
  77. property OnLoadToken: TOnLoadToken read fOnLoadToken write fOnLoadToken;
  78. end;
  79. {$M-}
  80. EOAuthException = class (Exception);
  81. implementation
  82. uses
  83. System.JSON, System.DateUtils, System.Types;
  84. {$I QuickLib.INC}
  85. constructor TOAuthBase.Create;
  86. begin
  87. inherited Create;
  88. fClient:=TJsonHttpClient.Create;
  89. {$IFNDEF DELPHIRX101_UP}
  90. fServer:=nil;
  91. {$ENDIF}
  92. fToken:=TOAuthToken.Create;
  93. fToken.AccessToken:='';
  94. fToken.AccessTokenExpiration:=0;
  95. fToken.RefreshToken:='';
  96. fToken.RetrieveDateTime:=EncodeDateTime(1900, 1, 1, 23, 59, 00, 00);
  97. end;
  98. function TOAuthBase.CreateAuthorizationHTMLPage(
  99. const aAuthorised: boolean): string;
  100. begin
  101. if aAuthorised then
  102. result:='Access Authorised! You can now close this page and return to the application'
  103. else
  104. result:='Access Denied!';
  105. end;
  106. destructor TOAuthBase.Destroy;
  107. begin
  108. fClient.Free;
  109. fServer.Free;
  110. fToken.Free;
  111. inherited;
  112. end;
  113. procedure TOAuthBase.ExchangeAuthForAccessToken(const aAuthToken: string);
  114. var
  115. resp: IHttpRequestResponse;
  116. accToken: string;
  117. refrToken: string;
  118. expiry: integer;
  119. begin
  120. fToken.AccessToken:='';
  121. fToken.AccessTokenExpiration:=0;
  122. try
  123. case GetMethodFromRequest(CreateAuthToAccessRequest(aAuthToken)) of
  124. rmGET: resp:=fClient.Get(GetCleanRequest(CreateAuthToAccessRequest(aAuthToken)));
  125. rmPOST: resp:=fClient.Post(GetCleanRequest(CreateAuthToAccessRequest(aAuthToken)), '');
  126. end;
  127. if (assigned(resp)) and (resp.StatusCode = 200) then
  128. begin
  129. if Assigned(resp.Response) then
  130. begin
  131. if resp.Response.TryGetValue(AccessTokenParam, accToken) then
  132. fToken.AccessToken:=accToken;
  133. if resp.Response.TryGetValue(ExpirationParam, expiry) then
  134. fToken.AccessTokenExpiration:=expiry;
  135. if resp.Response.TryGetValue(RefreshTokenParam, refrToken) then
  136. fToken.RefreshToken:=refrToken;
  137. fToken.RetrieveDateTime:=Now;
  138. if Assigned(fOnSaveToken) then
  139. fOnSaveToken(fToken);
  140. if Assigned(fOnAuthorizationCompleted) then
  141. fOnAuthorizationCompleted(fToken);
  142. end;
  143. end
  144. else
  145. raise EOAuthException.Create('Something went wrong. Please try again');
  146. except
  147. raise EOAuthException.Create('Something went wrong. Please try again');
  148. end;
  149. end;
  150. procedure TOAuthBase.OnProcessRequest(aRequest: IHttpRequest;
  151. aResponse: IHttpResponse);
  152. begin
  153. fToken.AccessToken:='';
  154. fToken.AccessTokenExpiration:=0;
  155. if aRequest.UnparsedParams.Contains(AuthErrorParam) then
  156. aResponse.ContentText:= CreateAuthorizationHTMLPage(false)
  157. else
  158. if aRequest.UnparsedParams.Contains(AuthCodeParam) then
  159. begin
  160. ExchangeAuthForAccessToken(aRequest.Query[AuthCodeParam].AsString);
  161. aResponse.ContentText:= CreateAuthorizationHTMLPage(true);
  162. end;
  163. end;
  164. procedure TOAuthBase.RefreshAccessToken(const aRefreshToken: string);
  165. var
  166. resp: IHttpRequestResponse;
  167. accToken: string;
  168. expiry: integer;
  169. begin
  170. try
  171. case GetMethodFromRequest(CreateRefreshRequest(aRefreshToken)) of
  172. rmGET: resp:=fClient.Get(GetCleanRequest(CreateRefreshRequest(aRefreshToken)));
  173. rmPOST: resp:=fClient.Post(GetCleanRequest(CreateRefreshRequest(aRefreshToken)), '');
  174. end;
  175. if (assigned(resp)) and (resp.StatusCode = 200) then
  176. begin
  177. if Assigned(resp.Response) then
  178. begin
  179. if resp.Response.TryGetValue(AccessTokenParam, accToken) then
  180. fToken.AccessToken:=accToken;
  181. if resp.Response.TryGetValue(ExpirationParam, expiry) then
  182. fToken.AccessTokenExpiration:=expiry;
  183. fToken.RetrieveDateTime:=Now;
  184. if Assigned(fOnSaveToken) then
  185. fOnSaveToken(fToken);
  186. end;
  187. end
  188. else
  189. raise EOAuthException.Create('Something went wrong. Please try again');
  190. except
  191. raise EOAuthException.Create('Something went wrong. Please try again');
  192. end;
  193. end;
  194. procedure TOAuthBase.RefreshToken(const aOnRefreshCompleted:
  195. TOnRefreshCompleted = nil);
  196. begin
  197. fOnRefreshCompleted:=aOnRefreshCompleted;
  198. if Assigned(fOnLoadToken) then
  199. fOnLoadToken(fToken);
  200. if fToken.AccessToken = '' then
  201. Authorize(TOnAuthorizationCompleted(fOnRefreshCompleted))
  202. else
  203. begin
  204. GetAccessToken;
  205. if Assigned(fOnRefreshCompleted) then
  206. fOnRefreshCompleted(fToken);
  207. end;
  208. end;
  209. procedure TOAuthBase.Authorize(const aOnAuthorizationCompleted:
  210. TOnAuthorizationCompleted = nil);
  211. begin
  212. fOnAuthorizationCompleted:=aOnAuthorizationCompleted;
  213. if Assigned(fOnLoadToken) then
  214. fOnLoadToken(fToken);
  215. if IsTokenValid then
  216. Exit;
  217. fClient:=TJsonHttpClient.Create;
  218. if Assigned(fServer) then
  219. fServer.Stop;
  220. FreeAndNil(fServer);
  221. fServer:=THttpServer.Create(GetDomain(fCallbackURL), GetPort(fCallbackURL), false, nil);
  222. try
  223. fServer.OnNewRequest:=OnProcessRequest;
  224. fServer.Start;
  225. OpenURL(GetCleanRequest(CreateAuthorizationRequest));
  226. except
  227. fServer.Stop;
  228. FreeAndNil(fServer);
  229. end;
  230. end;
  231. function TOAuthBase.GetAccessToken: string;
  232. begin
  233. result:='';
  234. if IsTokenValid then
  235. result:=fToken.AccessToken
  236. else
  237. RefreshAccessToken(fToken.RefreshToken);
  238. end;
  239. function TOAuthBase.IsTokenValid: boolean;
  240. var
  241. expDate: TDateTime;
  242. begin
  243. expDate:=IncSecond(fToken.RetrieveDateTime, fToken.AccessTokenExpiration);
  244. result:= CompareDateTime(expDate, Now) = GreaterThanValue;
  245. end;
  246. end.