1# Copyright 2019 Google LLC
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# https://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"""Get user credentials from interactive code environments.
16
17This module contains helpers for getting user credentials from interactive
18code environments installed on a development machine, such as Jupyter
19notebooks.
20"""
21
22from __future__ import absolute_import
23
24import contextlib
25import socket
26
27import google_auth_oauthlib.flow
28
29
30LOCALHOST = "localhost"
31DEFAULT_PORTS_TO_TRY = 100
32
33
34def is_port_open(port):
35 """Check if a port is open on localhost.
36 Based on StackOverflow answer: https://stackoverflow.com/a/43238489/101923
37 Parameters
38 ----------
39 port : int
40 A port to check on localhost.
41 Returns
42 -------
43 is_open : bool
44 True if a socket can be opened at the requested port.
45 """
46 with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
47 try:
48 sock.bind((LOCALHOST, port))
49 sock.listen(1)
50 except socket.error:
51 is_open = False
52 else:
53 is_open = True
54 return is_open
55
56
57def find_open_port(start=8080, stop=None):
58 """Find an open port between ``start`` and ``stop``.
59 Parameters
60 ----------
61 start : Optional[int]
62 Beginning of range of ports to try. Defaults to 8080.
63 stop : Optional[int]
64 End of range of ports to try (not including exactly equals ``stop``).
65 This function tries 100 possible ports if no ``stop`` is specified.
66 Returns
67 -------
68 Optional[int]
69 ``None`` if no open port is found, otherwise an integer indicating an
70 open port.
71 """
72 if not stop:
73 stop = start + DEFAULT_PORTS_TO_TRY
74
75 for port in range(start, stop):
76 if is_port_open(port):
77 return port
78
79 # No open ports found.
80 return None
81
82
83def get_user_credentials(
84 scopes, client_id, client_secret, minimum_port=8080, maximum_port=None
85):
86 """Gets credentials associated with your Google user account.
87
88 This function authenticates using your user credentials by going through
89 the OAuth 2.0 flow. You'll open a browser window to authenticate to your
90 Google account. The permissions it requests correspond to the scopes
91 you've provided.
92
93 To obtain the ``client_id`` and ``client_secret``, create an **OAuth
94 client ID** with application type **Other** from the `Credentials page on
95 the Google Developer's Console
96 <https://console.developers.google.com/apis/credentials>`_. Learn more
97 with the `Authenticating as an end user
98 <https://cloud.google.com/docs/authentication/end-user>`_ guide.
99
100 Args:
101 scopes (Sequence[str]):
102 A list of scopes to use when authenticating to Google APIs. See
103 the `list of OAuth 2.0 scopes for Google APIs
104 <https://developers.google.com/identity/protocols/googlescopes>`_.
105 client_id (str):
106 A string that identifies your application to Google APIs. Find
107 this value in the `Credentials page on the Google Developer's
108 Console
109 <https://console.developers.google.com/apis/credentials>`_.
110 client_secret (str):
111 A string that verifies your application to Google APIs. Find this
112 value in the `Credentials page on the Google Developer's Console
113 <https://console.developers.google.com/apis/credentials>`_.
114 minimum_port (int):
115 Beginning of range of ports to try for redirect URI HTTP server.
116 Defaults to 8080.
117 maximum_port (Optional[int]):
118 End of range of ports to try (not including exactly equals ``stop``).
119 This function tries 100 possible ports if no ``stop`` is specified.
120
121 Returns:
122 google.oauth2.credentials.Credentials:
123 The OAuth 2.0 credentials for the user.
124
125 Examples:
126 Get credentials for your user account and use them to run a query
127 with BigQuery::
128
129 import google_auth_oauthlib
130
131 # TODO: Create a client ID for your project.
132 client_id = "YOUR-CLIENT-ID.apps.googleusercontent.com"
133 client_secret = "abc_ThIsIsAsEcReT"
134
135 # TODO: Choose the needed scopes for your applications.
136 scopes = ["https://www.googleapis.com/auth/cloud-platform"]
137
138 credentials = google_auth_oauthlib.get_user_credentials(
139 scopes, client_id, client_secret
140 )
141
142 # 1. Open the link.
143 # 2. Authorize the application to have access to your account.
144 # 3. Copy and paste the authorization code to the prompt.
145
146 # Use the credentials to construct a client for Google APIs.
147 from google.cloud import bigquery
148
149 bigquery_client = bigquery.Client(
150 credentials=credentials, project="your-project-id"
151 )
152 print(list(bigquery_client.query("SELECT 1").result()))
153 """
154
155 client_config = {
156 "installed": {
157 "client_id": client_id,
158 "client_secret": client_secret,
159 "auth_uri": "https://accounts.google.com/o/oauth2/auth",
160 "token_uri": "https://oauth2.googleapis.com/token",
161 }
162 }
163
164 app_flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_config(
165 client_config, scopes=scopes
166 )
167
168 port = find_open_port(start=minimum_port, stop=maximum_port)
169 if not port:
170 raise ConnectionError("Could not find open port.")
171
172 return app_flow.run_local_server(host=LOCALHOST, port=port)