Version 6.1.4.1, tag libreoffice-6.1.4.1
[LibreOffice.git] / external / libcmis / libcmis-google-2FA-implementation.patch
blobefa7f031019176983716ca25d30177bc5f25b7e1
1 diff --git a/src/libcmis/oauth2-providers.cxx b/src/libcmis/oauth2-providers.cxx
2 --- a/src/libcmis/oauth2-providers.cxx
3 +++ b/src/libcmis/oauth2-providers.cxx
4 @@ -29,9 +29,15 @@
5 #include <libxml/HTMLparser.h>
6 #include <libxml/xmlreader.h>
8 +#include "session-factory.hxx"
9 #include "oauth2-providers.hxx"
10 #include "http-session.hxx"
12 +#define CHALLENGE_PAGE_ACTION "/signin"
13 +#define CHALLENGE_PAGE_ACTION_LEN sizeof( CHALLENGE_PAGE_ACTION ) - 1
14 +#define PIN_FORM_ACTION "/signin/challenge/ipp"
15 +#define PIN_FORM_ACTION_LEN sizeof( PIN_FORM_ACTION ) - 1
17 using namespace std;
19 #if LIBXML_VERSION < 20621
20 @@ -51,10 +52,23 @@ string OAuth2Providers::OAuth2Gdrive( HttpSession* session, const string& authUr
21 * 4) subsequent post to send a consent for the application
22 * receive a single-use authorization code
23 * this code is returned as a string
24 + *
25 + * Sequence with 2FA is:
26 + * 1) a get to activate login page
27 + * receive first login page, html format
28 + * 2) subsequent post to sent email
29 + * receive html page for password input
30 + * 3) subsequent post to send password
31 + * receive html page for pin input
32 + * 3b) subsequent post to send pin number
33 + * receive html page for application consent
34 + * 4) subsequent post to send a consent for the application
35 + * receive a single-use authorization code
36 + * this code is returned as a string
39 static const string CONTENT_TYPE( "application/x-www-form-urlencoded" );
40 - // STEP 1: Log in
41 + // STEP 1: get login page
42 string res;
43 try
45 @@ -66,6 +80,8 @@ string OAuth2Providers::OAuth2Gdrive( HttpSession* session, const string& authUr
46 return string( );
49 + // STEP 2: send email
51 string loginEmailPost, loginEmailLink;
52 if ( !parseResponse( res.c_str( ), loginEmailPost, loginEmailLink ) )
53 return string( );
54 @@ -86,6 +102,8 @@ string OAuth2Providers::OAuth2Gdrive( HttpSession* session, const string& authUr
55 return string( );
58 + // STEP 3: password page
60 string loginPasswdPost, loginPasswdLink;
61 if ( !parseResponse( loginEmailRes.c_str( ), loginPasswdPost, loginPasswdLink ) )
62 return string( );
63 @@ -106,10 +124,60 @@ string OAuth2Providers::OAuth2Gdrive( HttpSession* session, const string& authUr
64 return string( );
67 - // STEP 2: allow libcmis to access google drive
68 string approvalPost, approvalLink;
69 if ( !parseResponse( loginPasswdRes. c_str( ), approvalPost, approvalLink) )
70 return string( );
72 + // when 2FA is enabled, link doesn't start with 'http'
73 + if ( approvalLink.compare(0, 4, "http") != 0 )
74 + {
75 + // STEP 3b: 2 Factor Authentication, pin code request
77 + string loginChallengePost( approvalPost );
78 + string loginChallengeLink( approvalLink );
80 + libcmis::OAuth2AuthCodeProvider fallbackProvider = libcmis::SessionFactory::getOAuth2AuthCodeProvider( );
81 + string pin( fallbackProvider( "", "", "" ) );
83 + if( pin.empty() )
84 + {
85 + // unset OAuth2AuthCode Provider to avoid showing pin request again in the HttpSession::oauth2Authenticate
86 + libcmis::SessionFactory::setOAuth2AuthCodeProvider( NULL );
87 + return string( );
88 + }
90 + loginChallengeLink = "https://accounts.google.com" + loginChallengeLink;
91 + loginChallengePost += "Pin=";
92 + loginChallengePost += string( pin );
94 + istringstream loginChallengeIs( loginChallengePost );
95 + string loginChallengeRes;
96 + try
97 + {
98 + // send a post with pin, receive the application consent page
99 + loginChallengeRes = session->httpPostRequest ( loginChallengeLink, loginChallengeIs, CONTENT_TYPE )
100 + ->getStream( )->str( );
102 + catch ( const CurlException& e )
104 + return string( );
107 + approvalPost = string();
108 + approvalLink = string();
110 + if ( !parseResponse( loginChallengeRes. c_str( ), approvalPost, approvalLink) )
111 + return string( );
113 + else if( approvalLink.compare( "https://accounts.google.com/ServiceLoginAuth" ) == 0 )
115 + // wrong password,
116 + // unset OAuth2AuthCode Provider to avoid showing pin request again in the HttpSession::oauth2Authenticate
117 + libcmis::SessionFactory::setOAuth2AuthCodeProvider( NULL );
118 + return string( );
121 + // STEP 4: allow libcmis to access google drive
122 approvalPost += "submit_access=true";
124 istringstream approvalIs( approvalPost );
125 @@ -125,7 +186,7 @@ string OAuth2Providers::OAuth2Gdrive( HttpSession* session, const string& authUr
126 throw e.getCmisException( );
129 - // STEP 3: Take the authentication code from the text bar
130 + // Take the authentication code from the text bar
131 string code = parseCode( approvalRes.c_str( ) );
133 return code;
134 @@ -216,6 +277,9 @@ int OAuth2Providers::parseResponse ( const char* response, string& post, string&
135 if ( doc == NULL ) return 0;
136 xmlTextReaderPtr reader = xmlReaderWalker( doc );
137 if ( reader == NULL ) return 0;
139 + bool readInputField = false;
141 while ( true )
143 // Go to the next node, quit if not found
144 @@ -227,15 +291,30 @@ int OAuth2Providers::parseResponse ( const char* response, string& post, string&
146 xmlChar* action = xmlTextReaderGetAttribute( reader,
147 BAD_CAST( "action" ));
149 + // GDrive pin code page contains many forms.
150 + // We have to parse only the form with pin field.
151 if ( action != NULL )
153 - if ( xmlStrlen(action) > 0)
154 + bool bChallengePage = ( strncmp( (char*)action,
155 + CHALLENGE_PAGE_ACTION,
156 + CHALLENGE_PAGE_ACTION_LEN ) == 0 );
157 + bool bIsRightForm = ( strncmp( (char*)action,
158 + PIN_FORM_ACTION,
159 + PIN_FORM_ACTION_LEN ) == 0 );
160 + if ( ( xmlStrlen( action ) > 0 )
161 + && ( ( bChallengePage && bIsRightForm ) || !bChallengePage ) )
163 link = string ( (char*) action);
164 + readInputField = true;
166 + else
167 + readInputField = false;
168 xmlFree (action);
171 // Find input values
172 - if ( !xmlStrcmp( nodeName, BAD_CAST( "input" ) ) )
173 + if ( readInputField && !xmlStrcmp( nodeName, BAD_CAST( "input" ) ) )
175 xmlChar* name = xmlTextReaderGetAttribute( reader,
176 BAD_CAST( "name" ));