Import upstream version 1.0.0
Debian Janitor
1 year, 30 days ago
0 | Apache License | |
0 | ||
1 | Apache License | |
1 | 2 | Version 2.0, January 2004 |
2 | https://www.apache.org/licenses/ | |
3 | http://www.apache.org/licenses/ | |
3 | 4 | |
4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
5 | 6 | |
191 | 192 | you may not use this file except in compliance with the License. |
192 | 193 | You may obtain a copy of the License at |
193 | 194 | |
194 | https://www.apache.org/licenses/LICENSE-2.0 | |
195 | http://www.apache.org/licenses/LICENSE-2.0 | |
195 | 196 | |
196 | 197 | Unless required by applicable law or agreed to in writing, software |
197 | 198 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | 15 | |
16 | 16 | # Generated by synthtool. DO NOT EDIT! |
17 | 17 | include README.rst LICENSE |
18 | recursive-include google *.json *.proto | |
18 | recursive-include google *.json *.proto py.typed | |
19 | 19 | recursive-include tests * |
20 | 20 | global-exclude *.py[co] |
21 | 21 | global-exclude __pycache__ |
22 | 22 | |
23 | 23 | # Exclude scripts for samples readmegen |
24 | prune scripts/readme-gen⏎ | |
24 | prune scripts/readme-gen |
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: google-auth-oauthlib |
2 | Version: 0.4.2 | |
2 | Version: 1.0.0 | |
3 | 3 | Summary: Google Authentication Library |
4 | 4 | Home-page: https://github.com/GoogleCloudPlatform/google-auth-library-python-oauthlib |
5 | 5 | Author: Google Cloud Platform |
6 | 6 | Author-email: jonwayne+google-auth@google.com |
7 | 7 | License: Apache 2.0 |
8 | Description: oauthlib integration for Google Auth | |
9 | ==================================== | |
10 | ||
11 | |pypi| | |
12 | ||
13 | This library provides `oauthlib`_ integration with `google-auth`_. | |
14 | ||
15 | .. |build| image:: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib.svg?branch=master | |
16 | :target: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib | |
17 | .. |docs| image:: https://readthedocs.org/projects/google-auth-oauthlib/badge/?version=latest | |
18 | :target: https://google-auth-oauthlib.readthedocs.io/en/latest/ | |
19 | .. |pypi| image:: https://img.shields.io/pypi/v/google-auth-oauthlib.svg | |
20 | :target: https://pypi.python.org/pypi/google-auth-oauthlib | |
21 | ||
22 | .. _oauthlib: https://github.com/idan/oauthlib | |
23 | .. _google-auth: https://github.com/googleapis/google-auth-library-python | |
24 | ||
25 | Installing | |
26 | ---------- | |
27 | ||
28 | You can install using `pip`_:: | |
29 | ||
30 | $ pip install google-auth-oauthlib | |
31 | ||
32 | .. _pip: https://pip.pypa.io/en/stable/ | |
33 | ||
34 | Documentation | |
35 | ------------- | |
36 | ||
37 | The latest documentation is available at `google-auth-oauthlib.readthedocs.io`_. | |
38 | ||
39 | .. _google-auth-oauthlib.readthedocs.io: http://google-auth-oauthlib.readthedocs.io/ | |
40 | ||
41 | License | |
42 | ------- | |
43 | ||
44 | Apache 2.0 - See `the LICENSE`_ for more information. | |
45 | ||
46 | .. _the LICENSE: https://github.com/googleapis/google-auth-library-python-oauthlib/blob/master/LICENSE | |
47 | ||
48 | 8 | Keywords: google auth oauth client oauthlib |
49 | Platform: UNKNOWN | |
50 | 9 | Classifier: Programming Language :: Python :: 3 |
51 | 10 | Classifier: Programming Language :: Python :: 3.6 |
52 | 11 | Classifier: Programming Language :: Python :: 3.7 |
53 | 12 | Classifier: Programming Language :: Python :: 3.8 |
54 | 13 | Classifier: Programming Language :: Python :: 3.9 |
55 | Classifier: Development Status :: 3 - Alpha | |
14 | Classifier: Programming Language :: Python :: 3.10 | |
15 | Classifier: Programming Language :: Python :: 3.11 | |
16 | Classifier: Development Status :: 5 - Production/Stable | |
56 | 17 | Classifier: Intended Audience :: Developers |
57 | 18 | Classifier: License :: OSI Approved :: Apache Software License |
58 | 19 | Classifier: Operating System :: POSIX |
62 | 23 | Classifier: Topic :: Internet :: WWW/HTTP |
63 | 24 | Requires-Python: >=3.6 |
64 | 25 | Provides-Extra: tool |
26 | License-File: LICENSE | |
27 | ||
28 | oauthlib integration for Google Auth | |
29 | ==================================== | |
30 | ||
31 | |pypi| | |
32 | ||
33 | This library provides `oauthlib`_ integration with `google-auth`_. | |
34 | ||
35 | .. |build| image:: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib.svg?branch=main | |
36 | :target: https://googleapis.dev/python/google-auth-oauthlib/latest/index.html | |
37 | .. |pypi| image:: https://img.shields.io/pypi/v/google-auth-oauthlib.svg | |
38 | :target: https://pypi.python.org/pypi/google-auth-oauthlib | |
39 | ||
40 | .. _oauthlib: https://github.com/idan/oauthlib | |
41 | .. _google-auth: https://github.com/googleapis/google-auth-library-python | |
42 | ||
43 | Installing | |
44 | ---------- | |
45 | ||
46 | You can install using `pip`_:: | |
47 | ||
48 | $ pip install google-auth-oauthlib | |
49 | ||
50 | .. _pip: https://pip.pypa.io/en/stable/ | |
51 | ||
52 | Documentation | |
53 | ------------- | |
54 | ||
55 | The latest documentation is available at `google-auth-oauthlib.googleapis.dev`_. | |
56 | ||
57 | .. _google-auth-oauthlib.googleapis.dev: https://googleapis.dev/python/google-auth-oauthlib/latest/index.html | |
58 | ||
59 | Supported Python Versions | |
60 | ------------------------- | |
61 | Python >= 3.6 | |
62 | ||
63 | ||
64 | Unsupported Python Versions | |
65 | --------------------------- | |
66 | ||
67 | Python == 2.7, Python == 3.5. | |
68 | ||
69 | The last version of this library compatible with Python 2.7 and 3.5 is | |
70 | `google-auth-oauthlib==0.4.1`. | |
71 | ||
72 | License | |
73 | ------- | |
74 | ||
75 | Apache 2.0 - See `the LICENSE`_ for more information. | |
76 | ||
77 | .. _the LICENSE: https://github.com/googleapis/google-auth-library-python-oauthlib/blob/main/LICENSE |
4 | 4 | |
5 | 5 | This library provides `oauthlib`_ integration with `google-auth`_. |
6 | 6 | |
7 | .. |build| image:: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib.svg?branch=master | |
8 | :target: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib | |
9 | .. |docs| image:: https://readthedocs.org/projects/google-auth-oauthlib/badge/?version=latest | |
10 | :target: https://google-auth-oauthlib.readthedocs.io/en/latest/ | |
7 | .. |build| image:: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib.svg?branch=main | |
8 | :target: https://googleapis.dev/python/google-auth-oauthlib/latest/index.html | |
11 | 9 | .. |pypi| image:: https://img.shields.io/pypi/v/google-auth-oauthlib.svg |
12 | 10 | :target: https://pypi.python.org/pypi/google-auth-oauthlib |
13 | 11 | |
26 | 24 | Documentation |
27 | 25 | ------------- |
28 | 26 | |
29 | The latest documentation is available at `google-auth-oauthlib.readthedocs.io`_. | |
27 | The latest documentation is available at `google-auth-oauthlib.googleapis.dev`_. | |
30 | 28 | |
31 | .. _google-auth-oauthlib.readthedocs.io: http://google-auth-oauthlib.readthedocs.io/ | |
29 | .. _google-auth-oauthlib.googleapis.dev: https://googleapis.dev/python/google-auth-oauthlib/latest/index.html | |
30 | ||
31 | Supported Python Versions | |
32 | ------------------------- | |
33 | Python >= 3.6 | |
34 | ||
35 | ||
36 | Unsupported Python Versions | |
37 | --------------------------- | |
38 | ||
39 | Python == 2.7, Python == 3.5. | |
40 | ||
41 | The last version of this library compatible with Python 2.7 and 3.5 is | |
42 | `google-auth-oauthlib==0.4.1`. | |
32 | 43 | |
33 | 44 | License |
34 | 45 | ------- |
35 | 46 | |
36 | 47 | Apache 2.0 - See `the LICENSE`_ for more information. |
37 | 48 | |
38 | .. _the LICENSE: https://github.com/googleapis/google-auth-library-python-oauthlib/blob/master/LICENSE | |
49 | .. _the LICENSE: https://github.com/googleapis/google-auth-library-python-oauthlib/blob/main/LICENSE |
14 | 14 | """OAuth 2.0 Authorization Flow |
15 | 15 | |
16 | 16 | This module provides integration with `requests-oauthlib`_ for running the |
17 | `OAuth 2.0 Authorization Flow`_ and acquiring user credentials. | |
18 | ||
19 | Here's an example of using :class:`Flow` with the installed application | |
20 | authorization flow:: | |
21 | ||
22 | from google_auth_oauthlib.flow import Flow | |
17 | `OAuth 2.0 Authorization Flow`_ and acquiring user credentials. See | |
18 | `Using OAuth 2.0 to Access Google APIs`_ for an overview of OAuth 2.0 | |
19 | authorization scenarios Google APIs support. | |
20 | ||
21 | Here's an example of using :class:`InstalledAppFlow`:: | |
22 | ||
23 | from google_auth_oauthlib.flow import InstalledAppFlow | |
23 | 24 | |
24 | 25 | # Create the flow using the client secrets file from the Google API |
25 | 26 | # Console. |
26 | flow = Flow.from_client_secrets_file( | |
27 | 'path/to/client_secrets.json', | |
28 | scopes=['profile', 'email'], | |
29 | redirect_uri='urn:ietf:wg:oauth:2.0:oob') | |
30 | ||
31 | # Tell the user to go to the authorization URL. | |
32 | auth_url, _ = flow.authorization_url(prompt='consent') | |
33 | ||
34 | print('Please go to this URL: {}'.format(auth_url)) | |
35 | ||
36 | # The user will get an authorization code. This code is used to get the | |
37 | # access token. | |
38 | code = input('Enter the authorization code: ') | |
39 | flow.fetch_token(code=code) | |
27 | flow = InstalledAppFlow.from_client_secrets_file( | |
28 | 'client_secrets.json', | |
29 | scopes=['profile', 'email']) | |
30 | ||
31 | flow.run_local_server() | |
40 | 32 | |
41 | 33 | # You can use flow.credentials, or you can just get a requests session |
42 | 34 | # using flow.authorized_session. |
43 | 35 | session = flow.authorized_session() |
44 | print(session.get('https://www.googleapis.com/userinfo/v2/me').json()) | |
45 | ||
46 | This particular flow can be handled entirely by using | |
47 | :class:`InstalledAppFlow`. | |
48 | ||
49 | .. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/ | |
36 | ||
37 | profile_info = session.get( | |
38 | 'https://www.googleapis.com/userinfo/v2/me').json() | |
39 | ||
40 | print(profile_info) | |
41 | # {'name': '...', 'email': '...', ...} | |
42 | ||
43 | .. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/latest/ | |
50 | 44 | .. _OAuth 2.0 Authorization Flow: |
51 | 45 | https://tools.ietf.org/html/rfc6749#section-1.2 |
46 | .. _Using OAuth 2.0 to Access Google APIs: | |
47 | https://developers.google.com/identity/protocols/oauth2 | |
48 | ||
52 | 49 | """ |
53 | 50 | from base64 import urlsafe_b64encode |
54 | 51 | import hashlib |
66 | 63 | |
67 | 64 | import google.auth.transport.requests |
68 | 65 | import google.oauth2.credentials |
69 | from six.moves import input | |
70 | 66 | |
71 | 67 | import google_auth_oauthlib.helpers |
72 | 68 | |
87 | 83 | from the `Google API Console`_. |
88 | 84 | |
89 | 85 | .. _client secrets file: |
90 | https://developers.google.com/identity/protocols/OAuth2WebServer | |
86 | https://developers.google.com/identity/protocols/oauth2/web-server | |
91 | 87 | #creatingcred |
92 | 88 | .. _Google API Console: |
93 | 89 | https://console.developers.google.com/apis/credentials |
100 | 96 | client_config, |
101 | 97 | redirect_uri=None, |
102 | 98 | code_verifier=None, |
103 | autogenerate_code_verifier=False, | |
99 | autogenerate_code_verifier=True, | |
104 | 100 | ): |
105 | 101 | """ |
106 | 102 | Args: |
118 | 114 | autogenerate_code_verifier (bool): If true, auto-generate a |
119 | 115 | code_verifier. |
120 | 116 | .. _client secrets: |
121 | https://developers.google.com/api-client-library/python/guide | |
122 | /aaa_client_secrets | |
117 | https://github.com/googleapis/google-api-python-client/blob | |
118 | /main/docs/client-secrets.md | |
123 | 119 | """ |
124 | 120 | self.client_type = client_type |
125 | 121 | """str: The client type, either ``'web'`` or ``'installed'``""" |
152 | 148 | format. |
153 | 149 | |
154 | 150 | .. _client secrets: |
155 | https://developers.google.com/api-client-library/python/guide | |
156 | /aaa_client_secrets | |
151 | https://github.com/googleapis/google-api-python-client/blob/main/docs/client-secrets.md | |
157 | 152 | """ |
158 | 153 | if "web" in client_config: |
159 | 154 | client_type = "web" |
212 | 207 | |
213 | 208 | @redirect_uri.setter |
214 | 209 | def redirect_uri(self, value): |
210 | """The OAuth 2.0 redirect URI. Pass-through to | |
211 | ``self.oauth2session.redirect_uri``.""" | |
215 | 212 | self.oauth2session.redirect_uri = value |
216 | 213 | |
217 | 214 | def authorization_url(self, **kwargs): |
326 | 323 | local development or applications that are installed on a desktop operating |
327 | 324 | system. |
328 | 325 | |
329 | This flow has two strategies: The console strategy provided by | |
330 | :meth:`run_console` and the local server strategy provided by | |
331 | :meth:`run_local_server`. | |
326 | This flow uses a local server strategy provided by :meth:`run_local_server`. | |
332 | 327 | |
333 | 328 | Example:: |
334 | 329 | |
349 | 344 | # {'name': '...', 'email': '...', ...} |
350 | 345 | |
351 | 346 | |
352 | Note that these aren't the only two ways to accomplish the installed | |
353 | application flow, they are just the most common ways. You can use the | |
347 | Note that this isn't the only way to accomplish the installed | |
348 | application flow, just one of the most common. You can use the | |
354 | 349 | :class:`Flow` class to perform the same flow with different methods of |
355 | 350 | presenting the authorization URL to the user or obtaining the authorization |
356 | 351 | response, such as using an embedded web view. |
357 | 352 | |
358 | 353 | .. _Installed Application Authorization Flow: |
359 | https://developers.google.com/api-client-library/python/auth | |
360 | /installed-app | |
354 | https://github.com/googleapis/google-api-python-client/blob/main/docs/oauth-installed.md | |
361 | 355 | """ |
362 | ||
363 | _OOB_REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob" | |
364 | 356 | |
365 | 357 | _DEFAULT_AUTH_PROMPT_MESSAGE = ( |
366 | 358 | "Please visit this URL to authorize this application: {url}" |
375 | 367 | "The authentication flow has completed. You may close this window." |
376 | 368 | ) |
377 | 369 | |
378 | def run_console( | |
379 | self, | |
380 | authorization_prompt_message=_DEFAULT_AUTH_PROMPT_MESSAGE, | |
381 | authorization_code_message=_DEFAULT_AUTH_CODE_MESSAGE, | |
382 | **kwargs | |
383 | ): | |
384 | """Run the flow using the console strategy. | |
385 | ||
386 | The console strategy instructs the user to open the authorization URL | |
387 | in their browser. Once the authorization is complete the authorization | |
388 | server will give the user a code. The user then must copy & paste this | |
389 | code into the application. The code is then exchanged for a token. | |
390 | ||
391 | Args: | |
392 | authorization_prompt_message (str): The message to display to tell | |
393 | the user to navigate to the authorization URL. | |
394 | authorization_code_message (str): The message to display when | |
395 | prompting the user for the authorization code. | |
396 | kwargs: Additional keyword arguments passed through to | |
397 | :meth:`authorization_url`. | |
398 | ||
399 | Returns: | |
400 | google.oauth2.credentials.Credentials: The OAuth 2.0 credentials | |
401 | for the user. | |
402 | """ | |
403 | kwargs.setdefault("prompt", "consent") | |
404 | ||
405 | self.redirect_uri = self._OOB_REDIRECT_URI | |
406 | ||
407 | auth_url, _ = self.authorization_url(**kwargs) | |
408 | ||
409 | print(authorization_prompt_message.format(url=auth_url)) | |
410 | ||
411 | code = input(authorization_code_message) | |
412 | ||
413 | self.fetch_token(code=code) | |
414 | ||
415 | return self.credentials | |
416 | ||
417 | 370 | def run_local_server( |
418 | 371 | self, |
419 | 372 | host="localhost", |
373 | bind_addr=None, | |
420 | 374 | port=8080, |
421 | 375 | authorization_prompt_message=_DEFAULT_AUTH_PROMPT_MESSAGE, |
422 | 376 | success_message=_DEFAULT_WEB_SUCCESS_MESSAGE, |
423 | 377 | open_browser=True, |
378 | redirect_uri_trailing_slash=True, | |
379 | timeout_seconds=None, | |
424 | 380 | **kwargs |
425 | 381 | ): |
426 | 382 | """Run the flow using the server strategy. |
436 | 392 | Args: |
437 | 393 | host (str): The hostname for the local redirect server. This will |
438 | 394 | be served over http, not https. |
395 | bind_addr (str): Optionally provide an ip address for the redirect | |
396 | server to listen on when it is not the same as host | |
397 | (e.g. in a container). Default value is None, | |
398 | which means that the redirect server will listen | |
399 | on the ip address specified in the host parameter. | |
439 | 400 | port (int): The port for the local redirect server. |
440 | authorization_prompt_message (str): The message to display to tell | |
441 | the user to navigate to the authorization URL. | |
401 | authorization_prompt_message (str | None): The message to display to tell | |
402 | the user to navigate to the authorization URL. If None or empty, | |
403 | don't display anything. | |
442 | 404 | success_message (str): The message to display in the web browser |
443 | 405 | the authorization flow is complete. |
444 | 406 | open_browser (bool): Whether or not to open the authorization URL |
445 | 407 | in the user's browser. |
408 | redirect_uri_trailing_slash (bool): whether or not to add trailing | |
409 | slash when constructing the redirect_uri. Default value is True. | |
410 | timeout_seconds (int): It will raise an error after the timeout timing | |
411 | if there are no credentials response. The value is in seconds. | |
412 | When set to None there is no timeout. | |
413 | Default value is None. | |
446 | 414 | kwargs: Additional keyword arguments passed through to |
447 | 415 | :meth:`authorization_url`. |
448 | 416 | |
454 | 422 | # Fail fast if the address is occupied |
455 | 423 | wsgiref.simple_server.WSGIServer.allow_reuse_address = False |
456 | 424 | local_server = wsgiref.simple_server.make_server( |
457 | host, port, wsgi_app, handler_class=_WSGIRequestHandler | |
458 | ) | |
459 | ||
460 | self.redirect_uri = "http://{}:{}/".format(host, local_server.server_port) | |
425 | bind_addr or host, port, wsgi_app, handler_class=_WSGIRequestHandler | |
426 | ) | |
427 | ||
428 | redirect_uri_format = ( | |
429 | "http://{}:{}/" if redirect_uri_trailing_slash else "http://{}:{}" | |
430 | ) | |
431 | self.redirect_uri = redirect_uri_format.format(host, local_server.server_port) | |
461 | 432 | auth_url, _ = self.authorization_url(**kwargs) |
462 | 433 | |
463 | 434 | if open_browser: |
464 | 435 | webbrowser.open(auth_url, new=1, autoraise=True) |
465 | 436 | |
466 | print(authorization_prompt_message.format(url=auth_url)) | |
467 | ||
437 | if authorization_prompt_message: | |
438 | print(authorization_prompt_message.format(url=auth_url)) | |
439 | ||
440 | local_server.timeout = timeout_seconds | |
468 | 441 | local_server.handle_request() |
469 | 442 | |
470 | 443 | # Note: using https here because oauthlib is very picky that |
516 | 489 | Returns: |
517 | 490 | Iterable[bytes]: The response body. |
518 | 491 | """ |
519 | start_response("200 OK", [("Content-type", "text/plain")]) | |
492 | start_response("200 OK", [("Content-type", "text/plain; charset=utf-8")]) | |
520 | 493 | self.last_request_uri = wsgiref.util.request_uri(environ) |
521 | 494 | return [self._success_message.encode("utf-8")] |
17 | 17 | Typically, you'll want to use the higher-level helpers in |
18 | 18 | :mod:`google_auth_oauthlib.flow`. |
19 | 19 | |
20 | .. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/ | |
20 | .. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/latest/ | |
21 | 21 | """ |
22 | 22 | |
23 | 23 | import datetime |
24 | 24 | import json |
25 | 25 | |
26 | from google.auth import external_account_authorized_user | |
26 | 27 | import google.oauth2.credentials |
27 | 28 | import requests_oauthlib |
28 | 29 | |
50 | 51 | oauthlib session and the validated client configuration. |
51 | 52 | |
52 | 53 | .. _client secrets: |
53 | https://developers.google.com/api-client-library/python/guide | |
54 | /aaa_client_secrets | |
54 | https://github.com/googleapis/google-api-python-client/blob/main/docs/client-secrets.md | |
55 | 55 | """ |
56 | 56 | |
57 | 57 | if "web" in client_config: |
88 | 88 | oauthlib session and the validated client configuration. |
89 | 89 | |
90 | 90 | .. _client secrets: |
91 | https://developers.google.com/api-client-library/python/guide | |
92 | /aaa_client_secrets | |
91 | https://github.com/googleapis/google-api-python-client/blob/main/docs/client-secrets.md | |
93 | 92 | """ |
94 | 93 | with open(client_secrets_file, "r") as json_file: |
95 | 94 | client_config = json.load(json_file) |
126 | 125 | "There is no access token for this session, did you call " "fetch_token?" |
127 | 126 | ) |
128 | 127 | |
129 | credentials = google.oauth2.credentials.Credentials( | |
130 | session.token["access_token"], | |
131 | refresh_token=session.token.get("refresh_token"), | |
132 | id_token=session.token.get("id_token"), | |
133 | token_uri=client_config.get("token_uri"), | |
134 | client_id=client_config.get("client_id"), | |
135 | client_secret=client_config.get("client_secret"), | |
136 | scopes=session.scope, | |
137 | ) | |
128 | if "3pi" in client_config: | |
129 | credentials = external_account_authorized_user.Credentials( | |
130 | token=session.token["access_token"], | |
131 | refresh_token=session.token.get("refresh_token"), | |
132 | token_url=client_config.get("token_uri"), | |
133 | client_id=client_config.get("client_id"), | |
134 | client_secret=client_config.get("client_secret"), | |
135 | token_info_url=client_config.get("token_info_url"), | |
136 | scopes=session.scope, | |
137 | ) | |
138 | else: | |
139 | credentials = google.oauth2.credentials.Credentials( | |
140 | session.token["access_token"], | |
141 | refresh_token=session.token.get("refresh_token"), | |
142 | id_token=session.token.get("id_token"), | |
143 | token_uri=client_config.get("token_uri"), | |
144 | client_id=client_config.get("client_id"), | |
145 | client_secret=client_config.get("client_secret"), | |
146 | scopes=session.scope, | |
147 | granted_scopes=session.token.get("scope"), | |
148 | ) | |
138 | 149 | credentials.expiry = datetime.datetime.utcfromtimestamp(session.token["expires_at"]) |
139 | 150 | return credentials |
20 | 20 | |
21 | 21 | from __future__ import absolute_import |
22 | 22 | |
23 | import contextlib | |
24 | import socket | |
25 | ||
23 | 26 | import google_auth_oauthlib.flow |
24 | 27 | |
25 | 28 | |
26 | def get_user_credentials(scopes, client_id, client_secret): | |
29 | LOCALHOST = "localhost" | |
30 | DEFAULT_PORTS_TO_TRY = 100 | |
31 | ||
32 | ||
33 | def is_port_open(port): | |
34 | """Check if a port is open on localhost. | |
35 | Based on StackOverflow answer: https://stackoverflow.com/a/43238489/101923 | |
36 | Parameters | |
37 | ---------- | |
38 | port : int | |
39 | A port to check on localhost. | |
40 | Returns | |
41 | ------- | |
42 | is_open : bool | |
43 | True if a socket can be opened at the requested port. | |
44 | """ | |
45 | with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: | |
46 | try: | |
47 | sock.bind((LOCALHOST, port)) | |
48 | sock.listen(1) | |
49 | except socket.error: | |
50 | is_open = False | |
51 | else: | |
52 | is_open = True | |
53 | return is_open | |
54 | ||
55 | ||
56 | def find_open_port(start=8080, stop=None): | |
57 | """Find an open port between ``start`` and ``stop``. | |
58 | Parameters | |
59 | ---------- | |
60 | start : Optional[int] | |
61 | Beginning of range of ports to try. Defaults to 8080. | |
62 | stop : Optional[int] | |
63 | End of range of ports to try (not including exactly equals ``stop``). | |
64 | This function tries 100 possible ports if no ``stop`` is specified. | |
65 | Returns | |
66 | ------- | |
67 | Optional[int] | |
68 | ``None`` if no open port is found, otherwise an integer indicating an | |
69 | open port. | |
70 | """ | |
71 | if not stop: | |
72 | stop = start + DEFAULT_PORTS_TO_TRY | |
73 | ||
74 | for port in range(start, stop): | |
75 | if is_port_open(port): | |
76 | return port | |
77 | ||
78 | # No open ports found. | |
79 | return None | |
80 | ||
81 | ||
82 | def get_user_credentials( | |
83 | scopes, client_id, client_secret, minimum_port=8080, maximum_port=None | |
84 | ): | |
27 | 85 | """Gets credentials associated with your Google user account. |
28 | 86 | |
29 | 87 | This function authenticates using your user credentials by going through |
52 | 110 | A string that verifies your application to Google APIs. Find this |
53 | 111 | value in the `Credentials page on the Google Developer's Console |
54 | 112 | <https://console.developers.google.com/apis/credentials>`_. |
113 | minimum_port (int): | |
114 | Beginning of range of ports to try for redirect URI HTTP server. | |
115 | Defaults to 8080. | |
116 | maximum_port (Optional[int]): | |
117 | End of range of ports to try (not including exactly equals ``stop``). | |
118 | This function tries 100 possible ports if no ``stop`` is specified. | |
55 | 119 | |
56 | 120 | Returns: |
57 | 121 | google.oauth2.credentials.Credentials: |
91 | 155 | "installed": { |
92 | 156 | "client_id": client_id, |
93 | 157 | "client_secret": client_secret, |
94 | "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob"], | |
95 | 158 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", |
96 | 159 | "token_uri": "https://oauth2.googleapis.com/token", |
97 | 160 | } |
101 | 164 | client_config, scopes=scopes |
102 | 165 | ) |
103 | 166 | |
104 | return app_flow.run_console() | |
167 | port = find_open_port(start=minimum_port, stop=maximum_port) | |
168 | if not port: | |
169 | raise ConnectionError("Could not find open port.") | |
170 | ||
171 | return app_flow.run_local_server(host=LOCALHOST, port=port) |
71 | 71 | default=os.path.join(click.get_app_dir(APP_NAME), DEFAULT_CREDENTIALS_FILENAME), |
72 | 72 | help="Path to store OAuth2 credentials.", |
73 | 73 | ) |
74 | @click.option( | |
75 | "--headless", | |
76 | is_flag=True, | |
77 | metavar="<headless_mode>", | |
78 | show_default=True, | |
79 | default=False, | |
80 | help="Run a console based flow.", | |
81 | ) | |
82 | def main(client_secrets, scope, save, credentials, headless): | |
74 | def main(client_secrets, scope, save, credentials): | |
83 | 75 | """Command-line tool for obtaining authorization and credentials from a user. |
84 | 76 | |
85 | 77 | This tool uses the OAuth 2.0 Authorization Code grant as described |
87 | 79 | https://tools.ietf.org/html/rfc6749#section-1.3.1 |
88 | 80 | |
89 | 81 | This tool is intended for assist developers in obtaining credentials |
90 | for testing applications where it may not be possible or easy to run a | |
91 | complete OAuth 2.0 authorization flow, especially in the case of code | |
92 | samples or embedded devices without input / display capabilities. | |
82 | for testing applications or samples. | |
93 | 83 | |
94 | 84 | This is not intended for production use where a combination of |
95 | 85 | companion and on-device applications should complete the OAuth 2.0 |
101 | 91 | client_secrets, scopes=scope |
102 | 92 | ) |
103 | 93 | |
104 | if not headless: | |
105 | creds = flow.run_local_server() | |
106 | else: | |
107 | creds = flow.run_console() | |
94 | creds = flow.run_local_server() | |
108 | 95 | |
109 | 96 | creds_data = { |
110 | 97 | "token": creds.token, |
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: google-auth-oauthlib |
2 | Version: 0.4.2 | |
2 | Version: 1.0.0 | |
3 | 3 | Summary: Google Authentication Library |
4 | 4 | Home-page: https://github.com/GoogleCloudPlatform/google-auth-library-python-oauthlib |
5 | 5 | Author: Google Cloud Platform |
6 | 6 | Author-email: jonwayne+google-auth@google.com |
7 | 7 | License: Apache 2.0 |
8 | Description: oauthlib integration for Google Auth | |
9 | ==================================== | |
10 | ||
11 | |pypi| | |
12 | ||
13 | This library provides `oauthlib`_ integration with `google-auth`_. | |
14 | ||
15 | .. |build| image:: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib.svg?branch=master | |
16 | :target: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib | |
17 | .. |docs| image:: https://readthedocs.org/projects/google-auth-oauthlib/badge/?version=latest | |
18 | :target: https://google-auth-oauthlib.readthedocs.io/en/latest/ | |
19 | .. |pypi| image:: https://img.shields.io/pypi/v/google-auth-oauthlib.svg | |
20 | :target: https://pypi.python.org/pypi/google-auth-oauthlib | |
21 | ||
22 | .. _oauthlib: https://github.com/idan/oauthlib | |
23 | .. _google-auth: https://github.com/googleapis/google-auth-library-python | |
24 | ||
25 | Installing | |
26 | ---------- | |
27 | ||
28 | You can install using `pip`_:: | |
29 | ||
30 | $ pip install google-auth-oauthlib | |
31 | ||
32 | .. _pip: https://pip.pypa.io/en/stable/ | |
33 | ||
34 | Documentation | |
35 | ------------- | |
36 | ||
37 | The latest documentation is available at `google-auth-oauthlib.readthedocs.io`_. | |
38 | ||
39 | .. _google-auth-oauthlib.readthedocs.io: http://google-auth-oauthlib.readthedocs.io/ | |
40 | ||
41 | License | |
42 | ------- | |
43 | ||
44 | Apache 2.0 - See `the LICENSE`_ for more information. | |
45 | ||
46 | .. _the LICENSE: https://github.com/googleapis/google-auth-library-python-oauthlib/blob/master/LICENSE | |
47 | ||
48 | 8 | Keywords: google auth oauth client oauthlib |
49 | Platform: UNKNOWN | |
50 | 9 | Classifier: Programming Language :: Python :: 3 |
51 | 10 | Classifier: Programming Language :: Python :: 3.6 |
52 | 11 | Classifier: Programming Language :: Python :: 3.7 |
53 | 12 | Classifier: Programming Language :: Python :: 3.8 |
54 | 13 | Classifier: Programming Language :: Python :: 3.9 |
55 | Classifier: Development Status :: 3 - Alpha | |
14 | Classifier: Programming Language :: Python :: 3.10 | |
15 | Classifier: Programming Language :: Python :: 3.11 | |
16 | Classifier: Development Status :: 5 - Production/Stable | |
56 | 17 | Classifier: Intended Audience :: Developers |
57 | 18 | Classifier: License :: OSI Approved :: Apache Software License |
58 | 19 | Classifier: Operating System :: POSIX |
62 | 23 | Classifier: Topic :: Internet :: WWW/HTTP |
63 | 24 | Requires-Python: >=3.6 |
64 | 25 | Provides-Extra: tool |
26 | License-File: LICENSE | |
27 | ||
28 | oauthlib integration for Google Auth | |
29 | ==================================== | |
30 | ||
31 | |pypi| | |
32 | ||
33 | This library provides `oauthlib`_ integration with `google-auth`_. | |
34 | ||
35 | .. |build| image:: https://travis-ci.org/googleapis/google-auth-library-python-oauthlib.svg?branch=main | |
36 | :target: https://googleapis.dev/python/google-auth-oauthlib/latest/index.html | |
37 | .. |pypi| image:: https://img.shields.io/pypi/v/google-auth-oauthlib.svg | |
38 | :target: https://pypi.python.org/pypi/google-auth-oauthlib | |
39 | ||
40 | .. _oauthlib: https://github.com/idan/oauthlib | |
41 | .. _google-auth: https://github.com/googleapis/google-auth-library-python | |
42 | ||
43 | Installing | |
44 | ---------- | |
45 | ||
46 | You can install using `pip`_:: | |
47 | ||
48 | $ pip install google-auth-oauthlib | |
49 | ||
50 | .. _pip: https://pip.pypa.io/en/stable/ | |
51 | ||
52 | Documentation | |
53 | ------------- | |
54 | ||
55 | The latest documentation is available at `google-auth-oauthlib.googleapis.dev`_. | |
56 | ||
57 | .. _google-auth-oauthlib.googleapis.dev: https://googleapis.dev/python/google-auth-oauthlib/latest/index.html | |
58 | ||
59 | Supported Python Versions | |
60 | ------------------------- | |
61 | Python >= 3.6 | |
62 | ||
63 | ||
64 | Unsupported Python Versions | |
65 | --------------------------- | |
66 | ||
67 | Python == 2.7, Python == 3.5. | |
68 | ||
69 | The last version of this library compatible with Python 2.7 and 3.5 is | |
70 | `google-auth-oauthlib==0.4.1`. | |
71 | ||
72 | License | |
73 | ------- | |
74 | ||
75 | Apache 2.0 - See `the LICENSE`_ for more information. | |
76 | ||
77 | .. _the LICENSE: https://github.com/googleapis/google-auth-library-python-oauthlib/blob/main/LICENSE |
17 | 17 | from setuptools import setup |
18 | 18 | |
19 | 19 | |
20 | TOOL_DEPENDENCIES = "click" | |
20 | TOOL_DEPENDENCIES = "click>=6.0.0" | |
21 | 21 | |
22 | DEPENDENCIES = ("google-auth", "requests-oauthlib>=0.7.0") | |
22 | DEPENDENCIES = ("google-auth>=2.15.0", "requests-oauthlib>=0.7.0") | |
23 | 23 | |
24 | 24 | |
25 | 25 | with io.open("README.rst", "r") as fh: |
26 | 26 | long_description = fh.read() |
27 | 27 | |
28 | 28 | |
29 | version = "0.4.2" | |
29 | version = "1.0.0" | |
30 | 30 | |
31 | 31 | setup( |
32 | 32 | name="google-auth-oauthlib", |
53 | 53 | "Programming Language :: Python :: 3.7", |
54 | 54 | "Programming Language :: Python :: 3.8", |
55 | 55 | "Programming Language :: Python :: 3.9", |
56 | "Development Status :: 3 - Alpha", | |
56 | "Programming Language :: Python :: 3.10", | |
57 | "Programming Language :: Python :: 3.11", | |
58 | "Development Status :: 5 - Production/Stable", | |
57 | 59 | "Intended Audience :: Developers", |
58 | 60 | "License :: OSI Approved :: Apache Software License", |
59 | 61 | "Operating System :: POSIX", |
6 | 6 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", |
7 | 7 | "client_secret": "itsasecrettoeveryone", |
8 | 8 | "redirect_uris": [ |
9 | "urn:ietf:wg:oauth:2.0:oob", | |
10 | 9 | "http://localhost" |
11 | 10 | ] |
12 | 11 | } |
23 | 23 | import mock |
24 | 24 | import pytest |
25 | 25 | import requests |
26 | from six.moves import urllib | |
26 | import urllib | |
27 | 27 | |
28 | 28 | from google_auth_oauthlib import flow |
29 | 29 | |
281 | 281 | with fetch_token_patch as fetch_token_mock: |
282 | 282 | yield fetch_token_mock |
283 | 283 | |
284 | @mock.patch("google_auth_oauthlib.flow.input", autospec=True) | |
285 | def test_run_console(self, input_mock, instance, mock_fetch_token): | |
286 | input_mock.return_value = mock.sentinel.code | |
287 | instance.code_verifier = "amanaplanacanalpanama" | |
288 | credentials = instance.run_console() | |
289 | ||
290 | assert credentials.token == mock.sentinel.access_token | |
291 | assert credentials._refresh_token == mock.sentinel.refresh_token | |
292 | assert credentials.id_token == mock.sentinel.id_token | |
293 | ||
294 | mock_fetch_token.assert_called_with( | |
295 | CLIENT_SECRETS_INFO["web"]["token_uri"], | |
296 | client_secret=CLIENT_SECRETS_INFO["web"]["client_secret"], | |
297 | code=mock.sentinel.code, | |
298 | code_verifier="amanaplanacanalpanama", | |
299 | ) | |
300 | ||
301 | 284 | @pytest.mark.webtest |
302 | 285 | @mock.patch("google_auth_oauthlib.flow.webbrowser", autospec=True) |
303 | 286 | def test_run_local_server(self, webbrowser_mock, instance, mock_fetch_token, port): |
320 | 303 | assert credentials._refresh_token == mock.sentinel.refresh_token |
321 | 304 | assert credentials.id_token == mock.sentinel.id_token |
322 | 305 | assert webbrowser_mock.open.called |
306 | assert instance.redirect_uri == f"http://localhost:{port}/" | |
323 | 307 | |
324 | 308 | expected_auth_response = auth_redirect_url.replace("http", "https") |
325 | 309 | mock_fetch_token.assert_called_with( |
340 | 324 | instance.code_verifier = "amanaplanacanalpanama" |
341 | 325 | |
342 | 326 | with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: |
343 | future = pool.submit(partial(instance.run_local_server, port=port)) | |
327 | future = pool.submit( | |
328 | partial( | |
329 | instance.run_local_server, | |
330 | port=port, | |
331 | redirect_uri_trailing_slash=False, | |
332 | ) | |
333 | ) | |
344 | 334 | |
345 | 335 | while not future.done(): |
346 | 336 | try: |
354 | 344 | assert credentials._refresh_token == mock.sentinel.refresh_token |
355 | 345 | assert credentials.id_token == mock.sentinel.id_token |
356 | 346 | assert webbrowser_mock.open.called |
347 | assert instance.redirect_uri == f"http://localhost:{port}" | |
357 | 348 | |
358 | 349 | expected_auth_response = auth_redirect_url.replace("http", "https") |
359 | 350 | mock_fetch_token.assert_called_with( |
377 | 368 | instance.run_local_server(open_browser=False) |
378 | 369 | |
379 | 370 | assert not webbrowser_mock.open.called |
371 | ||
372 | @mock.patch("google_auth_oauthlib.flow.webbrowser", autospec=True) | |
373 | @mock.patch("wsgiref.simple_server.make_server", autospec=True) | |
374 | def test_run_local_server_bind_addr( | |
375 | self, make_server_mock, webbrowser_mock, instance, mock_fetch_token | |
376 | ): | |
377 | def assign_last_request_uri(host, port, wsgi_app, **kwargs): | |
378 | wsgi_app.last_request_uri = self.REDIRECT_REQUEST_PATH | |
379 | return mock.Mock() | |
380 | ||
381 | make_server_mock.side_effect = assign_last_request_uri | |
382 | ||
383 | my_ip = socket.gethostbyname(socket.gethostname()) | |
384 | instance.run_local_server(bind_addr=my_ip, host="localhost") | |
385 | ||
386 | assert webbrowser_mock.open.called | |
387 | name, args, kwargs = make_server_mock.mock_calls[0] | |
388 | assert args[0] == my_ip | |
380 | 389 | |
381 | 390 | @pytest.mark.webtest |
382 | 391 | @mock.patch("google_auth_oauthlib.flow.webbrowser", autospec=True) |
18 | 18 | import mock |
19 | 19 | import pytest |
20 | 20 | |
21 | from google.auth import external_account_authorized_user | |
22 | import google.oauth2.credentials | |
21 | 23 | from google_auth_oauthlib import helpers |
22 | 24 | |
23 | 25 | DATA_DIR = os.path.join(os.path.dirname(__file__), "data") |
84 | 86 | |
85 | 87 | credentials = helpers.credentials_from_session(session, CLIENT_SECRETS_INFO["web"]) |
86 | 88 | |
89 | assert isinstance(credentials, google.oauth2.credentials.Credentials) | |
87 | 90 | assert credentials.token == mock.sentinel.access_token |
88 | 91 | assert credentials.expiry == datetime.datetime(1990, 5, 29, 8, 20, 0) |
89 | 92 | assert credentials._refresh_token == mock.sentinel.refresh_token |
91 | 94 | assert credentials._client_id == CLIENT_SECRETS_INFO["web"]["client_id"] |
92 | 95 | assert credentials._client_secret == CLIENT_SECRETS_INFO["web"]["client_secret"] |
93 | 96 | assert credentials._token_uri == CLIENT_SECRETS_INFO["web"]["token_uri"] |
97 | assert credentials.scopes == session.scope | |
98 | assert credentials.granted_scopes is None | |
99 | ||
100 | ||
101 | def test_credentials_from_session_granted_scopes(session): | |
102 | granted_scopes = ["scope1", "scope2"] | |
103 | session.token = { | |
104 | "access_token": mock.sentinel.access_token, | |
105 | "refresh_token": mock.sentinel.refresh_token, | |
106 | "id_token": mock.sentinel.id_token, | |
107 | "expires_at": 643969200.0, | |
108 | "scope": granted_scopes, | |
109 | } | |
110 | ||
111 | credentials = helpers.credentials_from_session(session, CLIENT_SECRETS_INFO["web"]) | |
112 | ||
113 | assert isinstance(credentials, google.oauth2.credentials.Credentials) | |
114 | assert credentials.token == mock.sentinel.access_token | |
115 | assert credentials.expiry == datetime.datetime(1990, 5, 29, 8, 20, 0) | |
116 | assert credentials._refresh_token == mock.sentinel.refresh_token | |
117 | assert credentials.id_token == mock.sentinel.id_token | |
118 | assert credentials._client_id == CLIENT_SECRETS_INFO["web"]["client_id"] | |
119 | assert credentials._client_secret == CLIENT_SECRETS_INFO["web"]["client_secret"] | |
120 | assert credentials._token_uri == CLIENT_SECRETS_INFO["web"]["token_uri"] | |
121 | assert credentials.scopes == session.scope | |
122 | assert credentials.granted_scopes == granted_scopes | |
123 | ||
124 | ||
125 | def test_credentials_from_session_3pi(session): | |
126 | session.token = { | |
127 | "access_token": mock.sentinel.access_token, | |
128 | "refresh_token": mock.sentinel.refresh_token, | |
129 | "id_token": mock.sentinel.id_token, | |
130 | "expires_at": 643969200.0, | |
131 | } | |
132 | ||
133 | client_secrets_info = CLIENT_SECRETS_INFO["web"].copy() | |
134 | client_secrets_info["3pi"] = True | |
135 | client_secrets_info[ | |
136 | "token_info_url" | |
137 | ] = "https://accounts.google.com/o/oauth2/introspect" | |
138 | credentials = helpers.credentials_from_session(session, client_secrets_info) | |
139 | ||
140 | assert isinstance(credentials, external_account_authorized_user.Credentials) | |
141 | assert credentials.token == mock.sentinel.access_token | |
142 | assert credentials.expiry == datetime.datetime(1990, 5, 29, 8, 20, 0) | |
143 | assert credentials._refresh_token == mock.sentinel.refresh_token | |
144 | assert credentials._client_id == CLIENT_SECRETS_INFO["web"]["client_id"] | |
145 | assert credentials._client_secret == CLIENT_SECRETS_INFO["web"]["client_secret"] | |
146 | assert credentials._token_url == CLIENT_SECRETS_INFO["web"]["token_uri"] | |
147 | assert ( | |
148 | credentials._token_info_url == "https://accounts.google.com/o/oauth2/introspect" | |
149 | ) | |
150 | assert credentials.scopes == session.scope | |
94 | 151 | |
95 | 152 | |
96 | 153 | def test_bad_credentials(session): |
11 | 11 | # See the License for the specific language governing permissions and |
12 | 12 | # limitations under the License. |
13 | 13 | |
14 | import socket | |
15 | ||
14 | 16 | import mock |
17 | import pytest | |
18 | ||
19 | ||
20 | def test_find_open_port_finds_start_port(monkeypatch): | |
21 | from google_auth_oauthlib import interactive as module_under_test | |
22 | ||
23 | monkeypatch.setattr(socket, "socket", mock.create_autospec(socket.socket)) | |
24 | port = module_under_test.find_open_port(9999) | |
25 | assert port == 9999 | |
26 | ||
27 | ||
28 | def test_find_open_port_finds_stop_port(monkeypatch): | |
29 | from google_auth_oauthlib import interactive as module_under_test | |
30 | ||
31 | socket_instance = mock.create_autospec(socket.socket, instance=True) | |
32 | ||
33 | def mock_socket(family, type_): | |
34 | return socket_instance | |
35 | ||
36 | monkeypatch.setattr(socket, "socket", mock_socket) | |
37 | socket_instance.listen.side_effect = [socket.error] * 99 + [None] | |
38 | port = module_under_test.find_open_port(9000, stop=9100) | |
39 | assert port == 9099 | |
40 | ||
41 | ||
42 | def test_find_open_port_returns_none(monkeypatch): | |
43 | from google_auth_oauthlib import interactive as module_under_test | |
44 | ||
45 | socket_instance = mock.create_autospec(socket.socket, instance=True) | |
46 | ||
47 | def mock_socket(family, type_): | |
48 | return socket_instance | |
49 | ||
50 | monkeypatch.setattr(socket, "socket", mock_socket) | |
51 | socket_instance.listen.side_effect = socket.error | |
52 | port = module_under_test.find_open_port(9000) | |
53 | assert port is None | |
54 | socket_instance.listen.assert_has_calls(mock.call(1) for _ in range(100)) | |
15 | 55 | |
16 | 56 | |
17 | 57 | def test_get_user_credentials(): |
32 | 72 | actual_client_config = mock_flow.from_client_config.call_args[0][0] |
33 | 73 | assert actual_client_config["installed"]["client_id"] == "some-client-id" |
34 | 74 | assert actual_client_config["installed"]["client_secret"] == "shh-secret" |
35 | mock_flow_instance.run_console.assert_called_once() | |
75 | mock_flow_instance.run_local_server.assert_called_once() | |
76 | ||
77 | ||
78 | def test_get_user_credentials_raises_connectionerror(monkeypatch): | |
79 | from google_auth_oauthlib import flow | |
80 | from google_auth_oauthlib import interactive as module_under_test | |
81 | ||
82 | def mock_find_open_port(start=8080, stop=None): | |
83 | return None | |
84 | ||
85 | monkeypatch.setattr(module_under_test, "find_open_port", mock_find_open_port) | |
86 | mock_flow = mock.create_autospec(flow.InstalledAppFlow, instance=True) | |
87 | ||
88 | with mock.patch( | |
89 | "google_auth_oauthlib.flow.InstalledAppFlow", autospec=True | |
90 | ) as mock_flow, pytest.raises(ConnectionError): | |
91 | mock_flow.from_client_config.return_value = mock_flow | |
92 | module_under_test.get_user_credentials( | |
93 | ["scopes"], "some-client-id", "shh-secret" | |
94 | ) | |
95 | ||
96 | mock_flow.run_local_server.assert_not_called() |
56 | 56 | flow.return_value = dummy_credentials |
57 | 57 | yield flow |
58 | 58 | |
59 | @pytest.fixture | |
60 | def console_mock(self, dummy_credentials): | |
61 | run_console_patch = mock.patch.object( | |
62 | google_auth_oauthlib.flow.InstalledAppFlow, "run_console", autospec=True | |
63 | ) | |
64 | ||
65 | with run_console_patch as flow: | |
66 | flow.return_value = dummy_credentials | |
67 | yield flow | |
68 | ||
69 | 59 | def test_help(self, runner): |
70 | 60 | result = runner.invoke(cli.main, ["--help"]) |
71 | 61 | assert not result.exception |
89 | 79 | assert creds.client_id == dummy_credentials.client_id |
90 | 80 | assert creds.client_secret == dummy_credentials.client_secret |
91 | 81 | assert creds.scopes == dummy_credentials.scopes |
92 | ||
93 | def test_headless(self, runner, dummy_credentials, console_mock): | |
94 | result = runner.invoke( | |
95 | cli.main, | |
96 | [ | |
97 | "--client-secrets", | |
98 | CLIENT_SECRETS_FILE, | |
99 | "--scope", | |
100 | "somescope", | |
101 | "--headless", | |
102 | ], | |
103 | ) | |
104 | console_mock.assert_called_with(mock.ANY) | |
105 | assert not result.exception | |
106 | assert dummy_credentials.refresh_token in result.output | |
107 | assert result.exit_code == 0 | |
108 | 82 | |
109 | 83 | def test_save_new_dir(self, runner, dummy_credentials, local_server_mock): |
110 | 84 | credentials_tmpdir = tempfile.mkdtemp() |