1""" 2gspread.auth 3~~~~~~~~~~~~ 4 5Simple authentication with OAuth. 6 7""" 8 9import json 10import os 11 12from google.oauth2.credentials import Credentials 13from google.oauth2.service_account import Credentials as ServiceAccountCredentials 14from google_auth_oauthlib.flow import InstalledAppFlow 15 16from .client import Client 17 18DEFAULT_SCOPES = [ 19 "https://www.googleapis.com/auth/spreadsheets", 20 "https://www.googleapis.com/auth/drive", 21] 22 23READONLY_SCOPES = [ 24 "https://www.googleapis.com/auth/spreadsheets.readonly", 25 "https://www.googleapis.com/auth/drive.readonly", 26] 27 28 29def get_config_dir(config_dir_name="gspread", os_is_windows=os.name == "nt"): 30 r"""Construct a config dir path. 31 32 By default: 33 * `%APPDATA%\gspread` on Windows 34 * `~/.config/gspread` everywhere else 35 36 """ 37 if os_is_windows: 38 return os.path.join(os.environ["APPDATA"], config_dir_name) 39 else: 40 return os.path.join(os.path.expanduser("~"), ".config", config_dir_name) 41 42 43DEFAULT_CONFIG_DIR = get_config_dir() 44 45DEFAULT_CREDENTIALS_FILENAME = os.path.join(DEFAULT_CONFIG_DIR, "credentials.json") 46DEFAULT_AUTHORIZED_USER_FILENAME = os.path.join( 47 DEFAULT_CONFIG_DIR, "authorized_user.json" 48) 49DEFAULT_SERVICE_ACCOUNT_FILENAME = os.path.join( 50 DEFAULT_CONFIG_DIR, "service_account.json" 51) 52 53 54def local_server_flow(client_config, scopes, port=0): 55 """Run an OAuth flow using a local server strategy. 56 57 Creates an OAuth flow and runs `google_auth_oauthlib.flow.InstalledAppFlow.run_local_server <https://google-auth-oauthlib.readthedocs.io/en/latest/reference/google_auth_oauthlib.flow.html#google_auth_oauthlib.flow.InstalledAppFlow.run_local_server>`_. 58 This will start a local web server and open the authorization URL in 59 the user’s browser. 60 61 Pass this function to ``flow`` parameter of :meth:`~gspread.oauth` to run 62 a local server flow. 63 """ 64 flow = InstalledAppFlow.from_client_config(client_config, scopes) 65 return flow.run_local_server(port=port) 66 67 68def console_flow(client_config, scopes): 69 """Run an OAuth flow using a console strategy. 70 71 Creates an OAuth flow and runs `google_auth_oauthlib.flow.InstalledAppFlow.run_console <https://google-auth-oauthlib.readthedocs.io/en/latest/reference/google_auth_oauthlib.flow.html#google_auth_oauthlib.flow.InstalledAppFlow.run_console>`_. 72 73 Pass this function to ``flow`` parameter of :meth:`~gspread.oauth` to run 74 a console strategy. 75 """ 76 flow = InstalledAppFlow.from_client_config(client_config, scopes) 77 return flow.run_console() 78 79 80def load_credentials(filename=DEFAULT_AUTHORIZED_USER_FILENAME): 81 if os.path.exists(filename): 82 return Credentials.from_authorized_user_file(filename) 83 84 return None 85 86 87def store_credentials(creds, filename=DEFAULT_AUTHORIZED_USER_FILENAME, strip="token"): 88 os.makedirs(os.path.dirname(filename), exist_ok=True) 89 with open(filename, "w") as f: 90 f.write(creds.to_json(strip)) 91 92 93def oauth( 94 scopes=DEFAULT_SCOPES, 95 flow=local_server_flow, 96 credentials_filename=DEFAULT_CREDENTIALS_FILENAME, 97 authorized_user_filename=DEFAULT_AUTHORIZED_USER_FILENAME, 98): 99 r"""Authenticate with OAuth Client ID. 100 101 By default this function will use the local server strategy and open 102 the authorization URL in the user’s browser:: 103 104 gc = gspread.oauth() 105 106 Another option is to run a console strategy. This way, the user is 107 instructed to open the authorization URL in their browser. Once the 108 authorization is complete, the user must then copy & paste the 109 authorization code into the application:: 110 111 gc = gspread.oauth(flow=gspread.auth.console_flow) 112 113 114 ``scopes`` parameter defaults to read/write scope available in 115 ``gspread.auth.DEFAULT_SCOPES``. It's read/write for Sheets 116 and Drive API:: 117 118 DEFAULT_SCOPES =[ 119 'https://www.googleapis.com/auth/spreadsheets', 120 'https://www.googleapis.com/auth/drive' 121 ] 122 123 You can also use ``gspread.auth.READONLY_SCOPES`` for read only access. 124 Obviously any method of ``gspread`` that updates a spreadsheet 125 **will not work** in this case:: 126 127 gc = gspread.oauth(scopes=gspread.auth.READONLY_SCOPES) 128 129 sh = gc.open("A spreadsheet") 130 sh.sheet1.update('A1', '42') # <-- this will not work 131 132 If you're storing your user credentials in a place other than the 133 default, you may provide a path to that file like so:: 134 135 gc = gspread.oauth( 136 credentials_filename='/alternative/path/credentials.json', 137 authorized_user_filename='/alternative/path/authorized_user.json', 138 ) 139 140 :param list scopes: The scopes used to obtain authorization. 141 :param function flow: OAuth flow to use for authentication. 142 Defaults to :meth:`~gspread.auth.local_server_flow` 143 :param str credentials_filename: Filepath (including name) pointing to a 144 credentials `.json` file. 145 Defaults to DEFAULT_CREDENTIALS_FILENAME: 146 * `%APPDATA%\gspread\credentials.json` on Windows 147 * `~/.config/gspread/credentials.json` everywhere else 148 :param str authorized_user_filename: Filepath (including name) pointing to 149 an authorized user `.json` file. 150 Defaults to DEFAULT_AUTHORIZED_USER_FILENAME: 151 * `%APPDATA%\gspread\authorized_user.json` on Windows 152 * `~/.config/gspread/authorized_user.json` everywhere else 153 154 :rtype: :class:`gspread.Client` 155 """ 156 creds = load_credentials(filename=authorized_user_filename) 157 158 if not creds: 159 with open(credentials_filename) as json_file: 160 client_config = json.load(json_file) 161 creds = flow(client_config=client_config, scopes=scopes) 162 store_credentials(creds, filename=authorized_user_filename) 163 164 client = Client(auth=creds) 165 return client 166 167 168def oauth_from_dict( 169 credentials=None, 170 authorized_user_info=None, 171 scopes=DEFAULT_SCOPES, 172 flow=local_server_flow, 173): 174 r"""Authenticate with OAuth Client ID. 175 176 By default this function will use the local server strategy and open 177 the authorization URL in the user's browser:: 178 179 gc = gspread.oauth() 180 181 Another option is to run a console strategy. This way, the user is 182 instructed to open the authorization URL in their browser. Once the 183 authorization is complete, the user must then copy & paste the 184 authorization code into the application:: 185 186 gc = gspread.oauth(flow=gspread.auth.console_flow) 187 188 189 ``scopes`` parameter defaults to read/write scope available in 190 ``gspread.auth.DEFAULT_SCOPES``. It's read/write for Sheets 191 and Drive API:: 192 193 DEFAULT_SCOPES =[ 194 'https://www.googleapis.com/auth/spreadsheets', 195 'https://www.googleapis.com/auth/drive' 196 ] 197 198 You can also use ``gspread.auth.READONLY_SCOPES`` for read only access. 199 Obviously any method of ``gspread`` that updates a spreadsheet 200 **will not work** in this case:: 201 202 gc = gspread.oauth(scopes=gspread.auth.READONLY_SCOPES) 203 204 sh = gc.open("A spreadsheet") 205 sh.sheet1.update('A1', '42') # <-- this will not work 206 207 This function requires you to pass the credentials directly as 208 a python dict. After the first authentication the function returns 209 the authenticated user info, this can be passed again to authenticate 210 the user without the need to run the flow again. 211 212 gc = gspread.oauth( 213 credentials=my_creds, 214 authorized_user_info=my_auth_user, 215 ) 216 217 :param dict credentials: The credentials from google cloud platform 218 :param dict authorized_user_info: The authenticated user 219 if already authenticated. 220 :param list scopes: The scopes used to obtain authorization. 221 :param function flow: OAuth flow to use for authentication. 222 Defaults to :meth:`~gspread.auth.local_server_flow` 223 224 :rtype: :class:`gspread.Client` 225 """ 226 227 creds = None 228 if authorized_user_info is not None: 229 creds = Credentials.from_authorized_user_info(authorized_user_info, scopes) 230 231 if not creds: 232 creds = flow(client_config=credentials, scopes=scopes) 233 234 client = Client(auth=creds) 235 236 # must return the creds to the user 237 # must strip the token an use the dedicated method from Credentials 238 # to return a dict "safe to store". 239 return (client, creds.to_json("token")) 240 241 242def service_account(filename=DEFAULT_SERVICE_ACCOUNT_FILENAME, scopes=DEFAULT_SCOPES): 243 """Authenticate using a service account. 244 245 ``scopes`` parameter defaults to read/write scope available in 246 ``gspread.auth.DEFAULT_SCOPES``. It's read/write for Sheets 247 and Drive API:: 248 249 DEFAULT_SCOPES =[ 250 'https://www.googleapis.com/auth/spreadsheets', 251 'https://www.googleapis.com/auth/drive' 252 ] 253 254 You can also use ``gspread.auth.READONLY_SCOPES`` for read only access. 255 Obviously any method of ``gspread`` that updates a spreadsheet 256 **will not work** in this case. 257 258 :param str filename: The path to the service account json file. 259 :param list scopes: The scopes used to obtain authorization. 260 261 :rtype: :class:`gspread.Client` 262 """ 263 creds = ServiceAccountCredentials.from_service_account_file(filename, scopes=scopes) 264 return Client(auth=creds) 265 266 267def service_account_from_dict(info, scopes=DEFAULT_SCOPES): 268 """Authenticate using a service account (json). 269 270 ``scopes`` parameter defaults to read/write scope available in 271 ``gspread.auth.DEFAULT_SCOPES``. It's read/write for Sheets 272 and Drive API:: 273 274 DEFAULT_SCOPES =[ 275 'https://www.googleapis.com/auth/spreadsheets', 276 'https://www.googleapis.com/auth/drive' 277 ] 278 279 You can also use ``gspread.auth.READONLY_SCOPES`` for read only access. 280 Obviously any method of ``gspread`` that updates a spreadsheet 281 **will not work** in this case. 282 283 :param info (Mapping[str, str]): The service account info in Google format 284 :param list scopes: The scopes used to obtain authorization. 285 286 :rtype: :class:`gspread.Client` 287 """ 288 creds = ServiceAccountCredentials.from_service_account_info( 289 info=info, 290 scopes=scopes, 291 ) 292 return Client(auth=creds) 293