1# Copyright 2014 Google Inc. All Rights Reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Helper routines to facilitate use of oauth2_client.""" 16 17from __future__ import absolute_import 18 19import json 20import os 21import sys 22import time 23import webbrowser 24 25from oauth2client.client import OAuth2WebServerFlow 26 27from gcs_oauth2_boto_plugin import oauth2_client 28 29CLIENT_ID = None 30CLIENT_SECRET = None 31 32GOOGLE_OAUTH2_PROVIDER_AUTHORIZATION_URI = ( 33 'https://accounts.google.com/o/oauth2/auth') 34GOOGLE_OAUTH2_PROVIDER_TOKEN_URI = ( 35 'https://accounts.google.com/o/oauth2/token') 36GOOGLE_OAUTH2_DEFAULT_FILE_PASSWORD = 'notasecret' 37 38OOB_REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob' 39 40 41def OAuth2ClientFromBotoConfig( 42 config, cred_type=oauth2_client.CredTypes.OAUTH2_USER_ACCOUNT): 43 token_cache = None 44 token_cache_type = config.get('OAuth2', 'token_cache', 'file_system') 45 if token_cache_type == 'file_system': 46 if config.has_option('OAuth2', 'token_cache_path_pattern'): 47 token_cache = oauth2_client.FileSystemTokenCache( 48 path_pattern=config.get('OAuth2', 'token_cache_path_pattern')) 49 else: 50 token_cache = oauth2_client.FileSystemTokenCache() 51 elif token_cache_type == 'in_memory': 52 token_cache = oauth2_client.InMemoryTokenCache() 53 else: 54 raise Exception( 55 "Invalid value for config option OAuth2/token_cache: %s" % 56 token_cache_type) 57 58 proxy_host = None 59 proxy_port = None 60 proxy_user = None 61 proxy_pass = None 62 if (config.has_option('Boto', 'proxy') 63 and config.has_option('Boto', 'proxy_port')): 64 proxy_host = config.get('Boto', 'proxy') 65 proxy_port = int(config.get('Boto', 'proxy_port')) 66 proxy_user = config.get('Boto', 'proxy_user', None) 67 proxy_pass = config.get('Boto', 'proxy_pass', None) 68 69 provider_authorization_uri = config.get( 70 'OAuth2', 'provider_authorization_uri', 71 GOOGLE_OAUTH2_PROVIDER_AUTHORIZATION_URI) 72 provider_token_uri = config.get( 73 'OAuth2', 'provider_token_uri', GOOGLE_OAUTH2_PROVIDER_TOKEN_URI) 74 75 if cred_type == oauth2_client.CredTypes.OAUTH2_SERVICE_ACCOUNT: 76 service_client_id = config.get('Credentials', 'gs_service_client_id', '') 77 private_key_filename = config.get('Credentials', 'gs_service_key_file', '') 78 with open(private_key_filename, 'rb') as private_key_file: 79 private_key = private_key_file.read() 80 81 json_key = None 82 try: 83 json_key = json.loads(private_key) 84 except ValueError: 85 pass 86 if json_key: 87 for json_entry in ('client_id', 'client_email', 'private_key_id', 88 'private_key'): 89 if json_entry not in json_key: 90 raise Exception('The JSON private key file at %s ' 91 'did not contain the required entry: %s' % 92 (private_key_filename, json_entry)) 93 94 return oauth2_client.OAuth2JsonServiceAccountClient( 95 json_key['client_id'], json_key['client_email'], 96 json_key['private_key_id'], json_key['private_key'], 97 access_token_cache=token_cache, auth_uri=provider_authorization_uri, 98 token_uri=provider_token_uri, 99 disable_ssl_certificate_validation=not(config.getbool( 100 'Boto', 'https_validate_certificates', True)), 101 proxy_host=proxy_host, proxy_port=proxy_port, 102 proxy_user=proxy_user, proxy_pass=proxy_pass) 103 else: 104 key_file_pass = config.get('Credentials', 'gs_service_key_file_password', 105 GOOGLE_OAUTH2_DEFAULT_FILE_PASSWORD) 106 107 return oauth2_client.OAuth2ServiceAccountClient( 108 service_client_id, private_key, key_file_pass, 109 access_token_cache=token_cache, auth_uri=provider_authorization_uri, 110 token_uri=provider_token_uri, 111 disable_ssl_certificate_validation=not(config.getbool( 112 'Boto', 'https_validate_certificates', True)), 113 proxy_host=proxy_host, proxy_port=proxy_port, 114 proxy_user=proxy_user, proxy_pass=proxy_pass) 115 116 elif cred_type == oauth2_client.CredTypes.OAUTH2_USER_ACCOUNT: 117 client_id = config.get('OAuth2', 'client_id', 118 os.environ.get('OAUTH2_CLIENT_ID', CLIENT_ID)) 119 if not client_id: 120 raise Exception( 121 'client_id for your application obtained from ' 122 'https://console.developers.google.com must be set in a boto config ' 123 'or with OAUTH2_CLIENT_ID environment variable or with ' 124 'gcs_oauth2_boto_plugin.SetFallbackClientIdAndSecret function.') 125 126 client_secret = config.get('OAuth2', 'client_secret', 127 os.environ.get('OAUTH2_CLIENT_SECRET', 128 CLIENT_SECRET)) 129 ca_certs_file=config.get_value('Boto', 'ca_certificates_file') 130 if ca_certs_file == 'system': 131 ca_certs_file = None 132 133 if not client_secret: 134 raise Exception( 135 'client_secret for your application obtained from ' 136 'https://console.developers.google.com must be set in a boto config ' 137 'or with OAUTH2_CLIENT_SECRET environment variable or with ' 138 'gcs_oauth2_boto_plugin.SetFallbackClientIdAndSecret function.') 139 return oauth2_client.OAuth2UserAccountClient( 140 provider_token_uri, client_id, client_secret, 141 config.get('Credentials', 'gs_oauth2_refresh_token'), 142 auth_uri=provider_authorization_uri, access_token_cache=token_cache, 143 disable_ssl_certificate_validation=not(config.getbool( 144 'Boto', 'https_validate_certificates', True)), 145 proxy_host=proxy_host, proxy_port=proxy_port, 146 proxy_user=proxy_user, proxy_pass=proxy_pass, 147 ca_certs_file=ca_certs_file) 148 else: 149 raise Exception('You have attempted to create an OAuth2 client without ' 150 'setting up OAuth2 credentials.') 151 152 153def OAuth2ApprovalFlow(client, scopes, launch_browser=False): 154 flow = OAuth2WebServerFlow( 155 client.client_id, client.client_secret, scopes, auth_uri=client.auth_uri, 156 token_uri=client.token_uri, redirect_uri=OOB_REDIRECT_URI) 157 approval_url = flow.step1_get_authorize_url() 158 159 if launch_browser: 160 sys.stdout.write( 161 'Attempting to launch a browser with the OAuth2 approval dialog at ' 162 'URL: %s\n\n' 163 '[Note: due to a Python bug, you may see a spurious error message ' 164 '"object is not\ncallable [...] in [...] Popen.__del__" which can be ' 165 'ignored.]\n\n' % approval_url) 166 else: 167 sys.stdout.write( 168 'Please navigate your browser to the following URL:\n%s\n' % 169 approval_url) 170 171 sys.stdout.write( 172 'In your browser you should see a page that requests you to authorize ' 173 'access to Google Cloud Platform APIs and Services on your behalf. ' 174 'After you approve, an authorization code will be displayed.\n\n') 175 if (launch_browser and 176 not webbrowser.open(approval_url, new=1, autoraise=True)): 177 sys.stdout.write( 178 'Launching browser appears to have failed; please navigate a browser ' 179 'to the following URL:\n%s\n' % approval_url) 180 # Short delay; webbrowser.open on linux insists on printing out a message 181 # which we don't want to run into the prompt for the auth code. 182 time.sleep(2) 183 code = raw_input('Enter the authorization code: ') 184 credentials = flow.step2_exchange(code, http=client.CreateHttpRequest()) 185 return credentials.refresh_token 186 187 188def SetFallbackClientIdAndSecret(client_id, client_secret): 189 global CLIENT_ID 190 global CLIENT_SECRET 191 192 CLIENT_ID = client_id 193 CLIENT_SECRET = client_secret 194 195 196def SetLock(lock): 197 oauth2_client.token_exchange_lock = lock 198 199