2
2
import logging
3
3
import os
4
4
import urllib .parse as urlparse
5
+ from pathlib import Path
5
6
6
7
import aiohttp
7
8
import click
21
22
REFRESH_URL = "https://www.googleapis.com/oauth2/v4/token"
22
23
23
24
try :
24
- from requests_oauthlib import OAuth2Session
25
+ from aiohttp_oauthlib import OAuth2Session
25
26
26
27
have_oauth2 = True
27
28
except ImportError :
@@ -37,6 +38,9 @@ def __init__(
37
38
url = None ,
38
39
connector : aiohttp .BaseConnector = None ,
39
40
):
41
+ if not have_oauth2 :
42
+ raise exceptions .UserError ("aiohttp-oauthlib not installed" )
43
+
40
44
# Required for discovering collections
41
45
if url is not None :
42
46
self .url = url
@@ -45,68 +49,84 @@ def __init__(
45
49
self ._settings = {}
46
50
self .connector = connector
47
51
48
- if not have_oauth2 :
49
- raise exceptions .UserError ("requests-oauthlib not installed" )
52
+ self ._token_file = Path (expand_path (token_file ))
53
+ self ._client_id = client_id
54
+ self ._client_secret = client_secret
55
+ self ._token = None
50
56
51
- token_file = expand_path (token_file )
52
- return self ._init_token (token_file , client_id , client_secret )
57
+ async def request (self , method , path , ** kwargs ):
58
+ if not self ._token :
59
+ await self ._init_token ()
53
60
54
- def _init_token (self , token_file , client_id , client_secret ):
55
- token = None
56
- try :
57
- with open (token_file ) as f :
58
- token = json .load (f )
59
- except OSError :
60
- pass
61
- except ValueError as e :
62
- raise exceptions .UserError (
63
- "Failed to load token file {}, try deleting it. "
64
- "Original error: {}" .format (token_file , e )
65
- )
61
+ return await super ().request (method , path , ** kwargs )
66
62
67
- def _save_token (token ):
68
- checkdir (expand_path (os .path .dirname (token_file )), create = True )
69
- with atomic_write (token_file , mode = "w" , overwrite = True ) as f :
70
- json .dump (token , f )
63
+ def _save_token (self , token ):
64
+ """Helper function called by OAuth2Session when a token is updated."""
65
+ checkdir (expand_path (os .path .dirname (self ._token_file )), create = True )
66
+ with atomic_write (self ._token_file , mode = "w" , overwrite = True ) as f :
67
+ json .dump (token , f )
71
68
72
- self ._session = OAuth2Session (
73
- client_id = client_id ,
74
- token = token ,
69
+ @property
70
+ def _session (self ):
71
+ """Return a new OAuth session for requests."""
72
+
73
+ return OAuth2Session (
74
+ client_id = self ._client_id ,
75
+ token = self ._token ,
75
76
redirect_uri = "urn:ietf:wg:oauth:2.0:oob" ,
76
77
scope = self .scope ,
77
78
auto_refresh_url = REFRESH_URL ,
78
79
auto_refresh_kwargs = {
79
- "client_id" : client_id ,
80
- "client_secret" : client_secret ,
80
+ "client_id" : self . _client_id ,
81
+ "client_secret" : self . _client_secret ,
81
82
},
82
- token_updater = _save_token ,
83
+ token_updater = lambda token : self ._save_token (token ),
84
+ connector = self .connector ,
85
+ connector_owner = False ,
83
86
)
84
87
85
- if not token :
86
- authorization_url , state = self ._session .authorization_url (
87
- TOKEN_URL ,
88
- # access_type and approval_prompt are Google specific
89
- # extra parameters.
90
- access_type = "offline" ,
91
- approval_prompt = "force" ,
92
- )
93
- click .echo (f"Opening { authorization_url } ..." )
94
- try :
95
- open_graphical_browser (authorization_url )
96
- except Exception as e :
97
- logger .warning (str (e ))
98
-
99
- click .echo ("Follow the instructions on the page." )
100
- code = click .prompt ("Paste obtained code" )
101
- token = self ._session .fetch_token (
102
- REFRESH_URL ,
103
- code = code ,
104
- # Google specific extra parameter used for client
105
- # authentication
106
- client_secret = client_secret ,
88
+ async def _init_token (self ):
89
+ try :
90
+ with self ._token_file .open () as f :
91
+ self ._token = json .load (f )
92
+ except FileNotFoundError :
93
+ pass
94
+ except ValueError as e :
95
+ raise exceptions .UserError (
96
+ "Failed to load token file {}, try deleting it. "
97
+ "Original error: {}" .format (self ._token_file , e )
107
98
)
99
+
100
+ if not self ._token :
101
+ # Some times a task stops at this `async`, and another continues the flow.
102
+ # At this point, the user has already completed the flow, but is prompeted
103
+ # for a second one.
104
+ async with self ._session as session :
105
+ authorization_url , state = session .authorization_url (
106
+ TOKEN_URL ,
107
+ # access_type and approval_prompt are Google specific
108
+ # extra parameters.
109
+ access_type = "offline" ,
110
+ approval_prompt = "force" ,
111
+ )
112
+ click .echo (f"Opening { authorization_url } ..." )
113
+ try :
114
+ open_graphical_browser (authorization_url )
115
+ except Exception as e :
116
+ logger .warning (str (e ))
117
+
118
+ click .echo ("Follow the instructions on the page." )
119
+ code = click .prompt ("Paste obtained code" )
120
+
121
+ self ._token = await session .fetch_token (
122
+ REFRESH_URL ,
123
+ code = code ,
124
+ # Google specific extra param used for client authentication:
125
+ client_secret = self ._client_secret ,
126
+ )
127
+
108
128
# FIXME: Ugly
109
- _save_token (token )
129
+ self . _save_token (self . _token )
110
130
111
131
112
132
class GoogleCalendarStorage (dav .CalDAVStorage ):
0 commit comments