From 8bfa97c7094284e1e45e232842dfc1ddbc066a2a Mon Sep 17 00:00:00 2001 From: Brian Clark Date: Sun, 28 Aug 2022 10:38:57 -0400 Subject: [PATCH 1/4] Implement Google installed app loopback redirect OAuth flow --- README.md | 16 ++++---- debian/control | 2 +- grive/CMakeLists.txt | 4 ++ grive/src/main.cc | 80 +++++++++++++++++++++++++++++++++---- package/fedora16/grive.spec | 1 + 5 files changed, 87 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 35eee2a..ff8eecf 100644 --- a/README.md +++ b/README.md @@ -39,12 +39,12 @@ grive -a A URL should be printed. Go to the link. You will need to login to your Google account if you haven't done so. After granting the permission to Grive, the -browser will show you an authenication code. Copy-and-paste that to the -standard input of Grive. +authorization code will be forwarded to the Grive application and you will be +redirected to a localhost web page confirming the authorization. -If everything works fine, Grive will create .grive and .grive_state files in your -current directory. It will also start downloading files from your Google Drive to -your current directory. +If everything works fine, Grive will create .grive and .grive\_state files in +your current directory. It will also start downloading files from your Google +Drive to your current directory. To resync the direcory, run `grive` in the folder. @@ -154,6 +154,7 @@ You need the following libraries: - libgcrypt - Boost (Boost filesystem, program_options, regex, unit_test_framework and system are required) - expat +- libcpprest-dev There are also some optional dependencies: - CppUnit (for unit tests) @@ -165,16 +166,17 @@ these packages: sudo apt-get install git cmake build-essential libgcrypt20-dev libyajl-dev \ libboost-all-dev libcurl4-openssl-dev libexpat1-dev libcppunit-dev binutils-dev \ - debhelper zlib1g-dev dpkg-dev pkg-config + debhelper zlib1g-dev dpkg-dev pkg-config libcpprest-dev Fedora: - sudo dnf install git cmake libgcrypt-devel gcc-c++ libstdc++ yajl-devel boost-devel libcurl-devel expat-devel binutils zlib + sudo dnf install git cmake libgcrypt-devel gcc-c++ libstdc++ yajl-devel boost-devel libcurl-devel expat-devel binutils zlib cpprest-devel FreeBSD: pkg install git cmake boost-libs yajl libgcrypt pkgconf cppunit libbfd + cpprestsdk ### Build Debian packages diff --git a/debian/control b/debian/control index 150f688..65b4f91 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: grive2 Section: net Priority: optional Maintainer: Vitaliy Filippov -Build-Depends: debhelper, cmake, pkg-config, zlib1g-dev, libcurl4-openssl-dev | libcurl4-gnutls-dev, libboost-filesystem-dev, libboost-program-options-dev, libboost-test-dev, libboost-regex-dev, libexpat1-dev, libgcrypt-dev, libyajl-dev +Build-Depends: debhelper, cmake, pkg-config, zlib1g-dev, libcurl4-openssl-dev | libcurl4-gnutls-dev, libboost-filesystem-dev, libboost-program-options-dev, libboost-test-dev, libboost-regex-dev, libexpat1-dev, libgcrypt-dev, libyajl-dev, libcpprest-dev Standards-Version: 3.9.6 Homepage: https://yourcmc.ru/wiki/Grive2 diff --git a/grive/CMakeLists.txt b/grive/CMakeLists.txt index ac5b781..def223d 100644 --- a/grive/CMakeLists.txt +++ b/grive/CMakeLists.txt @@ -1,6 +1,8 @@ project( grive ) find_package(Boost COMPONENTS program_options REQUIRED) +find_package(cpprestsdk REQUIRED) +find_package(OpenSSL REQUIRED) include_directories( ${grive_SOURCE_DIR}/../libgrive/src @@ -18,6 +20,8 @@ add_executable( grive_executable target_link_libraries( grive_executable grive + OpenSSL::Crypto + cpprestsdk::cpprest ) set(DEFAULT_APP_ID "615557989097-i93d4d1ojpen0m0dso18ldr6orjkidgf.apps.googleusercontent.com") diff --git a/grive/src/main.cc b/grive/src/main.cc index 54d5560..9e89285 100644 --- a/grive/src/main.cc +++ b/grive/src/main.cc @@ -45,13 +45,21 @@ #include #include #include +#include + +#include +#include const std::string default_id = APP_ID ; const std::string default_secret = APP_SECRET ; +const std::string default_redirect_uri = "http://localhost:9898" ; using namespace gr ; namespace po = boost::program_options; +using namespace web::http::experimental::listener; +using namespace web::http; + // libgcrypt insist this to be done in application, not library void InitGCrypt() { @@ -101,6 +109,63 @@ void InitLog( const po::variables_map& vm ) LogBase::Inst( comp_log.release() ) ; } +// AuthCode reads an authorization code from the "code" query parameter passed +// via client-side redirect to the redirect_uri specified in uri +std::string AuthCode( std::string uri ) +{ + // Set up an HTTP listener that is waiting for Google + // to hit the specified local URI with the authorization + // code response + http_listener listener(uri); + listener. + open(). + then([uri]() { + std::cout + << "\n" + << "Listening on " << uri << " for an authorization code from Google" + << std::endl; + }). + wait(); + + // Set up a promise to read the authorization code from the + // redirect + std::promise result; + listener.support(methods::GET, [&result] (http_request req) { + // Extract the "code" query parameter + auto params = uri::split_query(req.request_uri().query()); + auto found_code = params.find("code"); + + // If auth code is missing, respond with basic web page + // containing error message + if (found_code == params.end()) { + std::cout + << "request received without auth code: " << req.absolute_uri().to_string() + << std::endl; + auto msg = + "grive2 authorization code redirect missing 'code' query parameter.\n\n" + "Try the auth flow again."; + req.reply(status_codes::BadRequest, msg); + } + + // If found, respond with basic web page telling user to close + // the window and pass the actual code back to the rest of the + // application + auto code = found_code->second; + std::cout << "received authorization code" << std::endl; + auto msg = "Received grive2 authorization code. You may now close this window."; + req.reply(status_codes::OK, msg).wait(); + result.set_value(code); + }); + + // Block until we receive an auth code + std::string code = result.get_future().get(); + + // Having received the code, block until listener is shut down + listener.close().wait(); + + return code; +} + int Main( int argc, char **argv ) { InitGCrypt() ; @@ -115,6 +180,7 @@ int Main( int argc, char **argv ) ( "secret,e", po::value(), "Authentication secret") ( "print-url", "Only print url for request") ( "path,p", po::value(), "Path to working copy root") + ( "redirect-uri", po::value(), "local URI on which to listen for auth redirect") ( "dir,s", po::value(), "Single subdirectory to sync") ( "verbose,V", "Verbose mode. Enable more messages than normal.") ( "log-http", po::value(), "Log all HTTP responses in this file for debugging.") @@ -183,6 +249,9 @@ int Main( int argc, char **argv ) std::string secret = vm.count( "secret" ) > 0 ? vm["secret"].as() : default_secret ; + std::string uri = vm.count( "redirect-uri" ) > 0 + ? vm["redirect-uri"].as() + : default_redirect_uri ; OAuth2 token( http.get(), id, secret ) ; @@ -194,16 +263,11 @@ int Main( int argc, char **argv ) std::cout << "-----------------------\n" - << "Please go to this URL and get an authentication code:\n\n" + << "Please go to this URL to authorize the app:\n\n" << token.MakeAuthURL() << std::endl ; - - std::cout - << "\n-----------------------\n" - << "Please input the authentication code here: " << std::endl ; - std::string code ; - std::cin >> code ; - + + std::string code = AuthCode(uri); token.Auth( code ) ; // save to config diff --git a/package/fedora16/grive.spec b/package/fedora16/grive.spec index 3a7e767..2f774d9 100644 --- a/package/fedora16/grive.spec +++ b/package/fedora16/grive.spec @@ -36,6 +36,7 @@ BuildRequires: expat-devel BuildRequires: openssl-devel BuildRequires: boost-devel BuildRequires: binutils-devel +BuildRequires: cpprest-devel %description The purpose of this project is to provide an independent implementation From 3b5cb0f0994585106b9faa6f8ddfc9e285a234f5 Mon Sep 17 00:00:00 2001 From: Brian Clark Date: Sun, 28 Aug 2022 16:04:29 -0400 Subject: [PATCH 2/4] Incorporate redirect_uri into OAuth and Config modules --- grive/src/main.cc | 11 +++++++---- libgrive/src/protocol/OAuth2.cc | 18 +++++++++++------- libgrive/src/protocol/OAuth2.hh | 7 +++++-- libgrive/src/util/Config.cc | 2 ++ 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/grive/src/main.cc b/grive/src/main.cc index 9e89285..d791190 100644 --- a/grive/src/main.cc +++ b/grive/src/main.cc @@ -249,11 +249,11 @@ int Main( int argc, char **argv ) std::string secret = vm.count( "secret" ) > 0 ? vm["secret"].as() : default_secret ; - std::string uri = vm.count( "redirect-uri" ) > 0 + std::string redirect_uri = vm.count( "redirect-uri" ) > 0 ? vm["redirect-uri"].as() : default_redirect_uri ; - OAuth2 token( http.get(), id, secret ) ; + OAuth2 token( http.get(), id, secret, redirect_uri ) ; if ( vm.count("print-url") ) { @@ -267,24 +267,27 @@ int Main( int argc, char **argv ) << token.MakeAuthURL() << std::endl ; - std::string code = AuthCode(uri); + std::string code = AuthCode(redirect_uri); token.Auth( code ) ; // save to config config.Set( "id", Val( id ) ) ; config.Set( "secret", Val( secret ) ) ; config.Set( "refresh_token", Val( token.RefreshToken() ) ) ; + config.Set( "redirect_uri", Val( redirect_uri ) ) ; config.Save() ; } std::string refresh_token ; std::string id ; std::string secret ; + std::string redirect_uri ; try { refresh_token = config.Get("refresh_token").Str() ; id = config.Get("id").Str() ; secret = config.Get("secret").Str() ; + redirect_uri = config.Get("redirect_uri").Str() ; } catch ( Exception& e ) { @@ -296,7 +299,7 @@ int Main( int argc, char **argv ) return -1; } - OAuth2 token( http.get(), refresh_token, id, secret ) ; + OAuth2 token( http.get(), refresh_token, id, secret, redirect_uri ) ; AuthAgent agent( token, http.get() ) ; v2::Syncer2 syncer( &agent ); diff --git a/libgrive/src/protocol/OAuth2.cc b/libgrive/src/protocol/OAuth2.cc index db1858a..0562c1f 100644 --- a/libgrive/src/protocol/OAuth2.cc +++ b/libgrive/src/protocol/OAuth2.cc @@ -35,12 +35,14 @@ const std::string token_url = "https://accounts.google.com/o/oauth2/token" ; OAuth2::OAuth2( http::Agent* agent, const std::string& refresh_code, - const std::string& client_id, - const std::string& client_secret ) : + const std::string& client_id, + const std::string& client_secret, + const std::string& redirect_uri ) : m_refresh( refresh_code ), m_agent( agent ), m_client_id( client_id ), - m_client_secret( client_secret ) + m_client_secret( client_secret ), + m_redirect_uri ( redirect_uri ) { Refresh( ) ; } @@ -48,10 +50,12 @@ OAuth2::OAuth2( OAuth2::OAuth2( http::Agent* agent, const std::string& client_id, - const std::string& client_secret ) : + const std::string& client_secret, + const std::string& redirect_uri ) : m_agent( agent ), m_client_id( client_id ), - m_client_secret( client_secret ) + m_client_secret( client_secret ), + m_redirect_uri( redirect_uri ) { } @@ -61,7 +65,7 @@ void OAuth2::Auth( const std::string& auth_code ) "code=" + auth_code + "&client_id=" + m_client_id + "&client_secret=" + m_client_secret + - "&redirect_uri=" + "urn:ietf:wg:oauth:2.0:oob" + + "&redirect_uri=" + m_redirect_uri + "&grant_type=authorization_code" ; http::ValResponse resp ; @@ -85,7 +89,7 @@ std::string OAuth2::MakeAuthURL() { return "https://accounts.google.com/o/oauth2/auth" "?scope=" + m_agent->Escape( "https://www.googleapis.com/auth/drive" ) + - "&redirect_uri=urn:ietf:wg:oauth:2.0:oob" + "&redirect_uri=" + m_redirect_uri + "&response_type=code" "&client_id=" + m_client_id ; } diff --git a/libgrive/src/protocol/OAuth2.hh b/libgrive/src/protocol/OAuth2.hh index e9a23da..4598f09 100644 --- a/libgrive/src/protocol/OAuth2.hh +++ b/libgrive/src/protocol/OAuth2.hh @@ -35,12 +35,14 @@ public : OAuth2( http::Agent* agent, const std::string& client_id, - const std::string& client_secret ) ; + const std::string& client_secret, + const std::string& redirect_uri ) ; OAuth2( http::Agent* agent, const std::string& refresh_code, const std::string& client_id, - const std::string& client_secret ) ; + const std::string& client_secret, + const std::string& redirect_uri ) ; std::string Str() const ; @@ -62,6 +64,7 @@ private : const std::string m_client_id ; const std::string m_client_secret ; + const std::string m_redirect_uri ; } ; } // end of namespace diff --git a/libgrive/src/util/Config.cc b/libgrive/src/util/Config.cc index c08972f..261de6e 100644 --- a/libgrive/src/util/Config.cc +++ b/libgrive/src/util/Config.cc @@ -47,6 +47,8 @@ Config::Config( const po::variables_map& vm ) m_cmd.Add( "path", Val(vm.count("path") > 0 ? vm["path"].as() : default_root_folder ) ) ; + if ( vm.count( "redirect_uri" ) > 0 ) + m_cmd.Add( "redirect_uri", Val( vm["redirect_uri"].as() ) ) ; m_cmd.Add( "dir", Val(vm.count("dir") > 0 ? vm["dir"].as() : "" ) ) ; From ec50a5621bd98522af2b6b7c1cf814112bacf55e Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 10 Sep 2022 12:44:31 -0400 Subject: [PATCH 3/4] Allow runs on a valid refresh token without requiring a redirect URI be provided --- grive/src/main.cc | 11 ++++------- libgrive/src/util/Config.cc | 19 +++++++++++++------ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/grive/src/main.cc b/grive/src/main.cc index d791190..c8ccf0e 100644 --- a/grive/src/main.cc +++ b/grive/src/main.cc @@ -52,7 +52,6 @@ const std::string default_id = APP_ID ; const std::string default_secret = APP_SECRET ; -const std::string default_redirect_uri = "http://localhost:9898" ; using namespace gr ; namespace po = boost::program_options; @@ -110,7 +109,7 @@ void InitLog( const po::variables_map& vm ) } // AuthCode reads an authorization code from the "code" query parameter passed -// via client-side redirect to the redirect_uri specified in uri +// via client-side redirect to the redirect uri specified in uri std::string AuthCode( std::string uri ) { // Set up an HTTP listener that is waiting for Google @@ -249,9 +248,7 @@ int Main( int argc, char **argv ) std::string secret = vm.count( "secret" ) > 0 ? vm["secret"].as() : default_secret ; - std::string redirect_uri = vm.count( "redirect-uri" ) > 0 - ? vm["redirect-uri"].as() - : default_redirect_uri ; + std::string redirect_uri = config.Get("redirect-uri").Str(); OAuth2 token( http.get(), id, secret, redirect_uri ) ; @@ -274,7 +271,7 @@ int Main( int argc, char **argv ) config.Set( "id", Val( id ) ) ; config.Set( "secret", Val( secret ) ) ; config.Set( "refresh_token", Val( token.RefreshToken() ) ) ; - config.Set( "redirect_uri", Val( redirect_uri ) ) ; + config.Set( "redirect-uri", Val( redirect_uri ) ) ; config.Save() ; } @@ -287,7 +284,7 @@ int Main( int argc, char **argv ) refresh_token = config.Get("refresh_token").Str() ; id = config.Get("id").Str() ; secret = config.Get("secret").Str() ; - redirect_uri = config.Get("redirect_uri").Str() ; + redirect_uri = config.Get("redirect-uri").Str() ; } catch ( Exception& e ) { diff --git a/libgrive/src/util/Config.cc b/libgrive/src/util/Config.cc index 261de6e..870b6f8 100644 --- a/libgrive/src/util/Config.cc +++ b/libgrive/src/util/Config.cc @@ -32,9 +32,10 @@ namespace po = boost::program_options; namespace gr { -const std::string default_filename = ".grive"; -const char *env_name = "GR_CONFIG"; -const std::string default_root_folder = "."; +const char *env_name = "GR_CONFIG"; +const std::string default_filename = ".grive"; +const std::string default_root_folder = "."; +const std::string default_redirect_uri = "http://localhost:9898" ; Config::Config( const po::variables_map& vm ) { @@ -47,8 +48,6 @@ Config::Config( const po::variables_map& vm ) m_cmd.Add( "path", Val(vm.count("path") > 0 ? vm["path"].as() : default_root_folder ) ) ; - if ( vm.count( "redirect_uri" ) > 0 ) - m_cmd.Add( "redirect_uri", Val( vm["redirect_uri"].as() ) ) ; m_cmd.Add( "dir", Val(vm.count("dir") > 0 ? vm["dir"].as() : "" ) ) ; @@ -60,6 +59,14 @@ Config::Config( const po::variables_map& vm ) m_path = GetPath( fs::path(m_cmd["path"].Str()) ) ; m_file = Read( ) ; + + if ( vm.count( "redirect-uri" ) > 0 ) { + m_cmd.Add( "redirect-uri", Val( vm["redirect-uri"].as() ) ); + } else if (m_file.Has( "redirect-uri" )) { + ; + } else { + m_cmd.Add( "redirect-uri", Val( default_redirect_uri ) ); + } } fs::path Config::GetPath( const fs::path& root_path ) @@ -86,7 +93,7 @@ void Config::Save( ) void Config::Set( const std::string& key, const Val& value ) { - m_file.Add( key, value ) ; + m_file.Set( key, value ) ; } Val Config::Get( const std::string& key ) const From 58a183c876341b54826d15cdbb80896b88adba07 Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 10 Sep 2022 13:25:41 -0400 Subject: [PATCH 4/4] Add cpprestsdk hyperlink --- README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ff8eecf..f1dc333 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,28 @@ + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Grive2 0.5.2-dev](#grive2-052-dev) + - [Usage](#usage) + - [Exclude specific files and folders from sync: .griveignore](#exclude-specific-files-and-folders-from-sync-griveignore) + - [Scheduled syncs and syncs on file change events](#scheduled-syncs-and-syncs-on-file-change-events) + - [Shared files](#shared-files) + - [Different OAuth2 client to workaround over quota and google approval issues](#different-oauth2-client-to-workaround-over-quota-and-google-approval-issues) + - [Installation](#installation) + - [Install dependencies](#install-dependencies) + - [Build Debian packages](#build-debian-packages) + - [Manual build](#manual-build) + - [Version History](#version-history) + - [Grive2 v0.5.2-dev](#grive2-v052-dev) + - [Grive2 v0.5.1](#grive2-v051) + - [Grive2 v0.5](#grive2-v05) + - [Grive2 v0.4.2](#grive2-v042) + - [Grive2 v0.4.1](#grive2-v041) + - [Grive2 v0.4.0](#grive2-v040) + - [Grive v0.3](#grive-v03) + + + # Grive2 0.5.2-dev 13 Nov 2019, Vitaliy Filippov @@ -154,7 +179,7 @@ You need the following libraries: - libgcrypt - Boost (Boost filesystem, program_options, regex, unit_test_framework and system are required) - expat -- libcpprest-dev +- [libcpprest-dev](https://github.com/Microsoft/cpprestsdk) There are also some optional dependencies: - CppUnit (for unit tests)