@@ -60,6 +60,41 @@ def find_service(id):
6060    return  service 
6161
6262
63+ def  create_authtoken (provider_id , token ):
64+     # We store the ID if we get it back 
65+     if  token .has_key ("user_id" ):
66+         user_id  =  token ["user_id" ]
67+     else :
68+         user_id  =  "N/A" 
69+ 
70+     exp_secs  =  1800   # 30 min guess 
71+     try :
72+         exp_secs  =  int (token ["expires_in" ])
73+     except :
74+         pass 
75+ 
76+     # Create a random password and encrypt the response 
77+     # This ensures that a hostile takeover will not get access 
78+     #  to stored access and refresh tokens 
79+     password  =  password_generator .generate_pass ()
80+     cipher  =  simplecrypt .encrypt (password , json .dumps (token ))
81+ 
82+     # Convert to text and prepare for storage 
83+     b64_cipher  =  base64 .b64encode (cipher )
84+     expires  =  datetime .datetime .utcnow () +  datetime .timedelta (seconds = exp_secs )
85+ 
86+     entry  =  None 
87+     keyid  =  None 
88+ 
89+     # Find a random un-used user ID, and store the encrypted data 
90+     while  entry  is  None :
91+         keyid  =  '%030x'  %  random .randrange (16  **  32 )
92+         entry  =  dbmodel .insert_new_authtoken (keyid , user_id , b64_cipher , expires , provider_id )
93+ 
94+     # Return the keyid and authid 
95+     return  keyid , keyid  +  ':'  +  password 
96+ 
97+ 
6398class  RedirectToLoginHandler (webapp2 .RequestHandler ):
6499    """Creates a state and redirects the user to the login page""" 
65100
@@ -129,12 +164,16 @@ def get(self):
129164            if  filtertype  is  None  and  n .has_key ('hidden' ) and  n ['hidden' ]:
130165                continue 
131166
132-             link  =  '/login?id='  +  n ['id' ]
133-             if  self .request .get ('token' , None ) is  not   None :
134-                 link  +=  '&token='  +  self .request .get ('token' )
167+             link  =  '' 
168+             if  service .has_key ('cli-token' ) and  service ['cli-token' ]:
169+                 link  =  '/cli-token?id='  +  n ['id' ]
170+             else :
171+                 link  =  '/login?id='  +  n ['id' ]
172+                 if  self .request .get ('token' , None ) is  not   None :
173+                     link  +=  '&token='  +  self .request .get ('token' )
135174
136-             if  tokenversion  is  not   None :
137-                 link  +=  '&tokenversion='  +  str (tokenversion )
175+                  if  tokenversion  is  not   None :
176+                      link  +=  '&tokenversion='  +  str (tokenversion )
138177
139178            notes  =  '' 
140179            if  n .has_key ('notes' ):
@@ -309,39 +348,91 @@ def get(self, service=None):
309348                logging .info ('Returned refresh token for service %s' , provider ['id' ])
310349                return 
311350
312-             # We store the ID if we get it back 
313-             if  resp .has_key ("user_id" ):
314-                 user_id  =  resp ["user_id" ]
315-             else :
316-                 user_id  =  "N/A" 
351+             # Return the id and password to the user 
352+             keyid , authid  =  create_authtoken (provider ['id' ], resp )
317353
318-             exp_secs  =  1800   # 30 min guess 
319-             try :
320-                 exp_secs  =  int (resp ["expires_in" ])
321-             except :
322-                 pass 
354+             fetchtoken  =  statetoken .fetchtoken 
323355
324-             # Create a random password and encrypt the response 
325-             # This ensures that a hostile takeover will not get access 
326-             #  to stored access and refresh tokens 
327-             password  =  password_generator .generate_pass ()
328-             cipher  =  simplecrypt .encrypt (password , json .dumps (resp ))
356+             # If this was part of a polling request, signal completion 
357+             dbmodel .update_fetch_token (fetchtoken , authid )
329358
330-             # Convert to text and prepare for storage 
331-             b64_cipher  =  base64 .b64encode (cipher )
332-             expires  =  datetime .datetime .utcnow () +  datetime .timedelta (seconds = exp_secs )
333-             fetchtoken  =  statetoken .fetchtoken 
359+             # Report results to the user 
360+             template_values  =  {
361+                 'service' : display ,
362+                 'appname' : settings .APP_NAME ,
363+                 'longappname' : settings .SERVICE_DISPLAYNAME ,
364+                 'authid' : authid ,
365+                 'fetchtoken' : fetchtoken 
366+             }
334367
335-             entry  =  None 
336-             keyid  =  None 
368+             template  =  JINJA_ENVIRONMENT .get_template ('logged-in.html' )
369+             self .response .write (template .render (template_values ))
370+             statetoken .delete ()
337371
338-             # Find a random un-used user ID, and store the encrypted data 
339-             while  entry  is  None :
340-                 keyid  =  '%030x'  %  random .randrange (16  **  32 )
341-                 entry  =  dbmodel .insert_new_authtoken (keyid , user_id , b64_cipher , expires , provider ['id' ])
372+             logging .info ('Created new authid %s for service %s' , keyid , provider ['id' ])
342373
343-             # Return the id and password to the user 
344-             authid  =  keyid  +  ':'  +  password 
374+         except :
375+             logging .exception ('handler error for '  +  display )
376+ 
377+             template_values  =  {
378+                 'service' : display ,
379+                 'appname' : settings .APP_NAME ,
380+                 'longappname' : settings .SERVICE_DISPLAYNAME ,
381+                 'authid' : 'Server error, close window and try again' ,
382+                 'fetchtoken' : '' 
383+             }
384+ 
385+             template  =  JINJA_ENVIRONMENT .get_template ('logged-in.html' )
386+             self .response .write (template .render (template_values ))
387+ 
388+ class  CliTokenHandler (webapp2 .RequestHandler ):
389+     """Renders the cli-token.html page""" 
390+ 
391+     def  get (self ):
392+ 
393+         provider , service  =  find_provider_and_service (self .request .get ('id' , None ))
394+ 
395+         template_values  =  {
396+             'appname' : settings .SERVICE_DISPLAYNAME ,
397+             'service' : provider ['display' ],
398+             'id' : provider ['id' ]
399+         }
400+ 
401+         template  =  JINJA_ENVIRONMENT .get_template ('cli-token.html' )
402+         self .response .write (template .render (template_values ))
403+ 
404+ 
405+ class  CliTokenLoginHandler (webapp2 .RequestHandler ):
406+     """Handler that processes cli-token login and redirects the user to the logged-in page""" 
407+ 
408+     def  post (self ):
409+         display  =  'Unknown' 
410+         try :
411+             id  =  self .request .POST .get ('id' )
412+             provider , service  =  find_provider_and_service (id )
413+             display  =  provider ['display' ]
414+ 
415+             data  =  self .request .POST .get ('token' )
416+             content  =  base64 .b64decode (data ).decode ('utf8' )
417+             resp  =  json .loads (content )
418+ 
419+             urlfetch .set_default_fetch_deadline (20 )
420+             url  =  service ['auth-url' ]
421+             data  =  urllib .urlencode ({'client_id' : service ['client-id' ],
422+                                         'grant_type' : 'password' ,
423+                                         'scope' : provider ['scope' ],
424+                                         'username' : resp ['username' ],
425+                                         'password' : resp ['auth_token' ]
426+                                         })
427+             req  =  urllib2 .Request (url , data , {'Content-Type' : 'application/x-www-form-urlencoded' })
428+             f  =  urllib2 .urlopen (req )
429+             content  =  f .read ()
430+             resp  =  json .loads (content )
431+             f .close ()
432+ 
433+             keyid , authid  =  create_authtoken (id , resp )
434+ 
435+             fetchtoken  =  dbmodel .create_fetch_token (resp )
345436
346437            # If this was part of a polling request, signal completion 
347438            dbmodel .update_fetch_token (fetchtoken , authid )
@@ -357,7 +448,6 @@ def get(self, service=None):
357448
358449            template  =  JINJA_ENVIRONMENT .get_template ('logged-in.html' )
359450            self .response .write (template .render (template_values ))
360-             statetoken .delete ()
361451
362452            logging .info ('Created new authid %s for service %s' , keyid , provider ['id' ])
363453
@@ -559,11 +649,14 @@ def process(self, authid):
559649            url  =  service ['auth-url' ]
560650            request_params  =  {
561651                'client_id' : service ['client-id' ],
562-                 'redirect_uri' : service ['redirect-uri' ],
563-                 'client_secret' : service ['client-secret' ],
564652                'grant_type' : 'refresh_token' ,
565653                'refresh_token' : resp ['refresh_token' ]
566654            }
655+             if  service .has_key ("client_secret" ):
656+                 request_params ['client_secret' ] =  service ['client-secret' ]
657+             if  service .has_key ("redirect_uri" ):
658+                 request_params ['redirect_uri' ] =  service ['redirect-uri' ]
659+ 
567660            # Some services do not allow the state to be passed 
568661            if  service .has_key ('no-redirect_uri-for-refresh-request' ) and  service ['no-redirect_uri-for-refresh-request' ]:
569662                del  request_params ['redirect_uri' ]
@@ -673,12 +766,17 @@ def handle_v2(self, inputfragment):
673766                    logging .info ('Cached response to: %s is invalid because it expires in %s' , tokenhash , exp_secs )
674767
675768            url  =  service ['auth-url' ]
676-             data  =  urllib .urlencode ({'client_id' : service ['client-id' ],
677-                                      'redirect_uri' : service ['redirect-uri' ],
678-                                      'client_secret' : service ['client-secret' ],
679-                                      'grant_type' : 'refresh_token' ,
680-                                      'refresh_token' : refresh_token 
681-                                      })
769+             request_params  =  {
770+                 'client_id' : service ['client-id' ],
771+                 'grant_type' : 'refresh_token' ,
772+                 'refresh_token' : refresh_token 
773+             }
774+             if  service .has_key ("client_secret" ):
775+                 request_params ['client_secret' ] =  service ['client-secret' ]
776+             if  service .has_key ("redirect_uri" ):
777+                 request_params ['redirect_uri' ] =  service ['redirect-uri' ]
778+ 
779+             data  =  urllib .urlencode (request_params )
682780
683781            urlfetch .set_default_fetch_deadline (20 )
684782
@@ -983,6 +1081,8 @@ def get(self):
9831081app  =  webapp2 .WSGIApplication ([
9841082    ('/logged-in' , LoginHandler ),
9851083    ('/login' , RedirectToLoginHandler ),
1084+     ('/cli-token' , CliTokenHandler ),
1085+     ('/cli-token-login' , CliTokenLoginHandler ),
9861086    ('/refresh' , RefreshHandler ),
9871087    ('/fetch' , FetchHandler ),
9881088    ('/token-state' , TokenStateHandler ),
0 commit comments