blob: 9e1141646b64711780c328542a33e28af90429f1 [file] [log] [blame]
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -07001# 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 Credentials.
16
17This module provides credentials based on OAuth 2.0 access and refresh tokens.
18These credentials usually access resources on behalf of a user (resource
19owner).
20
21Specifically, this is intended to use access tokens acquired using the
22`Authorization Code grant`_ and can refresh those tokens using a
23optional `refresh token`_.
24
25Obtaining the initial access and refresh token is outside of the scope of this
26module. Consult `rfc6749 section 4.1`_ for complete details on the
27Authorization Code grant flow.
28
29.. _Authorization Code grant: https://tools.ietf.org/html/rfc6749#section-1.3.1
30.. _refresh token: https://tools.ietf.org/html/rfc6749#section-6
31.. _rfc6749 section 4.1: https://tools.ietf.org/html/rfc6749#section-4.1
32"""
33
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -080034import io
35import json
36
37import six
38
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070039from google.auth import _helpers
40from google.auth import credentials
Thea Flowers118c0482018-05-24 13:34:07 -070041from google.auth import exceptions
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070042from google.oauth2 import _client
43
44
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -080045# The Google OAuth 2.0 token endpoint. Used for authorized user credentials.
Thea Flowersb7eb1a72018-07-31 14:25:20 -070046_GOOGLE_OAUTH2_TOKEN_ENDPOINT = 'https://oauth2.googleapis.com/token'
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -080047
48
Mahmoud Bassiounycb7b3c42017-09-20 09:01:28 -070049class Credentials(credentials.ReadOnlyScoped, credentials.Credentials):
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070050 """Credentials using OAuth 2.0 access and refresh tokens."""
51
Jon Wayne Parrott26a16372017-03-28 13:03:33 -070052 def __init__(self, token, refresh_token=None, id_token=None,
53 token_uri=None, client_id=None, client_secret=None,
54 scopes=None):
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070055 """
56 Args:
57 token (Optional(str)): The OAuth 2.0 access token. Can be None
58 if refresh information is provided.
59 refresh_token (str): The OAuth 2.0 refresh token. If specified,
60 credentials can be refreshed.
Jon Wayne Parrott26a16372017-03-28 13:03:33 -070061 id_token (str): The Open ID Connect ID Token.
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070062 token_uri (str): The OAuth 2.0 authorization server's token
63 endpoint URI. Must be specified for refresh, can be left as
64 None if the token can not be refreshed.
65 client_id (str): The OAuth 2.0 client ID. Must be specified for
66 refresh, can be left as None if the token can not be refreshed.
67 client_secret(str): The OAuth 2.0 client secret. Must be specified
68 for refresh, can be left as None if the token can not be
69 refreshed.
Eugene W. Foley49a18c42019-05-22 13:50:38 -040070 scopes (Sequence[str]): The scopes used to obtain authorization.
71 This parameter is used by :meth:`has_scopes`. OAuth 2.0
72 credentials can not request additional scopes after
73 authorization. The scopes must be derivable from the refresh
74 token if refresh information is provided (e.g. The refresh
75 token scopes are a superset of this or contain a wild card
76 scope like 'https://www.googleapis.com/auth/any-api').
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070077 """
78 super(Credentials, self).__init__()
79 self.token = token
80 self._refresh_token = refresh_token
Jon Wayne Parrott26a16372017-03-28 13:03:33 -070081 self._id_token = id_token
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -070082 self._scopes = scopes
83 self._token_uri = token_uri
84 self._client_id = client_id
85 self._client_secret = client_secret
86
87 @property
Jon Wayne Parrott2d0549a2017-03-01 09:27:16 -080088 def refresh_token(self):
89 """Optional[str]: The OAuth 2.0 refresh token."""
90 return self._refresh_token
91
92 @property
93 def token_uri(self):
94 """Optional[str]: The OAuth 2.0 authorization server's token endpoint
95 URI."""
96 return self._token_uri
97
98 @property
Jon Wayne Parrott26a16372017-03-28 13:03:33 -070099 def id_token(self):
100 """Optional[str]: The Open ID Connect ID Token.
101
102 Depending on the authorization server and the scopes requested, this
103 may be populated when credentials are obtained and updated when
104 :meth:`refresh` is called. This token is a JWT. It can be verified
105 and decoded using :func:`google.oauth2.id_token.verify_oauth2_token`.
106 """
107 return self._id_token
108
109 @property
Jon Wayne Parrott2d0549a2017-03-01 09:27:16 -0800110 def client_id(self):
111 """Optional[str]: The OAuth 2.0 client ID."""
112 return self._client_id
113
114 @property
115 def client_secret(self):
116 """Optional[str]: The OAuth 2.0 client secret."""
117 return self._client_secret
118
119 @property
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700120 def requires_scopes(self):
121 """False: OAuth 2.0 credentials have their scopes set when
122 the initial token is requested and can not be changed."""
123 return False
124
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700125 @_helpers.copy_docstring(credentials.Credentials)
126 def refresh(self, request):
Thea Flowers118c0482018-05-24 13:34:07 -0700127 if (self._refresh_token is None or
128 self._token_uri is None or
129 self._client_id is None or
130 self._client_secret is None):
131 raise exceptions.RefreshError(
132 'The credentials do not contain the necessary fields need to '
133 'refresh the access token. You must specify refresh_token, '
134 'token_uri, client_id, and client_secret.')
135
Jon Wayne Parrott26a16372017-03-28 13:03:33 -0700136 access_token, refresh_token, expiry, grant_response = (
137 _client.refresh_grant(
138 request, self._token_uri, self._refresh_token, self._client_id,
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400139 self._client_secret, self._scopes))
Jon Wayne Parrott10ec7e92016-10-17 10:46:38 -0700140
141 self.token = access_token
142 self.expiry = expiry
143 self._refresh_token = refresh_token
Jon Wayne Parrott26a16372017-03-28 13:03:33 -0700144 self._id_token = grant_response.get('id_token')
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800145
Eugene W. Foley49a18c42019-05-22 13:50:38 -0400146 if self._scopes and 'scopes' in grant_response:
147 requested_scopes = frozenset(self._scopes)
148 granted_scopes = frozenset(grant_response['scopes'].split())
149 scopes_requested_but_not_granted = (
150 requested_scopes - granted_scopes)
151 if scopes_requested_but_not_granted:
152 raise exceptions.RefreshError(
153 'Not all requested scopes were granted by the '
154 'authorization server, missing scopes {}.'.format(
155 ', '.join(scopes_requested_but_not_granted)))
156
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800157 @classmethod
158 def from_authorized_user_info(cls, info, scopes=None):
159 """Creates a Credentials instance from parsed authorized user info.
160
161 Args:
162 info (Mapping[str, str]): The authorized user info in Google
163 format.
164 scopes (Sequence[str]): Optional list of scopes to include in the
165 credentials.
166
167 Returns:
168 google.oauth2.credentials.Credentials: The constructed
169 credentials.
170
171 Raises:
172 ValueError: If the info is not in the expected format.
173 """
174 keys_needed = set(('refresh_token', 'client_id', 'client_secret'))
175 missing = keys_needed.difference(six.iterkeys(info))
176
177 if missing:
178 raise ValueError(
179 'Authorized user info was not in the expected format, missing '
180 'fields {}.'.format(', '.join(missing)))
181
Emile Caron530f5f92019-07-26 01:23:25 +0200182 return cls(
Hiranya Jayathilaka23c88f72017-12-05 09:29:59 -0800183 None, # No access token, must be refreshed.
184 refresh_token=info['refresh_token'],
185 token_uri=_GOOGLE_OAUTH2_TOKEN_ENDPOINT,
186 scopes=scopes,
187 client_id=info['client_id'],
188 client_secret=info['client_secret'])
189
190 @classmethod
191 def from_authorized_user_file(cls, filename, scopes=None):
192 """Creates a Credentials instance from an authorized user json file.
193
194 Args:
195 filename (str): The path to the authorized user json file.
196 scopes (Sequence[str]): Optional list of scopes to include in the
197 credentials.
198
199 Returns:
200 google.oauth2.credentials.Credentials: The constructed
201 credentials.
202
203 Raises:
204 ValueError: If the file is not in the expected format.
205 """
206 with io.open(filename, 'r', encoding='utf-8') as json_file:
207 data = json.load(json_file)
208 return cls.from_authorized_user_info(data, scopes)