Skip to content

Commit f119c4e

Browse files
fix: avoid deprecated "out-of-band" authentication flow (#186)
* fix: avoid deprecated "out-of-band" authentication flow * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 82bb5dc commit f119c4e

File tree

2 files changed

+132
-3
lines changed

2 files changed

+132
-3
lines changed

google_auth_oauthlib/interactive.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,68 @@
2121

2222
from __future__ import absolute_import
2323

24+
import contextlib
25+
import socket
26+
2427
import google_auth_oauthlib.flow
2528

2629

27-
def get_user_credentials(scopes, client_id, client_secret):
30+
LOCALHOST = "localhost"
31+
DEFAULT_PORTS_TO_TRY = 100
32+
33+
34+
def 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+
57+
def 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+
83+
def get_user_credentials(
84+
scopes, client_id, client_secret, minimum_port=8080, maximum_port=None
85+
):
2886
"""Gets credentials associated with your Google user account.
2987
3088
This function authenticates using your user credentials by going through
@@ -53,6 +111,12 @@ def get_user_credentials(scopes, client_id, client_secret):
53111
A string that verifies your application to Google APIs. Find this
54112
value in the `Credentials page on the Google Developer's Console
55113
<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.
56120
57121
Returns:
58122
google.oauth2.credentials.Credentials:
@@ -102,4 +166,8 @@ def get_user_credentials(scopes, client_id, client_secret):
102166
client_config, scopes=scopes
103167
)
104168

105-
return app_flow.run_console()
169+
port = find_open_port()
170+
if not port:
171+
raise ConnectionError("Could not find open port.")
172+
173+
return app_flow.run_local_server(host=LOCALHOST, port=port)

tests/unit/test_interactive.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,47 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import socket
16+
1517
import mock
18+
import pytest
19+
20+
21+
def test_find_open_port_finds_start_port(monkeypatch):
22+
from google_auth_oauthlib import interactive as module_under_test
23+
24+
monkeypatch.setattr(socket, "socket", mock.create_autospec(socket.socket))
25+
port = module_under_test.find_open_port(9999)
26+
assert port == 9999
27+
28+
29+
def test_find_open_port_finds_stop_port(monkeypatch):
30+
from google_auth_oauthlib import interactive as module_under_test
31+
32+
socket_instance = mock.create_autospec(socket.socket, instance=True)
33+
34+
def mock_socket(family, type_):
35+
return socket_instance
36+
37+
monkeypatch.setattr(socket, "socket", mock_socket)
38+
socket_instance.listen.side_effect = [socket.error] * 99 + [None]
39+
port = module_under_test.find_open_port(9000, stop=9100)
40+
assert port == 9099
41+
42+
43+
def test_find_open_port_returns_none(monkeypatch):
44+
from google_auth_oauthlib import interactive as module_under_test
45+
46+
socket_instance = mock.create_autospec(socket.socket, instance=True)
47+
48+
def mock_socket(family, type_):
49+
return socket_instance
50+
51+
monkeypatch.setattr(socket, "socket", mock_socket)
52+
socket_instance.listen.side_effect = socket.error
53+
port = module_under_test.find_open_port(9000)
54+
assert port is None
55+
socket_instance.listen.assert_has_calls(mock.call(1) for _ in range(100))
1656

1757

1858
def test_get_user_credentials():
@@ -33,4 +73,25 @@ def test_get_user_credentials():
3373
actual_client_config = mock_flow.from_client_config.call_args[0][0]
3474
assert actual_client_config["installed"]["client_id"] == "some-client-id"
3575
assert actual_client_config["installed"]["client_secret"] == "shh-secret"
36-
mock_flow_instance.run_console.assert_called_once()
76+
mock_flow_instance.run_local_server.assert_called_once()
77+
78+
79+
def test_get_user_credentials_raises_connectionerror(monkeypatch):
80+
from google_auth_oauthlib import flow
81+
from google_auth_oauthlib import interactive as module_under_test
82+
83+
def mock_find_open_port():
84+
return None
85+
86+
monkeypatch.setattr(module_under_test, "find_open_port", mock_find_open_port)
87+
mock_flow = mock.create_autospec(flow.InstalledAppFlow, instance=True)
88+
89+
with mock.patch(
90+
"google_auth_oauthlib.flow.InstalledAppFlow", autospec=True
91+
) as mock_flow, pytest.raises(ConnectionError):
92+
mock_flow.from_client_config.return_value = mock_flow
93+
module_under_test.get_user_credentials(
94+
["scopes"], "some-client-id", "shh-secret"
95+
)
96+
97+
mock_flow.run_local_server.assert_not_called()

0 commit comments

Comments
 (0)