summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSzymon Kłos <eszkadev@gmail.com>2016-05-15 00:52:50 +0200
committerDavid Tardon <dtardon@redhat.com>2016-05-16 07:24:23 +0000
commit511db54e95d02292417d7cd076e4bd6d50956d64 (patch)
treeab8b4d46a35b1c9cc52679e966e0786bd18ca916
parent5e54d9e9bc856520808a446f20575e29a50d017b (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.mk1
-rwxr-xr-xexternal/libcmis/libcmis-google-2FA-implementation.patch156
-rw-r--r--ucb/source/ucp/cmis/auth_provider.cxx42
-rw-r--r--ucb/source/ucp/cmis/auth_provider.hxx4
-rw-r--r--ucb/source/ucp/cmis/cmis_content.cxx3
-rw-r--r--ucb/source/ucp/cmis/cmis_repo_content.cxx3
-rw-r--r--uui/source/authfallbackdlg.cxx5
-rw-r--r--uui/uiconfig/ui/authfallback.ui2
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>