Implement Google OAuth loopback IP address flow (fixes #362 #372 #365)

master
Vitaliy Filippov 2022-11-09 12:37:10 +03:00
parent 6901fbb169
commit 6645206d27
4 changed files with 146 additions and 25 deletions

View File

@ -194,17 +194,15 @@ int Main( int argc, char **argv )
std::cout std::cout
<< "-----------------------\n" << "-----------------------\n"
<< "Please go to this URL and get an authentication code:\n\n" << "Please open this URL in your browser to authenticate Grive2:\n\n"
<< token.MakeAuthURL() << token.MakeAuthURL()
<< std::endl ; << std::endl ;
std::cout if ( !token.GetCode() )
<< "\n-----------------------\n" {
<< "Please input the authentication code here: " << std::endl ; std::cout << "Authentication failed\n";
std::string code ; return -1;
std::cin >> code ; }
token.Auth( code ) ;
// save to config // save to config
config.Set( "id", Val( id ) ) ; config.Set( "id", Val( id ) ) ;

View File

@ -25,6 +25,13 @@
#include "http/Header.hh" #include "http/Header.hh"
#include "util/log/Log.hh" #include "util/log/Log.hh"
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
// for debugging // for debugging
#include <iostream> #include <iostream>
@ -50,18 +57,29 @@ OAuth2::OAuth2(
const std::string& client_id, const std::string& client_id,
const std::string& client_secret ) : const std::string& client_secret ) :
m_agent( agent ), m_agent( agent ),
m_port( 0 ),
m_socket( -1 ),
m_client_id( client_id ), m_client_id( client_id ),
m_client_secret( client_secret ) m_client_secret( client_secret )
{ {
} }
void OAuth2::Auth( const std::string& auth_code ) OAuth2::~OAuth2()
{
if ( m_socket >= 0 )
{
close( m_socket );
m_socket = -1;
}
}
bool OAuth2::Auth( const std::string& auth_code )
{ {
std::string post = std::string post =
"code=" + auth_code + "code=" + auth_code +
"&client_id=" + m_client_id + "&client_id=" + m_client_id +
"&client_secret=" + m_client_secret + "&client_secret=" + m_client_secret +
"&redirect_uri=" + "urn:ietf:wg:oauth:2.0:oob" + "&redirect_uri=http%3A%2F%2Flocalhost:" + std::to_string( m_port ) + "%2Fauth" +
"&grant_type=authorization_code" ; "&grant_type=authorization_code" ;
http::ValResponse resp ; http::ValResponse resp ;
@ -77,19 +95,120 @@ void OAuth2::Auth( const std::string& auth_code )
{ {
Log( "Failed to obtain auth token: HTTP %1%, body: %2%", Log( "Failed to obtain auth token: HTTP %1%, body: %2%",
code, m_agent->LastError(), log::error ) ; code, m_agent->LastError(), log::error ) ;
BOOST_THROW_EXCEPTION( AuthFailed() ); return false;
} }
return true;
} }
std::string OAuth2::MakeAuthURL() std::string OAuth2::MakeAuthURL()
{ {
if ( !m_port )
{
sockaddr_storage addr = { 0 };
addr.ss_family = AF_INET;
m_socket = socket( AF_INET, SOCK_STREAM, 0 );
if ( m_socket < 0 )
throw std::runtime_error( std::string("socket: ") + strerror(errno) );
if ( bind( m_socket, (sockaddr*)&addr, sizeof( addr ) ) < 0 )
{
close( m_socket );
m_socket = -1;
throw std::runtime_error( std::string("bind: ") + strerror(errno) );
}
socklen_t len = sizeof( addr );
if ( getsockname( m_socket, (sockaddr *)&addr, &len ) == -1 )
{
close( m_socket );
m_socket = -1;
throw std::runtime_error( std::string("getsockname: ") + strerror(errno) );
}
m_port = ntohs(((sockaddr_in*)&addr)->sin_port);
if ( listen( m_socket, 128 ) < 0 )
{
close( m_socket );
m_socket = -1;
m_port = 0;
throw std::runtime_error( std::string("listen: ") + strerror(errno) );
}
}
return "https://accounts.google.com/o/oauth2/auth" return "https://accounts.google.com/o/oauth2/auth"
"?scope=" + m_agent->Escape( "https://www.googleapis.com/auth/drive" ) + "?scope=" + m_agent->Escape( "https://www.googleapis.com/auth/drive" ) +
"&redirect_uri=urn:ietf:wg:oauth:2.0:oob" "&redirect_uri=http%3A%2F%2Flocalhost:" + std::to_string( m_port ) + "%2Fauth" +
"&response_type=code" "&response_type=code"
"&client_id=" + m_client_id ; "&client_id=" + m_client_id ;
} }
bool OAuth2::GetCode( )
{
sockaddr_storage addr = { 0 };
int peer_fd = -1;
while ( peer_fd < 0 )
{
socklen_t peer_addr_size = sizeof( addr );
peer_fd = accept( m_socket, (sockaddr*)&addr, &peer_addr_size );
if ( peer_fd == -1 && errno != EAGAIN && errno != EINTR )
throw std::runtime_error( std::string("accept: ") + strerror(errno) );
}
fcntl( peer_fd, F_SETFL, fcntl( peer_fd, F_GETFL, 0 ) | O_NONBLOCK );
struct pollfd pfd = (struct pollfd){
.fd = peer_fd,
.events = POLLIN|POLLRDHUP,
};
char buf[4096];
std::string request;
while ( true )
{
pfd.revents = 0;
poll( &pfd, 1, -1 );
if ( pfd.revents & POLLRDHUP )
break;
int r = 1;
while ( r > 0 )
{
r = read( peer_fd, buf, sizeof( buf ) );
if ( r > 0 )
request += std::string( buf, r );
else if ( r == 0 )
break;
else if ( errno != EAGAIN && errno != EINTR )
throw std::runtime_error( std::string("read: ") + strerror(errno) );
}
if ( r == 0 || ( r < 0 && request.find( "\n" ) > 0 ) ) // GET ... HTTP/1.1\r\n
break;
}
bool ok = false;
if ( request.substr( 0, 10 ) == "GET /auth?" )
{
std::string line = request;
int p = line.find( "\n" );
if ( p > 0 )
line = line.substr( 0, p );
p = line.rfind( " " );
if ( p > 0 )
line = line.substr( 0, p );
p = line.find( "code=" );
if ( p > 0 )
line = line.substr( p+5 );
p = line.find( "&" );
if ( p > 0 )
line = line.substr( 0, p );
ok = Auth( line );
}
std::string response = ( ok
? "Authenticated successfully. Please close the page"
: "Authentication error. Please try again" );
response = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Connection: close\r\n"
"\r\n"+
response+
"\r\n";
write( peer_fd, response.c_str(), response.size() );
close( peer_fd );
return ok;
}
void OAuth2::Refresh( ) void OAuth2::Refresh( )
{ {
std::string post = std::string post =

View File

@ -41,13 +41,15 @@ public :
const std::string& refresh_code, const std::string& refresh_code,
const std::string& client_id, const std::string& client_id,
const std::string& client_secret ) ; const std::string& client_secret ) ;
~OAuth2( ) ;
std::string Str() const ; std::string Str() const ;
std::string MakeAuthURL() ; std::string MakeAuthURL() ;
void Auth( const std::string& auth_code ) ; bool Auth( const std::string& auth_code ) ;
void Refresh( ) ; void Refresh( ) ;
bool GetCode( ) ;
std::string RefreshToken( ) const ; std::string RefreshToken( ) const ;
std::string AccessToken( ) const ; std::string AccessToken( ) const ;
@ -59,6 +61,8 @@ private :
std::string m_access ; std::string m_access ;
std::string m_refresh ; std::string m_refresh ;
http::Agent* m_agent ; http::Agent* m_agent ;
int m_port ;
int m_socket ;
const std::string m_client_id ; const std::string m_client_id ;
const std::string m_client_secret ; const std::string m_client_secret ;

View File

@ -84,7 +84,7 @@ void Config::Save( )
void Config::Set( const std::string& key, const Val& value ) 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 Val Config::Get( const std::string& key ) const