summaryrefslogtreecommitdiffstats
path: root/external/libcmis/libcmis-google-2FA-implementation.patch
blob: efa7f031019176983716ca25d30177bc5f25b7e1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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" ));