diff options
author | Szymon Kłos <eszkadev@gmail.com> | 2016-05-15 00:52:50 +0200 |
---|---|---|
committer | David Tardon <dtardon@redhat.com> | 2016-05-16 07:24:23 +0000 |
commit | 511db54e95d02292417d7cd076e4bd6d50956d64 (patch) | |
tree | ab8b4d46a35b1c9cc52679e966e0786bd18ca916 | |
parent | 5e54d9e9bc856520808a446f20575e29a50d017b (diff) |
tdf#87938 libcmis: Google 2FA implementation
2 Factor Authentication for Google Drive, changes:
+ parsing additional page with challenge - pin code verification
+ calling the interaction dialog requesting the code
+ dialog title changed to more general: Authentication Code
+ dialog shows url field only if not empty
Change-Id: Idb3ebbad6a12849b9e50af87b46324bfbe966bab
Reviewed-on: https://gerrit.libreoffice.org/25002
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: David Tardon <dtardon@redhat.com>
-rw-r--r-- | external/libcmis/UnpackedTarball_cmis.mk | 1 | ||||
-rwxr-xr-x | external/libcmis/libcmis-google-2FA-implementation.patch | 156 | ||||
-rw-r--r-- | ucb/source/ucp/cmis/auth_provider.cxx | 42 | ||||
-rw-r--r-- | ucb/source/ucp/cmis/auth_provider.hxx | 4 | ||||
-rw-r--r-- | ucb/source/ucp/cmis/cmis_content.cxx | 3 | ||||
-rw-r--r-- | ucb/source/ucp/cmis/cmis_repo_content.cxx | 3 | ||||
-rw-r--r-- | uui/source/authfallbackdlg.cxx | 5 | ||||
-rw-r--r-- | uui/uiconfig/ui/authfallback.ui | 2 |
8 files changed, 214 insertions, 2 deletions
diff --git a/external/libcmis/UnpackedTarball_cmis.mk b/external/libcmis/UnpackedTarball_cmis.mk index 435e7c588018..bd66e2efeb5e 100644 --- a/external/libcmis/UnpackedTarball_cmis.mk +++ b/external/libcmis/UnpackedTarball_cmis.mk @@ -16,6 +16,7 @@ $(eval $(call gb_UnpackedTarball_set_patchlevel,cmis,1)) $(eval $(call gb_UnpackedTarball_add_patches,cmis, \ external/libcmis/libcmis-libxml2_compatibility.patch \ external/libcmis/libcmis-fix-google-drive.patch \ + external/libcmis/libcmis-google-2FA-implementation.patch \ )) ifeq ($(OS)$(COM),WNTMSC) diff --git a/external/libcmis/libcmis-google-2FA-implementation.patch b/external/libcmis/libcmis-google-2FA-implementation.patch new file mode 100755 index 000000000000..23f9775f6f59 --- /dev/null +++ b/external/libcmis/libcmis-google-2FA-implementation.patch @@ -0,0 +1,156 @@ +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,6 +29,7 @@ + #include <libxml/HTMLparser.h> + #include <libxml/xmlreader.h> + ++#include "session-factory.hxx" + #include "oauth2-providers.hxx" + #include "http-session.hxx" + +@@ -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,53 @@ 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( "", "", "" ) ); ++ ++ 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,24 @@ int OAuth2Providers::parseResponse ( const char* response, string& post, string& + { + xmlChar* action = xmlTextReaderGetAttribute( reader, + BAD_CAST( "action" )); ++ ++ // GDrive pin code page contains 3 forms. ++ // First form resends pin code, we need to omit its input fields. ++ // Also we mustn't parse action field from the 'select challenge' form. + if ( action != NULL ) + { +- if ( xmlStrlen(action) > 0) ++ if ( ( strcmp( (char*)action, "/signin/challenge" ) != 0 ) ++ && ( strcmp( (char*)action, "/signin/selectchallenge" ) != 0 ) ++ && xmlStrlen(action) > 0 ) ++ { + link = string ( (char*) action); ++ readInputField = true; ++ } + 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" )); + diff --git a/ucb/source/ucp/cmis/auth_provider.cxx b/ucb/source/ucp/cmis/auth_provider.cxx index 64cb84ecd6ca..0c3e4c0ec52f 100644 --- a/ucb/source/ucp/cmis/auth_provider.cxx +++ b/ucb/source/ucp/cmis/auth_provider.cxx @@ -110,6 +110,48 @@ namespace cmis return strdup( "" ); } + + char* AuthProvider::gdriveAuthCodeFallback( const char* /*url*/, + const char* /*username*/, + const char* /*password*/ ) + { + OUString instructions = "PIN:"; + OUString url_oustr( "" ); + const css::uno::Reference< + css::ucb::XCommandEnvironment> xEnv = getXEnv( ); + + if ( xEnv.is() ) + { + uno::Reference< task::XInteractionHandler > xIH + = xEnv->getInteractionHandler(); + + if ( xIH.is() ) + { + rtl::Reference< ucbhelper::AuthenticationFallbackRequest > xRequest + = new ucbhelper::AuthenticationFallbackRequest ( + instructions, url_oustr ); + + xIH->handle( xRequest.get() ); + + rtl::Reference< ucbhelper::InteractionContinuation > xSelection + = xRequest->getSelection(); + + if ( xSelection.is() ) + { + // Handler handled the request. + const rtl::Reference< ucbhelper::InteractionAuthFallback >& + xAuthFallback = xRequest->getAuthFallbackInter( ); + if ( xAuthFallback.is() ) + { + OUString code = xAuthFallback->getCode( ); + return strdup( OUSTR_TO_STDSTR( code ).c_str( ) ); + } + } + } + } + + return strdup( "" ); + } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/ucb/source/ucp/cmis/auth_provider.hxx b/ucb/source/ucp/cmis/auth_provider.hxx index 977254a8877c..e633e95bd012 100644 --- a/ucb/source/ucp/cmis/auth_provider.hxx +++ b/ucb/source/ucp/cmis/auth_provider.hxx @@ -34,6 +34,10 @@ namespace cmis const char* /*username*/, const char* /*password*/ ); + static char* gdriveAuthCodeFallback( const char* /*url*/, + const char* /*username*/, + const char* /*password*/ ); + static void setXEnv( const css::uno::Reference< css::ucb::XCommandEnvironment>& xEnv ) { sm_xEnv = xEnv; } static const css::uno::Reference< css::ucb::XCommandEnvironment>& getXEnv( ) { return sm_xEnv; } diff --git a/ucb/source/ucp/cmis/cmis_content.cxx b/ucb/source/ucp/cmis/cmis_content.cxx index dea8ec7b01ef..b097a6f4e0ca 100644 --- a/ucb/source/ucp/cmis/cmis_content.cxx +++ b/ucb/source/ucp/cmis/cmis_content.cxx @@ -376,10 +376,13 @@ namespace cmis // Initiate a CMIS session and register it as we found nothing libcmis::OAuth2DataPtr oauth2Data; if ( m_aURL.getBindingUrl( ) == GDRIVE_BASE_URL ) + { + libcmis::SessionFactory::setOAuth2AuthCodeProvider( authProvider.gdriveAuthCodeFallback ); oauth2Data.reset( new libcmis::OAuth2Data( GDRIVE_AUTH_URL, GDRIVE_TOKEN_URL, GDRIVE_SCOPE, GDRIVE_REDIRECT_URI, GDRIVE_CLIENT_ID, GDRIVE_CLIENT_SECRET ) ); + } if ( m_aURL.getBindingUrl().startsWith( ALFRESCO_CLOUD_BASE_URL ) ) oauth2Data.reset( new libcmis::OAuth2Data( ALFRESCO_CLOUD_AUTH_URL, ALFRESCO_CLOUD_TOKEN_URL, diff --git a/ucb/source/ucp/cmis/cmis_repo_content.cxx b/ucb/source/ucp/cmis/cmis_repo_content.cxx index 75b3790510e5..cc1df948df44 100644 --- a/ucb/source/ucp/cmis/cmis_repo_content.cxx +++ b/ucb/source/ucp/cmis/cmis_repo_content.cxx @@ -175,10 +175,13 @@ namespace cmis // Create a session to get repositories libcmis::OAuth2DataPtr oauth2Data; if ( m_aURL.getBindingUrl( ) == GDRIVE_BASE_URL ) + { + libcmis::SessionFactory::setOAuth2AuthCodeProvider( authProvider.gdriveAuthCodeFallback ); oauth2Data.reset( new libcmis::OAuth2Data( GDRIVE_AUTH_URL, GDRIVE_TOKEN_URL, GDRIVE_SCOPE, GDRIVE_REDIRECT_URI, GDRIVE_CLIENT_ID, GDRIVE_CLIENT_SECRET ) ); + } if ( m_aURL.getBindingUrl().startsWith( ALFRESCO_CLOUD_BASE_URL ) ) oauth2Data.reset( new libcmis::OAuth2Data( ALFRESCO_CLOUD_AUTH_URL, ALFRESCO_CLOUD_TOKEN_URL, diff --git a/uui/source/authfallbackdlg.cxx b/uui/source/authfallbackdlg.cxx index 70ed1e7396d9..a6383d1e4e21 100644 --- a/uui/source/authfallbackdlg.cxx +++ b/uui/source/authfallbackdlg.cxx @@ -29,7 +29,10 @@ AuthFallbackDlg::AuthFallbackDlg(Window* pParent, const OUString& instructions, m_pBTOk->Enable(); m_pTVInstructions->SetText( instructions ); - m_pEDUrl->SetText( url ); + if( url.isEmpty() ) + m_pEDUrl->Hide(); + else + m_pEDUrl->SetText( url ); } AuthFallbackDlg::~AuthFallbackDlg() diff --git a/uui/uiconfig/ui/authfallback.ui b/uui/uiconfig/ui/authfallback.ui index a947ad4e24f2..6c99edfe04cb 100644 --- a/uui/uiconfig/ui/authfallback.ui +++ b/uui/uiconfig/ui/authfallback.ui @@ -5,7 +5,7 @@ <object class="GtkDialog" id="AuthFallbackDlg"> <property name="can_focus">False</property> <property name="border_width">6</property> - <property name="title" translatable="yes">OneDrive Authentication Code</property> + <property name="title" translatable="yes">Authentication Code</property> <property name="default_width">400</property> <property name="default_height">270</property> <property name="type_hint">dialog</property> |