blob: e70ee3dd32583e25714a280f91b4da7f0dce0f08 [file] [log] [blame]
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -08001# Copyright 2016 Google Inc.
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"""OAuth 2.0 Authorization Flow
16
Jon Wayne Parrott474c8df2017-02-08 13:14:02 -080017.. warning::
18 This module is experimental and is subject to change signficantly
19 within major version releases.
20
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -080021This module provides integration with `requests-oauthlib`_ for running the
22`OAuth 2.0 Authorization Flow`_ and acquiring user credentials.
23
24Here's an example of using the flow with the installed application
25authorization flow::
26
27 import google.oauth2.flow
28
29 # Create the flow using the client secrets file from the Google API
30 # Console.
31 flow = google.oauth2.flow.Flow.from_client_secrets_file(
32 'path/to/client_secrets.json',
33 scopes=['profile', 'email'],
34 redirect_uri='urn:ietf:wg:oauth:2.0:oob')
35
36 # Tell the user to go to the authorization URL.
37 auth_url, _ = flow.authorization_url(prompt='consent')
38
39 print('Please go to this URL: {}'.format(auth_url))
40
41 # The user will get an authorization code. This code is used to get the
42 # access token.
43 code = input('Enter the authorization code: ')
44 flow.fetch_token(code=code)
45
46 # You can use flow.credentials, or you can just get a requests session
47 # using flow.authorized_session.
48 session = flow.authorized_session()
49 print(session.get('https://www.googleapis.com/userinfo/v2/me').json())
50
51.. _requests-oauthlib: http://requests-oauthlib.readthedocs.io/en/stable/
52.. _OAuth 2.0 Authorization Flow:
53 https://tools.ietf.org/html/rfc6749#section-1.2
54"""
55
56import json
57
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -080058import google.auth.transport.requests
59import google.oauth2.credentials
Jon Wayne Parrott3ff4d552017-02-08 14:43:38 -080060import google.oauth2.oauthlib
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -080061
62
63class Flow(object):
64 """OAuth 2.0 Authorization Flow
65
66 This class uses a :class:`requests_oauthlib.OAuth2Session` instance at
67 :attr:`oauth2session` to perform all of the OAuth 2.0 logic. This class
68 just provides convenience methods and sane defaults for doing Google's
69 particular flavors of OAuth 2.0.
70
71 Typically you'll construct an instance of this flow using
72 :meth:`from_client_secrets_file` and a `client secrets file`_ obtained
73 from the `Google API Console`_.
74
75 .. _client secrets file:
76 https://developers.google.com/identity/protocols/OAuth2WebServer
77 #creatingcred
78 .. _Google API Console:
79 https://console.developers.google.com/apis/credentials
80 """
81
Jon Wayne Parrott3ff4d552017-02-08 14:43:38 -080082 def __init__(self, oauth2session, client_type, client_config):
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -080083 """
84 Args:
Jon Wayne Parrott3ff4d552017-02-08 14:43:38 -080085 oauth2session (requests_oauthlib.OAuth2Session):
86 The OAuth 2.0 session from ``requests-oauthlib``.
87 client_type (str): The client type, either ``web`` or
88 ``installed``.
89 client_config (Mapping[str, Any]): The client
90 configuration in the Google `client secrets`_ format.
91
92 .. _client secrets:
93 https://developers.google.com/api-client-library/python/guide
94 /aaa_client_secrets
95 """
96 self.client_type = client_type
97 """str: The client type, either ``'web'`` or ``'installed'``"""
98 self.client_config = client_config[client_type]
99 """Mapping[str, Any]: The OAuth 2.0 client configuration."""
100 self.oauth2session = oauth2session
101 """requests_oauthlib.OAuth2Session: The OAuth 2.0 session."""
102
103 @classmethod
104 def from_client_config(cls, client_config, scopes, **kwargs):
105 """Creates a :class:`requests_oauthlib.OAuth2Session` from client
106 configuration loaded from a Google-format client secrets file.
107
108 Args:
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -0800109 client_config (Mapping[str, Any]): The client
110 configuration in the Google `client secrets`_ format.
111 scopes (Sequence[str]): The list of scopes to request during the
112 flow.
113 kwargs: Any additional parameters passed to
114 :class:`requests_oauthlib.OAuth2Session`
115
Jon Wayne Parrott3ff4d552017-02-08 14:43:38 -0800116 Returns:
117 Flow: The constructed Flow instance.
118
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -0800119 Raises:
120 ValueError: If the client configuration is not in the correct
121 format.
122
123 .. _client secrets:
124 https://developers.google.com/api-client-library/python/guide
125 /aaa_client_secrets
126 """
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -0800127 if 'web' in client_config:
Jon Wayne Parrott3ff4d552017-02-08 14:43:38 -0800128 client_type = 'web'
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -0800129 elif 'installed' in client_config:
Jon Wayne Parrott3ff4d552017-02-08 14:43:38 -0800130 client_type = 'installed'
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -0800131 else:
132 raise ValueError(
133 'Client secrets must be for a web or installed app.')
134
Jon Wayne Parrott3ff4d552017-02-08 14:43:38 -0800135 session, client_config = (
136 google.oauth2.oauthlib.session_from_client_config(
137 client_config, scopes, **kwargs))
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -0800138
Jon Wayne Parrott3ff4d552017-02-08 14:43:38 -0800139 return cls(session, client_type, client_config)
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -0800140
141 @classmethod
142 def from_client_secrets_file(cls, client_secrets_file, scopes, **kwargs):
143 """Creates a :class:`Flow` instance from a Google client secrets file.
144
145 Args:
146 client_secrets_file (str): The path to the client secrets .json
147 file.
148 scopes (Sequence[str]): The list of scopes to request during the
149 flow.
150 kwargs: Any additional parameters passed to
151 :class:`requests_oauthlib.OAuth2Session`
152
153 Returns:
154 Flow: The constructed Flow instance.
155 """
156 with open(client_secrets_file, 'r') as json_file:
157 client_config = json.load(json_file)
158
Jon Wayne Parrott3ff4d552017-02-08 14:43:38 -0800159 return cls.from_client_config(client_config, scopes=scopes, **kwargs)
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -0800160
161 @property
162 def redirect_uri(self):
163 """The OAuth 2.0 redirect URI. Pass-through to
164 ``self.oauth2session.redirect_uri``."""
165 return self.oauth2session.redirect_uri
166
167 @redirect_uri.setter
168 def redirect_uri(self, value):
169 self.oauth2session.redirect_uri = value
170
171 def authorization_url(self, **kwargs):
172 """Generates an authorization URL.
173
174 This is the first step in the OAuth 2.0 Authorization Flow. The user's
175 browser should be redirected to the returned URL.
176
177 This method calls
178 :meth:`requests_oauthlib.OAuth2Session.authorization_url`
179 and specifies the client configuration's authorization URI (usually
180 Google's authorization server) and specifies that "offline" access is
181 desired. This is required in order to obtain a refresh token.
182
183 Args:
184 kwargs: Additional arguments passed through to
185 :meth:`requests_oauthlib.OAuth2Session.authorization_url`
186
187 Returns:
188 Tuple[str, str]: The generated authorization URL and state. The
189 user must visit the URL to complete the flow. The state is used
190 when completing the flow to verify that the request originated
191 from your application. If your application is using a different
192 :class:`Flow` instance to obtain the token, you will need to
193 specify the ``state`` when constructing the :class:`Flow`.
194 """
195 url, state = self.oauth2session.authorization_url(
196 self.client_config['auth_uri'],
197 access_type='offline', **kwargs)
198
199 return url, state
200
201 def fetch_token(self, **kwargs):
202 """Completes the Authorization Flow and obtains an access token.
203
204 This is the final step in the OAuth 2.0 Authorization Flow. This is
205 called after the user consents.
206
207 This method calls
208 :meth:`requests_oauthlib.OAuth2Session.fetch_token`
209 and specifies the client configuration's token URI (usually Google's
210 token server).
211
212 Args:
213 kwargs: Arguments passed through to
214 :meth:`requests_oauthlib.OAuth2Session.fetch_token`. At least
215 one of ``code`` or ``authorization_response`` must be
216 specified.
217
218 Returns:
219 Mapping[str, str]: The obtained tokens. Typically, you will not use
220 return value of this function and instead and use
221 :meth:`credentials` to obtain a
222 :class:`~google.auth.credentials.Credentials` instance.
223 """
224 return self.oauth2session.fetch_token(
225 self.client_config['token_uri'],
226 client_secret=self.client_config['client_secret'],
227 **kwargs)
228
229 @property
230 def credentials(self):
231 """Returns credentials from the OAuth 2.0 session.
232
233 :meth:`fetch_token` must be called before accessing this. This method
234 constructs a :class:`google.oauth2.credentials.Credentials` class using
235 the session's token and the client config.
236
237 Returns:
238 google.oauth2.credentials.Credentials: The constructed credentials.
239
240 Raises:
241 ValueError: If there is no access token in the session.
242 """
Jon Wayne Parrott3ff4d552017-02-08 14:43:38 -0800243 return google.oauth2.oauthlib.credentials_from_session(
244 self.oauth2session, self.client_config)
Jon Wayne Parrott4382bc12017-01-10 13:35:51 -0800245
246 def authorized_session(self):
247 """Returns a :class:`requests.Session` authorized with credentials.
248
249 :meth:`fetch_token` must be called before this method. This method
250 constructs a :class:`google.auth.transport.requests.AuthorizedSession`
251 class using this flow's :attr:`credentials`.
252
253 Returns:
254 google.auth.transport.requests.AuthorizedSession: The constructed
255 session.
256 """
257 return google.auth.transport.requests.AuthorizedSession(
258 self.credentials)