Add downscoping to ouath2 credentials (#309)


diff --git a/google/oauth2/_client.py b/google/oauth2/_client.py
index dc35be2..5121a32 100644
--- a/google/oauth2/_client.py
+++ b/google/oauth2/_client.py
@@ -201,7 +201,8 @@
     return id_token, expiry, response_data
 
 
-def refresh_grant(request, token_uri, refresh_token, client_id, client_secret):
+def refresh_grant(request, token_uri, refresh_token, client_id, client_secret,
+                  scopes=None):
     """Implements the OAuth 2.0 refresh token grant.
 
     For more details, see `rfc678 section 6`_.
@@ -215,6 +216,10 @@
             token.
         client_id (str): The OAuth 2.0 application's client ID.
         client_secret (str): The Oauth 2.0 appliaction's client secret.
+        scopes (Optional(Sequence[str])): Scopes to request. If present, all
+            scopes must be authorized for the refresh token. Useful if refresh
+            token has a wild card scope (e.g.
+            'https://www.googleapis.com/auth/any-api').
 
     Returns:
         Tuple[str, Optional[str], Optional[datetime], Mapping[str, str]]: The
@@ -233,6 +238,8 @@
         'client_secret': client_secret,
         'refresh_token': refresh_token,
     }
+    if scopes:
+        body['scope'] = ' '.join(scopes)
 
     response_data = _token_endpoint_request(request, token_uri, body)
 
diff --git a/google/oauth2/credentials.py b/google/oauth2/credentials.py
index 4cb909c..b56e314 100644
--- a/google/oauth2/credentials.py
+++ b/google/oauth2/credentials.py
@@ -67,10 +67,13 @@
             client_secret(str): The OAuth 2.0 client secret. Must be specified
                 for refresh, can be left as None if the token can not be
                 refreshed.
-            scopes (Sequence[str]): The scopes that were originally used
-                to obtain authorization. This is a purely informative parameter
-                that can be used by :meth:`has_scopes`. OAuth 2.0 credentials
-                can not request additional scopes after authorization.
+            scopes (Sequence[str]): The scopes used to obtain authorization.
+                This parameter is used by :meth:`has_scopes`. OAuth 2.0
+                credentials can not request additional scopes after
+                authorization. The scopes must be derivable from the refresh
+                token if refresh information is provided (e.g. The refresh
+                token scopes are a superset of this or contain a wild card
+                scope like 'https://www.googleapis.com/auth/any-api').
         """
         super(Credentials, self).__init__()
         self.token = token
@@ -133,13 +136,24 @@
         access_token, refresh_token, expiry, grant_response = (
             _client.refresh_grant(
                 request, self._token_uri, self._refresh_token, self._client_id,
-                self._client_secret))
+                self._client_secret, self._scopes))
 
         self.token = access_token
         self.expiry = expiry
         self._refresh_token = refresh_token
         self._id_token = grant_response.get('id_token')
 
+        if self._scopes and 'scopes' in grant_response:
+            requested_scopes = frozenset(self._scopes)
+            granted_scopes = frozenset(grant_response['scopes'].split())
+            scopes_requested_but_not_granted = (
+                requested_scopes - granted_scopes)
+            if scopes_requested_but_not_granted:
+                raise exceptions.RefreshError(
+                    'Not all requested scopes were granted by the '
+                    'authorization server, missing scopes {}.'.format(
+                        ', '.join(scopes_requested_but_not_granted)))
+
     @classmethod
     def from_authorized_user_info(cls, info, scopes=None):
         """Creates a Credentials instance from parsed authorized user info.