UPD: MacCloud: step-57: unify TCloudDriverAuthPKCESession

This commit is contained in:
rich2014 2025-04-16 07:50:49 +08:00
commit ad45e74c24
3 changed files with 385 additions and 662 deletions

View file

@ -12,53 +12,17 @@ unit uDropBoxClient;
interface
uses
Classes, SysUtils, syncobjs, DateUtils,
CocoaAll, uMiniCocoa,
Classes, SysUtils, DateUtils,
CocoaAll,
uCloudDriver, uMiniHttpClient, uMiniUtil;
type
{ TDropBoxAuthPKCESession }
TDropBoxAuthPKCESession = class
strict private
_config: TCloudDriverConfig;
_dropBoxClient: TCloudDriver;
_codeVerifier: String;
_state: String;
_code: String;
_token: TCloudDriverToken;
_accountID: String;
_alert: NSAlert;
_lockObject: TCriticalSection;
private
procedure requestAuthorization;
procedure waitAuthorizationAndPrompt;
procedure closePrompt;
procedure requestToken;
procedure revokeToken;
procedure refreshToken;
procedure onRedirect( const url: NSURL );
function getAccessToken: String;
public
constructor Create( const config: TCloudDriverConfig; const dropBoxClient: TCloudDriver );
destructor Destroy; override;
function clone( const dropBoxClient: TCloudDriver ): TDropBoxAuthPKCESession;
public
function authorize: Boolean;
procedure unauthorize;
function authorized: Boolean;
procedure setAuthHeader( http: TMiniHttpClient );
protected
procedure setToken( const token: TCloudDriverToken );
function getToken: TCloudDriverToken;
end;
{ TDropBoxListFolderSession }
TDropBoxListFolderSession = class
private
_authSession: TDropBoxAuthPKCESession;
_authSession: TCloudDriverAuthPKCESession;
_path: String;
_files: TCloudFiles;
_cursor: String;
@ -68,7 +32,7 @@ type
procedure listFolderContinue;
procedure analyseListResult( const jsonString: String );
public
constructor Create( const authSession: TDropBoxAuthPKCESession; const path: String );
constructor Create( const authSession: TCloudDriverAuthPKCESession; const path: String );
destructor Destroy; override;
function getNextFile: TCloudFile;
end;
@ -77,13 +41,13 @@ type
TDropBoxDownloadSession = class
private
_authSession: TDropBoxAuthPKCESession;
_authSession: TCloudDriverAuthPKCESession;
_serverPath: String;
_localPath: String;
_callback: ICloudProgressCallback;
public
constructor Create(
const authSession: TDropBoxAuthPKCESession;
const authSession: TCloudDriverAuthPKCESession;
const serverPath: String;
const localPath: String;
const callback: ICloudProgressCallback );
@ -94,7 +58,7 @@ type
TDropBoxUploadSession = class
private
_authSession: TDropBoxAuthPKCESession;
_authSession: TCloudDriverAuthPKCESession;
_serverPath: String;
_localPath: String;
_localFileSize: Integer;
@ -104,7 +68,7 @@ type
procedure uploadLarge;
public
constructor Create(
const authSession: TDropBoxAuthPKCESession;
const authSession: TCloudDriverAuthPKCESession;
const serverPath: String;
const localPath: String;
const callback: ICloudProgressCallback );
@ -115,10 +79,10 @@ type
TDropBoxCreateFolderSession = class
private
_authSession: TDropBoxAuthPKCESession;
_authSession: TCloudDriverAuthPKCESession;
_path: String;
public
constructor Create( const authSession: TDropBoxAuthPKCESession; const path: String );
constructor Create( const authSession: TCloudDriverAuthPKCESession; const path: String );
procedure createFolder;
end;
@ -126,10 +90,10 @@ type
TDropBoxDeleteSession = class
private
_authSession: TDropBoxAuthPKCESession;
_authSession: TCloudDriverAuthPKCESession;
_path: String;
public
constructor Create( const authSession: TDropBoxAuthPKCESession; const path: String );
constructor Create( const authSession: TCloudDriverAuthPKCESession; const path: String );
procedure delete;
end;
@ -137,11 +101,11 @@ type
TDropBoxCopyMoveSession = class
private
_authSession: TDropBoxAuthPKCESession;
_authSession: TCloudDriverAuthPKCESession;
_fromPath: String;
_toPath: String;
public
constructor Create( const authSession: TDropBoxAuthPKCESession;
constructor Create( const authSession: TCloudDriverAuthPKCESession;
const fromPath: String; const toPath: String );
procedure copyOrMove( const needToMove: Boolean );
procedure copy;
@ -154,7 +118,7 @@ type
private
_listFolderSession: TDropBoxListFolderSession;
public
constructor Create( const authSession: TDropBoxAuthPKCESession; const path: String );
constructor Create( const authSession: TCloudDriverAuthPKCESession; const path: String );
destructor Destroy; override;
procedure listFolderBegin; override;
function listFolderGetNextFile: TCloudFile; override;
@ -166,7 +130,7 @@ type
TDropBoxClient = class( TCloudDriver )
private
_config: TCloudDriverConfig;
_authSession: TDropBoxAuthPKCESession;
_authSession: TCloudDriverAuthPKCESession;
public
class function driverName: String; override;
class function createInstance: TCloudDriver; override;
@ -341,280 +305,6 @@ begin
end;
end;
{ TDropBoxAuthPKCESession }
procedure TDropBoxAuthPKCESession.requestAuthorization;
var
queryItems: TQueryItemsDictonary;
codeChallenge: String;
begin
_codeVerifier:= TStringUtil.generateRandomString( 43 );
_state:= TStringUtil.generateRandomString( 10 );
codeChallenge:= THashUtil.sha256AndBase64( _codeVerifier ) ;
queryItems:= TQueryItemsDictonary.Create;
queryItems.Add( 'client_id', _config.clientID );
queryItems.Add( 'redirect_uri', _config.listenURI );
queryItems.Add( 'code_challenge', codeChallenge );
queryItems.Add( 'code_challenge_method', 'S256' );
queryItems.Add( 'response_type', 'code' );
queryItems.Add( 'token_access_type', 'offline' );
queryItems.Add( 'state', _state );
THttpClientUtil.openInSafari( DropBoxConst.URI.OAUTH2, queryItems );
end;
procedure TDropBoxAuthPKCESession.waitAuthorizationAndPrompt;
begin
NSApplication(NSAPP).setOpenURLObserver( @self.onRedirect );
_alert:= NSAlert.new;
_alert.setMessageText( StringToNSString('Waiting for DropBox authorization') );
_alert.setInformativeText( StringToNSString('Please login your DropBox account in Safari and authorize Double Commander to access. '#13'The authorization is completed on the DropBox official website, Double Command will not get your password.') );
_alert.addButtonWithTitle( NSSTR('Cancel') );
_alert.runModal;
NSApplication(NSAPP).setOpenURLObserver( nil );
_alert.release;
_alert:= nil;
end;
procedure TDropBoxAuthPKCESession.closePrompt;
var
button: NSButton;
begin
if _alert = nil then
Exit;
button:= NSButton( _alert.buttons.objectAtIndex(0) );
button.performClick( nil );
end;
procedure TDropBoxAuthPKCESession.requestToken;
var
http: TMiniHttpClient = nil;
cloudDriverResult: TCloudDriverResult = nil;
procedure doRequest;
var
queryItems: TQueryItemsDictonary;
begin
queryItems:= TQueryItemsDictonary.Create;
queryItems.Add( 'client_id', _config.clientID );
queryItems.Add( 'redirect_uri', _config.listenURI );
queryItems.Add( 'code', _code );
queryItems.Add( 'code_verifier', _codeVerifier );
queryItems.Add( 'grant_type', 'authorization_code' );
http.setQueryParams( queryItems );
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.connect;
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
DropBoxClientProcessResult( cloudDriverResult );
end;
procedure analyseResult;
var
json: NSDictionary;
begin
json:= TJsonUtil.parse( cloudDriverResult.httpResult.body );
_token.access:= TJsonUtil.getString( json, 'access_token' );
_token.refresh:= TJsonUtil.getString( json, 'refresh_token' );
_token.setExpiration( TJsonUtil.getInteger( json, 'expires_in' ) );
_accountID:= TJsonUtil.getString( json, 'account_id' );
end;
begin
if _code = EmptyStr then
Exit;
try
http:= TMiniHttpClient.Create( DropBoxConst.URI.TOKEN, HttpConst.Method.POST );
doRequest;
if cloudDriverResult.httpResult.resultCode <> 200 then
Exit;
analyseResult;
cloudDriverManager.driverUpdated( _dropBoxClient );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
end;
end;
procedure TDropBoxAuthPKCESession.revokeToken;
var
http: TMiniHttpClient = nil;
begin
try
http:= TMiniHttpClient.Create( DropBoxConst.URI.REVOKE_TOKEN, HttpConst.Method.POST );
self.setAuthHeader( http );
http.connect;
finally
_token.invalid;
FreeAndNil( http );
end;
end;
procedure TDropBoxAuthPKCESession.refreshToken;
var
http: TMiniHttpClient = nil;
cloudDriverResult: TCloudDriverResult = nil;
procedure doRequest;
var
queryItems: TQueryItemsDictonary;
begin
queryItems:= TQueryItemsDictonary.Create;
queryItems.Add( 'client_id', _config.clientID );
queryItems.Add( 'grant_type', 'refresh_token' );
queryItems.Add( 'refresh_token', _token.refresh );
http.setQueryParams( queryItems );
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.connect;
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
DropBoxClientProcessResult( cloudDriverResult );
end;
procedure analyseResult;
var
json: NSDictionary;
begin
json:= TJsonUtil.parse( cloudDriverResult.httpResult.body );
_token.access:= TJsonUtil.getString( json, 'access_token' );
_token.setExpiration( TJsonUtil.getInteger( json, 'expires_in' ) );
end;
begin
try
http:= TMiniHttpClient.Create( DropBoxConst.URI.TOKEN, HttpConst.Method.POST );
doRequest;
if cloudDriverResult.httpResult.resultCode <> 200 then
Exit;
analyseResult;
cloudDriverManager.driverUpdated( _dropBoxClient );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
end;
end;
procedure TDropBoxAuthPKCESession.onRedirect(const url: NSURL);
var
components: NSURLComponents;
state: String;
begin
components:= NSURLComponents.componentsWithURL_resolvingAgainstBaseURL( url, False );
state:= THttpClientUtil.queryValue( components, 'state' );
if state <> _state then
Exit;
_code:= THttpClientUtil.queryValue( components, 'code' );
closePrompt;
end;
function TDropBoxAuthPKCESession.getAccessToken: String;
procedure checkToken;
begin
try
if NOT _token.isValidAccessToken then begin
if _token.isValidFreshToken then begin
self.refreshToken;
end else begin
self.authorize;
end;
end;
except
on e: ECloudDriverTokenException do begin
TLogUtil.logError( 'Token Error: ' + e.ClassName + ': ' + e.Message );
_token.invalid;
self.authorize;
end;
end;
end;
begin
_lockObject.Acquire;
try
checkToken;
Result:= _token.access;
finally
_lockObject.Release;
end;
end;
constructor TDropBoxAuthPKCESession.Create(const config: TCloudDriverConfig; const dropBoxClient: TCloudDriver );
begin
_config:= config;
_dropBoxClient:= dropBoxClient;
_token:= TCloudDriverToken.Create;
_lockObject:= TCriticalSection.Create;
end;
destructor TDropBoxAuthPKCESession.Destroy;
begin
FreeAndNil( _token );
FreeAndNil( _lockObject );
end;
function TDropBoxAuthPKCESession.clone( const dropBoxClient: TCloudDriver ): TDropBoxAuthPKCESession;
begin
Result:= TDropBoxAuthPKCESession.Create( _config, dropBoxClient );
Result._accountID:= self._accountID;
Result._token:= self._token.clone;
end;
function TDropBoxAuthPKCESession.authorize: Boolean;
begin
_lockObject.Acquire;
try
requestAuthorization;
TThread.Synchronize( TThread.CurrentThread, @waitAuthorizationAndPrompt );
requestToken;
Result:= self.authorized;
finally
_codeVerifier:= EmptyStr;
_state:= EmptyStr;
_code:= EmptyStr;
_lockObject.Release;
end;
end;
procedure TDropBoxAuthPKCESession.unauthorize;
begin
_lockObject.Acquire;
try
revokeToken;
finally
_lockObject.Release;
end;
end;
function TDropBoxAuthPKCESession.authorized: Boolean;
begin
Result:= (_token.access <> EmptyStr);
end;
procedure TDropBoxAuthPKCESession.setAuthHeader(http: TMiniHttpClient);
var
access: String;
begin
access:= self.getAccessToken;
http.addHeader( DropBoxConst.HEADER.AUTH, 'Bearer ' + access );
end;
procedure TDropBoxAuthPKCESession.setToken(const token: TCloudDriverToken);
var
oldToken: TCloudDriverToken;
begin
oldToken:= _token;
_token:= token;
oldToken.Free;
end;
function TDropBoxAuthPKCESession.getToken: TCloudDriverToken;
begin
Result:= _token;
end;
{ TDropBoxListFolderSession }
procedure TDropBoxListFolderSession.listFolderFirst;
@ -711,7 +401,7 @@ begin
end;
end;
constructor TDropBoxListFolderSession.Create( const authSession: TDropBoxAuthPKCESession; const path: String );
constructor TDropBoxListFolderSession.Create( const authSession: TCloudDriverAuthPKCESession; const path: String );
begin
_authSession:= authSession;
if path <> '/' then
@ -746,7 +436,7 @@ end;
{ TDropBoxDownloadSession }
constructor TDropBoxDownloadSession.Create(
const authSession: TDropBoxAuthPKCESession;
const authSession: TCloudDriverAuthPKCESession;
const serverPath: String;
const localPath: String;
const callback: ICloudProgressCallback );
@ -783,7 +473,7 @@ end;
{ TDropBoxUploadSession }
constructor TDropBoxUploadSession.Create(
const authSession: TDropBoxAuthPKCESession; const serverPath: String;
const authSession: TCloudDriverAuthPKCESession; const serverPath: String;
const localPath: String; const callback: ICloudProgressCallback);
begin
_authSession:= authSession;
@ -952,7 +642,7 @@ end;
{ TDropBoxCreateFolderSession }
constructor TDropBoxCreateFolderSession.Create(
const authSession: TDropBoxAuthPKCESession; const path: String );
const authSession: TCloudDriverAuthPKCESession; const path: String );
begin
_authSession:= authSession;
_path:= path;
@ -985,7 +675,7 @@ end;
{ TDropBoxDeleteSession }
constructor TDropBoxDeleteSession.Create(
const authSession: TDropBoxAuthPKCESession; const path: String);
const authSession: TCloudDriverAuthPKCESession; const path: String);
begin
_authSession:= authSession;
_path:= path;
@ -1017,7 +707,7 @@ end;
{ TDropBoxCopyMoveSession }
constructor TDropBoxCopyMoveSession.Create( const authSession: TDropBoxAuthPKCESession;
constructor TDropBoxCopyMoveSession.Create( const authSession: TCloudDriverAuthPKCESession;
const fromPath: String; const toPath: String );
begin
_authSession:= authSession;
@ -1069,7 +759,7 @@ end;
{ TDropBoxLister }
constructor TDropBoxLister.Create(
const authSession: TDropBoxAuthPKCESession;
const authSession: TCloudDriverAuthPKCESession;
const path: String );
begin
_listFolderSession:= TDropBoxListFolderSession.Create( authSession, path );
@ -1108,9 +798,18 @@ begin
end;
constructor TDropBoxClient.Create(const config: TCloudDriverConfig);
var
params: TCloudDriverAuthPKCESessionParams;
begin
_config:= config;
_authSession:= TDropBoxAuthPKCESession.Create( _config, self );
params.config:= config;
params.resultProcessFunc:= @DropBoxClientProcessResult;
params.OAUTH2_URI:= DropBoxConst.URI.OAUTH2;
params.TOKEN_URI:= DropBoxConst.URI.TOKEN;
params.REVOKE_TOKEN_URI:= DropBoxConst.URI.REVOKE_TOKEN;
params.AUTH_HEADER:= DropBoxConst.HEADER.AUTH;
params.AUTH_TYPE:= 'Bearer';
_authSession:= TCloudDriverAuthPKCESession.Create( self, params );
end;
destructor TDropBoxClient.Destroy;
@ -1123,7 +822,7 @@ var
newClient: TDropBoxClient;
begin
newClient:= TDropBoxClient.Create( _config );
newClient._authSession:= self._authSession.clone( self );
newClient._authSession:= self._authSession.clone( newClient );
Result:= newClient;
end;

View file

@ -7,9 +7,9 @@ unit uCloudDriver;
interface
uses
Classes, SysUtils,
CocoaAll,
uMiniHttpClient;
Classes, SysUtils, syncobjs,
CocoaAll, uMiniCocoa,
uMiniHttpClient, uMiniUtil;
type
@ -29,6 +29,8 @@ type
ECloudDriverPermissionException = class( ECloudDriverException );
ECloudDriverRateLimitException = class( ECloudDriverException );
TCloudDriverResultProcessFunc = procedure ( const cloudDriverResult: TCloudDriverResult );
{ TCloudDriverConfig }
TCloudDriverConfig = class
@ -152,6 +154,54 @@ type
property observer: ICloudDriverObserver write _observer;
end;
{ TCloudDriverAuthPKCESessionParams }
TCloudDriverAuthPKCESessionParams = record
config: TCloudDriverConfig;
resultProcessFunc: TCloudDriverResultProcessFunc;
OAUTH2_URI: String;
TOKEN_URI: String;
REVOKE_TOKEN_URI: String;
AUTH_HEADER: String;
AUTH_TYPE: String;
end;
{ TCloudDriverAuthPKCESession }
TCloudDriverAuthPKCESession = class
strict private
_driver: TCloudDriver;
_params: TCloudDriverAuthPKCESessionParams;
_config: TCloudDriverConfig;
_codeVerifier: String;
_state: String;
_code: String;
_token: TCloudDriverToken;
_accountID: String;
_alert: NSAlert;
_lockObject: TCriticalSection;
private
procedure requestAuthorization;
procedure waitAuthorizationAndPrompt;
procedure closePrompt;
procedure requestToken;
procedure revokeToken;
procedure refreshToken;
procedure onRedirect( const url: NSURL );
function getAccessToken: String;
public
constructor Create( const driver: TCloudDriver; const params: TCloudDriverAuthPKCESessionParams );
destructor Destroy; override;
function clone( const driver: TCloudDriver ): TCloudDriverAuthPKCESession;
public
function authorize: Boolean;
procedure unauthorize;
function authorized: Boolean;
procedure setAuthHeader( http: TMiniHttpClient );
procedure setToken( const token: TCloudDriverToken );
function getToken: TCloudDriverToken;
end;
var
cloudDriverManager: TCloudDriverManager;
@ -222,6 +272,282 @@ begin
_accessExpirationTime:= 0;
end;
{ TCloudDriverAuthPKCESession }
procedure TCloudDriverAuthPKCESession.requestAuthorization;
var
queryItems: TQueryItemsDictonary;
codeChallenge: String;
begin
_codeVerifier:= TStringUtil.generateRandomString( 43 );
_state:= TStringUtil.generateRandomString( 10 );
codeChallenge:= THashUtil.sha256AndBase64( _codeVerifier ) ;
queryItems:= TQueryItemsDictonary.Create;
queryItems.Add( 'client_id', _config.clientID );
queryItems.Add( 'redirect_uri', _config.listenURI );
queryItems.Add( 'code_challenge', codeChallenge );
queryItems.Add( 'code_challenge_method', 'S256' );
queryItems.Add( 'response_type', 'code' );
queryItems.Add( 'token_access_type', 'offline' );
queryItems.Add( 'state', _state );
THttpClientUtil.openInSafari( _params.OAUTH2_URI, queryItems );
end;
procedure TCloudDriverAuthPKCESession.waitAuthorizationAndPrompt;
begin
NSApplication(NSAPP).setOpenURLObserver( @self.onRedirect );
_alert:= NSAlert.new;
_alert.setMessageText( StringToNSString('Waiting for ' + _driver.driverName + ' authorization') );
_alert.setInformativeText( StringToNSString('Please login your ' + _driver.driverName + ' account in Safari and authorize Double Commander to access. '#13'The authorization is completed on the ' + _driver.driverName + ' official website, Double Command will not get your password.') );
_alert.addButtonWithTitle( NSSTR('Cancel') );
_alert.runModal;
NSApplication(NSAPP).setOpenURLObserver( nil );
_alert.release;
_alert:= nil;
end;
procedure TCloudDriverAuthPKCESession.closePrompt;
var
button: NSButton;
begin
if _alert = nil then
Exit;
button:= NSButton( _alert.buttons.objectAtIndex(0) );
button.performClick( nil );
end;
procedure TCloudDriverAuthPKCESession.requestToken;
var
http: TMiniHttpClient = nil;
cloudDriverResult: TCloudDriverResult = nil;
procedure doRequest;
var
queryItems: TQueryItemsDictonary;
begin
queryItems:= TQueryItemsDictonary.Create;
queryItems.Add( 'client_id', _config.clientID );
queryItems.Add( 'redirect_uri', _config.listenURI );
queryItems.Add( 'code', _code );
queryItems.Add( 'code_verifier', _codeVerifier );
queryItems.Add( 'grant_type', 'authorization_code' );
http.setQueryParams( queryItems );
http.setContentType( HttpConst.ContentType.UrlEncoded );
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.connect;
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
_params.resultProcessFunc( cloudDriverResult );
end;
procedure analyseResult;
var
json: NSDictionary;
begin
json:= TJsonUtil.parse( cloudDriverResult.httpResult.body );
_token.access:= TJsonUtil.getString( json, 'access_token' );
_token.refresh:= TJsonUtil.getString( json, 'refresh_token' );
_token.setExpiration( TJsonUtil.getInteger( json, 'expires_in' ) );
_accountID:= TJsonUtil.getString( json, 'account_id' );
end;
begin
if _code = EmptyStr then
Exit;
try
http:= TMiniHttpClient.Create( _params.TOKEN_URI, HttpConst.Method.POST );
doRequest;
if cloudDriverResult.httpResult.resultCode <> 200 then
Exit;
analyseResult;
cloudDriverManager.driverUpdated( _driver );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
end;
end;
procedure TCloudDriverAuthPKCESession.revokeToken;
var
http: TMiniHttpClient = nil;
begin
try
http:= TMiniHttpClient.Create( _params.REVOKE_TOKEN_URI, HttpConst.Method.POST );
self.setAuthHeader( http );
http.connect;
finally
_token.invalid;
FreeAndNil( http );
end;
end;
procedure TCloudDriverAuthPKCESession.refreshToken;
var
http: TMiniHttpClient = nil;
cloudDriverResult: TCloudDriverResult = nil;
procedure doRequest;
var
queryItems: TQueryItemsDictonary;
begin
queryItems:= TQueryItemsDictonary.Create;
queryItems.Add( 'client_id', _config.clientID );
queryItems.Add( 'grant_type', 'refresh_token' );
queryItems.Add( 'refresh_token', _token.refresh );
http.setQueryParams( queryItems );
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.connect;
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
_params.resultProcessFunc( cloudDriverResult );
end;
procedure analyseResult;
var
json: NSDictionary;
begin
json:= TJsonUtil.parse( cloudDriverResult.httpResult.body );
_token.access:= TJsonUtil.getString( json, 'access_token' );
_token.setExpiration( TJsonUtil.getInteger( json, 'expires_in' ) );
end;
begin
try
http:= TMiniHttpClient.Create( _params.TOKEN_URI, HttpConst.Method.POST );
doRequest;
if cloudDriverResult.httpResult.resultCode <> 200 then
Exit;
analyseResult;
cloudDriverManager.driverUpdated( _driver );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
end;
end;
procedure TCloudDriverAuthPKCESession.onRedirect(const url: NSURL);
var
components: NSURLComponents;
state: String;
begin
components:= NSURLComponents.componentsWithURL_resolvingAgainstBaseURL( url, False );
state:= THttpClientUtil.queryValue( components, 'state' );
if state <> _state then
Exit;
_code:= THttpClientUtil.queryValue( components, 'code' );
closePrompt;
end;
function TCloudDriverAuthPKCESession.getAccessToken: String;
procedure checkToken;
begin
try
if NOT _token.isValidAccessToken then begin
if _token.isValidFreshToken then begin
self.refreshToken;
end else begin
self.authorize;
end;
end;
except
on e: ECloudDriverTokenException do begin
TLogUtil.logError( 'Token Error: ' + e.ClassName + ': ' + e.Message );
_token.invalid;
self.authorize;
end;
end;
end;
begin
_lockObject.Acquire;
try
checkToken;
Result:= _token.access;
finally
_lockObject.Release;
end;
end;
constructor TCloudDriverAuthPKCESession.Create(
const driver: TCloudDriver;
const params: TCloudDriverAuthPKCESessionParams );
begin
_driver:= driver;
_params:= params;
_config:= _params.config;
_token:= TCloudDriverToken.Create;
_lockObject:= TCriticalSection.Create;
end;
destructor TCloudDriverAuthPKCESession.Destroy;
begin
FreeAndNil( _token );
FreeAndNil( _lockObject );
end;
function TCloudDriverAuthPKCESession.clone( const driver: TCloudDriver ): TCloudDriverAuthPKCESession;
begin
Result:= TCloudDriverAuthPKCESession.Create( driver, _params );
Result._accountID:= _accountID;
Result._token:= _token.clone;
end;
function TCloudDriverAuthPKCESession.authorize: Boolean;
begin
_lockObject.Acquire;
try
requestAuthorization;
TThread.Synchronize( TThread.CurrentThread, @waitAuthorizationAndPrompt );
requestToken;
Result:= self.authorized;
finally
_codeVerifier:= EmptyStr;
_state:= EmptyStr;
_code:= EmptyStr;
_lockObject.Release;
end;
end;
procedure TCloudDriverAuthPKCESession.unauthorize;
begin
_lockObject.Acquire;
try
revokeToken;
finally
_lockObject.Release;
end;
end;
function TCloudDriverAuthPKCESession.authorized: Boolean;
begin
Result:= (_token.access <> EmptyStr);
end;
procedure TCloudDriverAuthPKCESession.setAuthHeader(http: TMiniHttpClient);
var
access: String;
begin
access:= self.getAccessToken;
http.addHeader( _params.AUTH_HEADER, _params.AUTH_TYPE + ' ' + access );
end;
procedure TCloudDriverAuthPKCESession.setToken(const token: TCloudDriverToken);
var
oldToken: TCloudDriverToken;
begin
oldToken:= _token;
_token:= token;
oldToken.Free;
end;
function TCloudDriverAuthPKCESession.getToken: TCloudDriverToken;
begin
Result:= _token;
end;
{ TCloudDriverManager }
constructor TCloudDriverManager.Create;

View file

@ -12,48 +12,12 @@ unit uYandexClient;
interface
uses
Classes, SysUtils, syncobjs, DateUtils,
CocoaAll, uMiniCocoa,
Classes, SysUtils, DateUtils,
CocoaAll,
uCloudDriver, uMiniHttpClient, uMiniUtil;
type
{ TCloudDriverAuthPKCESession }
TCloudDriverAuthPKCESession = class
strict private
_config: TCloudDriverConfig;
_driver: TCloudDriver;
_codeVerifier: String;
_state: String;
_code: String;
_token: TCloudDriverToken;
_accountID: String;
_alert: NSAlert;
_lockObject: TCriticalSection;
private
procedure requestAuthorization;
procedure waitAuthorizationAndPrompt;
procedure closePrompt;
procedure requestToken;
procedure revokeToken;
procedure refreshToken;
procedure onRedirect( const url: NSURL );
function getAccessToken: String;
public
constructor Create( const config: TCloudDriverConfig; const driver: TCloudDriver );
destructor Destroy; override;
function clone( const driver: TCloudDriver ): TCloudDriverAuthPKCESession;
public
function authorize: Boolean;
procedure unauthorize;
function authorized: Boolean;
procedure setAuthHeader( http: TMiniHttpClient );
protected
procedure setToken( const token: TCloudDriverToken );
function getToken: TCloudDriverToken;
end;
{ TCloudDriverListFolderSession }
TCloudDriverListFolderSession = class
@ -241,7 +205,7 @@ const
);
// raise the corresponding exception if there are errors
procedure CloudDriverProcessResult( const cloudDriverResult: TCloudDriverResult );
procedure YandexClientResultProcess( const cloudDriverResult: TCloudDriverResult );
var
httpResult: TMiniHttpResult;
httpError: NSError;
@ -293,281 +257,6 @@ begin
end;
end;
{ TCloudDriverAuthPKCESession }
procedure TCloudDriverAuthPKCESession.requestAuthorization;
var
queryItems: TQueryItemsDictonary;
codeChallenge: String;
begin
_codeVerifier:= TStringUtil.generateRandomString( 43 );
_state:= TStringUtil.generateRandomString( 10 );
codeChallenge:= THashUtil.sha256AndBase64( _codeVerifier ) ;
queryItems:= TQueryItemsDictonary.Create;
queryItems.Add( 'client_id', _config.clientID );
queryItems.Add( 'redirect_uri', _config.listenURI );
queryItems.Add( 'code_challenge', codeChallenge );
queryItems.Add( 'code_challenge_method', 'S256' );
queryItems.Add( 'response_type', 'code' );
queryItems.Add( 'token_access_type', 'offline' );
queryItems.Add( 'state', _state );
THttpClientUtil.openInSafari( YandexConst.URI.OAUTH2, queryItems );
end;
procedure TCloudDriverAuthPKCESession.waitAuthorizationAndPrompt;
begin
NSApplication(NSAPP).setOpenURLObserver( @self.onRedirect );
_alert:= NSAlert.new;
_alert.setMessageText( StringToNSString('Waiting for ' + _driver.driverName + ' authorization') );
_alert.setInformativeText( StringToNSString('Please login your ' + _driver.driverName + ' account in Safari and authorize Double Commander to access. '#13'The authorization is completed on the ' + _driver.driverName + ' official website, Double Command will not get your password.') );
_alert.addButtonWithTitle( NSSTR('Cancel') );
_alert.runModal;
NSApplication(NSAPP).setOpenURLObserver( nil );
_alert.release;
_alert:= nil;
end;
procedure TCloudDriverAuthPKCESession.closePrompt;
var
button: NSButton;
begin
if _alert = nil then
Exit;
button:= NSButton( _alert.buttons.objectAtIndex(0) );
button.performClick( nil );
end;
procedure TCloudDriverAuthPKCESession.requestToken;
var
http: TMiniHttpClient = nil;
cloudDriverResult: TCloudDriverResult = nil;
procedure doRequest;
var
queryItems: TQueryItemsDictonary;
begin
queryItems:= TQueryItemsDictonary.Create;
queryItems.Add( 'client_id', _config.clientID );
queryItems.Add( 'redirect_uri', _config.listenURI );
queryItems.Add( 'code', _code );
queryItems.Add( 'code_verifier', _codeVerifier );
queryItems.Add( 'grant_type', 'authorization_code' );
http.setQueryParams( queryItems );
http.setContentType( HttpConst.ContentType.UrlEncoded );
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.connect;
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
CloudDriverProcessResult( cloudDriverResult );
end;
procedure analyseResult;
var
json: NSDictionary;
begin
json:= TJsonUtil.parse( cloudDriverResult.httpResult.body );
_token.access:= TJsonUtil.getString( json, 'access_token' );
_token.refresh:= TJsonUtil.getString( json, 'refresh_token' );
_token.setExpiration( TJsonUtil.getInteger( json, 'expires_in' ) );
_accountID:= TJsonUtil.getString( json, 'account_id' );
end;
begin
if _code = EmptyStr then
Exit;
try
http:= TMiniHttpClient.Create( YandexConst.URI.TOKEN, HttpConst.Method.POST );
doRequest;
if cloudDriverResult.httpResult.resultCode <> 200 then
Exit;
analyseResult;
cloudDriverManager.driverUpdated( _driver );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
end;
end;
procedure TCloudDriverAuthPKCESession.revokeToken;
var
http: TMiniHttpClient = nil;
begin
try
http:= TMiniHttpClient.Create( YandexConst.URI.REVOKE_TOKEN, HttpConst.Method.POST );
self.setAuthHeader( http );
http.connect;
finally
_token.invalid;
FreeAndNil( http );
end;
end;
procedure TCloudDriverAuthPKCESession.refreshToken;
var
http: TMiniHttpClient = nil;
cloudDriverResult: TCloudDriverResult = nil;
procedure doRequest;
var
queryItems: TQueryItemsDictonary;
begin
queryItems:= TQueryItemsDictonary.Create;
queryItems.Add( 'client_id', _config.clientID );
queryItems.Add( 'grant_type', 'refresh_token' );
queryItems.Add( 'refresh_token', _token.refresh );
http.setQueryParams( queryItems );
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.connect;
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
CloudDriverProcessResult( cloudDriverResult );
end;
procedure analyseResult;
var
json: NSDictionary;
begin
json:= TJsonUtil.parse( cloudDriverResult.httpResult.body );
_token.access:= TJsonUtil.getString( json, 'access_token' );
_token.setExpiration( TJsonUtil.getInteger( json, 'expires_in' ) );
end;
begin
try
http:= TMiniHttpClient.Create( YandexConst.URI.TOKEN, HttpConst.Method.POST );
doRequest;
if cloudDriverResult.httpResult.resultCode <> 200 then
Exit;
analyseResult;
cloudDriverManager.driverUpdated( _driver );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
end;
end;
procedure TCloudDriverAuthPKCESession.onRedirect(const url: NSURL);
var
components: NSURLComponents;
state: String;
begin
components:= NSURLComponents.componentsWithURL_resolvingAgainstBaseURL( url, False );
state:= THttpClientUtil.queryValue( components, 'state' );
if state <> _state then
Exit;
_code:= THttpClientUtil.queryValue( components, 'code' );
closePrompt;
end;
function TCloudDriverAuthPKCESession.getAccessToken: String;
procedure checkToken;
begin
try
if NOT _token.isValidAccessToken then begin
if _token.isValidFreshToken then begin
self.refreshToken;
end else begin
self.authorize;
end;
end;
except
on e: ECloudDriverTokenException do begin
TLogUtil.logError( 'Token Error: ' + e.ClassName + ': ' + e.Message );
_token.invalid;
self.authorize;
end;
end;
end;
begin
_lockObject.Acquire;
try
checkToken;
Result:= _token.access;
finally
_lockObject.Release;
end;
end;
constructor TCloudDriverAuthPKCESession.Create(
const config: TCloudDriverConfig;
const driver: TCloudDriver );
begin
_config:= config;
_driver:= driver;
_token:= TCloudDriverToken.Create;
_lockObject:= TCriticalSection.Create;
end;
destructor TCloudDriverAuthPKCESession.Destroy;
begin
FreeAndNil( _token );
FreeAndNil( _lockObject );
end;
function TCloudDriverAuthPKCESession.clone( const driver: TCloudDriver ): TCloudDriverAuthPKCESession;
begin
Result:= TCloudDriverAuthPKCESession.Create( _config, driver );
Result._accountID:= self._accountID;
Result._token:= self._token.clone;
end;
function TCloudDriverAuthPKCESession.authorize: Boolean;
begin
_lockObject.Acquire;
try
requestAuthorization;
TThread.Synchronize( TThread.CurrentThread, @waitAuthorizationAndPrompt );
requestToken;
Result:= self.authorized;
finally
_codeVerifier:= EmptyStr;
_state:= EmptyStr;
_code:= EmptyStr;
_lockObject.Release;
end;
end;
procedure TCloudDriverAuthPKCESession.unauthorize;
begin
_lockObject.Acquire;
try
revokeToken;
finally
_lockObject.Release;
end;
end;
function TCloudDriverAuthPKCESession.authorized: Boolean;
begin
Result:= (_token.access <> EmptyStr);
end;
procedure TCloudDriverAuthPKCESession.setAuthHeader(http: TMiniHttpClient);
var
access: String;
begin
access:= self.getAccessToken;
http.addHeader( YandexConst.HEADER.AUTH, 'OAuth ' + access );
end;
procedure TCloudDriverAuthPKCESession.setToken(const token: TCloudDriverToken);
var
oldToken: TCloudDriverToken;
begin
oldToken:= _token;
_token:= token;
oldToken.Free;
end;
function TCloudDriverAuthPKCESession.getToken: TCloudDriverToken;
begin
Result:= _token;
end;
{ TCloudDriverListFolderSession }
procedure TCloudDriverListFolderSession.listFolderFirst;
@ -600,7 +289,7 @@ begin
if httpResult.resultCode = 200 then
analyseListResult( httpResult.body );
CloudDriverProcessResult( cloudDriverResult );
YandexClientResultProcess( cloudDriverResult );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
@ -712,7 +401,7 @@ procedure TYandexDownloadSession.download;
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.connect;
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
CloudDriverProcessResult( cloudDriverResult );
YandexClientResultProcess( cloudDriverResult );
json:= TJsonUtil.parse( cloudDriverResult.resultMessage );
Result:= TJsonUtil.getString( json, 'href' );
@ -734,7 +423,7 @@ procedure TYandexDownloadSession.download;
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.download( _localPath, _callback );
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
CloudDriverProcessResult( cloudDriverResult );
YandexClientResultProcess( cloudDriverResult );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
@ -778,7 +467,7 @@ procedure TYandexUploadSession.upload;
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.connect;
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
CloudDriverProcessResult( cloudDriverResult );
YandexClientResultProcess( cloudDriverResult );
json:= TJsonUtil.parse( cloudDriverResult.resultMessage );
Result:= TJsonUtil.getString( json, 'href' );
@ -798,7 +487,7 @@ procedure TYandexUploadSession.upload;
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.uploadRange( _localPath, range, _callback );
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
CloudDriverProcessResult( cloudDriverResult );
YandexClientResultProcess( cloudDriverResult );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
@ -855,7 +544,7 @@ begin
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.connect;
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
CloudDriverProcessResult( cloudDriverResult );
YandexClientResultProcess( cloudDriverResult );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
@ -887,7 +576,7 @@ begin
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.connect;
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
CloudDriverProcessResult( cloudDriverResult );
YandexClientResultProcess( cloudDriverResult );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
@ -928,7 +617,7 @@ begin
cloudDriverResult:= TCloudDriverResult.Create;
cloudDriverResult.httpResult:= http.connect;
cloudDriverResult.resultMessage:= cloudDriverResult.httpResult.body;
CloudDriverProcessResult( cloudDriverResult );
YandexClientResultProcess( cloudDriverResult );
finally
FreeAndNil( cloudDriverResult );
FreeAndNil( http );
@ -987,9 +676,18 @@ begin
end;
constructor TYandexClient.Create(const config: TCloudDriverConfig);
var
params: TCloudDriverAuthPKCESessionParams;
begin
_config:= config;
_authSession:= TCloudDriverAuthPKCESession.Create( _config, self );
params.config:= config;
params.resultProcessFunc:= @YandexClientResultProcess;
params.OAUTH2_URI:= YandexConst.URI.OAUTH2;
params.TOKEN_URI:= YandexConst.URI.TOKEN;
params.REVOKE_TOKEN_URI:= YandexConst.URI.REVOKE_TOKEN;
params.AUTH_HEADER:= YandexConst.HEADER.AUTH;
params.AUTH_TYPE:= 'OAuth';
_authSession:= TCloudDriverAuthPKCESession.Create( self, params );
end;
destructor TYandexClient.Destroy;
@ -1002,7 +700,7 @@ var
newClient: TYandexClient;
begin
newClient:= TYandexClient.Create( _config );
newClient._authSession:= self._authSession.clone( self );
newClient._authSession:= self._authSession.clone( newClient );
Result:= newClient;
end;