178 lines
6.8 KiB
Diff
178 lines
6.8 KiB
Diff
diff --git a/src/libcmis/oauth2-providers.cxx b/src/libcmis/oauth2-providers.cxx
|
|
--- a/src/libcmis/oauth2-providers.cxx
|
|
+++ b/src/libcmis/oauth2-providers.cxx
|
|
@@ -29,9 +29,15 @@
|
|
#include <libxml/HTMLparser.h>
|
|
#include <libxml/xmlreader.h>
|
|
|
|
+#include "session-factory.hxx"
|
|
#include "oauth2-providers.hxx"
|
|
#include "http-session.hxx"
|
|
|
|
+#define CHALLENGE_PAGE_ACTION "/signin"
|
|
+#define CHALLENGE_PAGE_ACTION_LEN sizeof( CHALLENGE_PAGE_ACTION ) - 1
|
|
+#define PIN_FORM_ACTION "/signin/challenge/ipp"
|
|
+#define PIN_FORM_ACTION_LEN sizeof( PIN_FORM_ACTION ) - 1
|
|
+
|
|
using namespace std;
|
|
|
|
#if LIBXML_VERSION < 20621
|
|
@@ -51,10 +52,23 @@ string OAuth2Providers::OAuth2Gdrive( HttpSession* session, const string& authUr
|
|
* 4) subsequent post to send a consent for the application
|
|
* receive a single-use authorization code
|
|
* this code is returned as a string
|
|
+ *
|
|
+ * Sequence with 2FA is:
|
|
+ * 1) a get to activate login page
|
|
+ * receive first login page, html format
|
|
+ * 2) subsequent post to sent email
|
|
+ * receive html page for password input
|
|
+ * 3) subsequent post to send password
|
|
+ * receive html page for pin input
|
|
+ * 3b) subsequent post to send pin number
|
|
+ * receive html page for application consent
|
|
+ * 4) subsequent post to send a consent for the application
|
|
+ * receive a single-use authorization code
|
|
+ * this code is returned as a string
|
|
*/
|
|
|
|
static const string CONTENT_TYPE( "application/x-www-form-urlencoded" );
|
|
- // STEP 1: Log in
|
|
+ // STEP 1: get login page
|
|
string res;
|
|
try
|
|
{
|
|
@@ -66,6 +80,8 @@ string OAuth2Providers::OAuth2Gdrive( HttpSession* session, const string& authUr
|
|
return string( );
|
|
}
|
|
|
|
+ // STEP 2: send email
|
|
+
|
|
string loginEmailPost, loginEmailLink;
|
|
if ( !parseResponse( res.c_str( ), loginEmailPost, loginEmailLink ) )
|
|
return string( );
|
|
@@ -86,6 +102,8 @@ string OAuth2Providers::OAuth2Gdrive( HttpSession* session, const string& authUr
|
|
return string( );
|
|
}
|
|
|
|
+ // STEP 3: password page
|
|
+
|
|
string loginPasswdPost, loginPasswdLink;
|
|
if ( !parseResponse( loginEmailRes.c_str( ), loginPasswdPost, loginPasswdLink ) )
|
|
return string( );
|
|
@@ -106,10 +124,60 @@ string OAuth2Providers::OAuth2Gdrive( HttpSession* session, const string& authUr
|
|
return string( );
|
|
}
|
|
|
|
- // STEP 2: allow libcmis to access google drive
|
|
string approvalPost, approvalLink;
|
|
if ( !parseResponse( loginPasswdRes. c_str( ), approvalPost, approvalLink) )
|
|
return string( );
|
|
+
|
|
+ // when 2FA is enabled, link doesn't start with 'http'
|
|
+ if ( approvalLink.compare(0, 4, "http") != 0 )
|
|
+ {
|
|
+ // STEP 3b: 2 Factor Authentication, pin code request
|
|
+
|
|
+ string loginChallengePost( approvalPost );
|
|
+ string loginChallengeLink( approvalLink );
|
|
+
|
|
+ libcmis::OAuth2AuthCodeProvider fallbackProvider = libcmis::SessionFactory::getOAuth2AuthCodeProvider( );
|
|
+ string pin( fallbackProvider( "", "", "" ) );
|
|
+
|
|
+ if( pin.empty() )
|
|
+ {
|
|
+ // unset OAuth2AuthCode Provider to avoid showing pin request again in the HttpSession::oauth2Authenticate
|
|
+ libcmis::SessionFactory::setOAuth2AuthCodeProvider( NULL );
|
|
+ return string( );
|
|
+ }
|
|
+
|
|
+ loginChallengeLink = "https://accounts.google.com" + loginChallengeLink;
|
|
+ loginChallengePost += "Pin=";
|
|
+ loginChallengePost += string( pin );
|
|
+
|
|
+ istringstream loginChallengeIs( loginChallengePost );
|
|
+ string loginChallengeRes;
|
|
+ try
|
|
+ {
|
|
+ // send a post with pin, receive the application consent page
|
|
+ loginChallengeRes = session->httpPostRequest ( loginChallengeLink, loginChallengeIs, CONTENT_TYPE )
|
|
+ ->getStream( )->str( );
|
|
+ }
|
|
+ catch ( const CurlException& e )
|
|
+ {
|
|
+ return string( );
|
|
+ }
|
|
+
|
|
+ approvalPost = string();
|
|
+ approvalLink = string();
|
|
+
|
|
+ if ( !parseResponse( loginChallengeRes. c_str( ), approvalPost, approvalLink) )
|
|
+ return string( );
|
|
+ }
|
|
+ else if( approvalLink.compare( "https://accounts.google.com/ServiceLoginAuth" ) == 0 )
|
|
+ {
|
|
+ // wrong password,
|
|
+ // unset OAuth2AuthCode Provider to avoid showing pin request again in the HttpSession::oauth2Authenticate
|
|
+ libcmis::SessionFactory::setOAuth2AuthCodeProvider( NULL );
|
|
+ return string( );
|
|
+ }
|
|
+
|
|
+ // STEP 4: allow libcmis to access google drive
|
|
approvalPost += "submit_access=true";
|
|
|
|
istringstream approvalIs( approvalPost );
|
|
@@ -125,7 +186,7 @@ string OAuth2Providers::OAuth2Gdrive( HttpSession* session, const string& authUr
|
|
throw e.getCmisException( );
|
|
}
|
|
|
|
- // STEP 3: Take the authentication code from the text bar
|
|
+ // Take the authentication code from the text bar
|
|
string code = parseCode( approvalRes.c_str( ) );
|
|
|
|
return code;
|
|
@@ -216,6 +277,9 @@ int OAuth2Providers::parseResponse ( const char* response, string& post, string&
|
|
if ( doc == NULL ) return 0;
|
|
xmlTextReaderPtr reader = xmlReaderWalker( doc );
|
|
if ( reader == NULL ) return 0;
|
|
+
|
|
+ bool readInputField = false;
|
|
+
|
|
while ( true )
|
|
{
|
|
// Go to the next node, quit if not found
|
|
@@ -227,15 +291,30 @@ int OAuth2Providers::parseResponse ( const char* response, string& post, string&
|
|
{
|
|
xmlChar* action = xmlTextReaderGetAttribute( reader,
|
|
BAD_CAST( "action" ));
|
|
+
|
|
+ // GDrive pin code page contains many forms.
|
|
+ // We have to parse only the form with pin field.
|
|
if ( action != NULL )
|
|
{
|
|
- if ( xmlStrlen(action) > 0)
|
|
+ bool bChallengePage = ( strncmp( (char*)action,
|
|
+ CHALLENGE_PAGE_ACTION,
|
|
+ CHALLENGE_PAGE_ACTION_LEN ) == 0 );
|
|
+ bool bIsRightForm = ( strncmp( (char*)action,
|
|
+ PIN_FORM_ACTION,
|
|
+ PIN_FORM_ACTION_LEN ) == 0 );
|
|
+ if ( ( xmlStrlen( action ) > 0 )
|
|
+ && ( ( bChallengePage && bIsRightForm ) || !bChallengePage ) )
|
|
+ {
|
|
link = string ( (char*) action);
|
|
+ readInputField = true;
|
|
+ }
|
|
+ else
|
|
+ readInputField = false;
|
|
xmlFree (action);
|
|
}
|
|
}
|
|
// Find input values
|
|
- if ( !xmlStrcmp( nodeName, BAD_CAST( "input" ) ) )
|
|
+ if ( readInputField && !xmlStrcmp( nodeName, BAD_CAST( "input" ) ) )
|
|
{
|
|
xmlChar* name = xmlTextReaderGetAttribute( reader,
|
|
BAD_CAST( "name" ));
|
|
|