| # Copyright 2015 Google Inc. All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """OAuth 2.0 utitilies for Google Developer Shell environment.""" |
| |
| import json |
| import os |
| |
| from . import client |
| |
| |
| DEVSHELL_ENV = 'DEVSHELL_CLIENT_PORT' |
| |
| |
| class Error(Exception): |
| """Errors for this module.""" |
| pass |
| |
| |
| class CommunicationError(Error): |
| """Errors for communication with the Developer Shell server.""" |
| |
| |
| class NoDevshellServer(Error): |
| """Error when no Developer Shell server can be contacted.""" |
| |
| |
| # The request for credential information to the Developer Shell client socket is |
| # always an empty PBLite-formatted JSON object, so just define it as a constant. |
| CREDENTIAL_INFO_REQUEST_JSON = '[]' |
| |
| |
| class CredentialInfoResponse(object): |
| """Credential information response from Developer Shell server. |
| |
| The credential information response from Developer Shell socket is a |
| PBLite-formatted JSON array with fields encoded by their index in the array: |
| * Index 0 - user email |
| * Index 1 - default project ID. None if the project context is not known. |
| * Index 2 - OAuth2 access token. None if there is no valid auth context. |
| """ |
| |
| def __init__(self, json_string): |
| """Initialize the response data from JSON PBLite array.""" |
| pbl = json.loads(json_string) |
| if not isinstance(pbl, list): |
| raise ValueError('Not a list: ' + str(pbl)) |
| pbl_len = len(pbl) |
| self.user_email = pbl[0] if pbl_len > 0 else None |
| self.project_id = pbl[1] if pbl_len > 1 else None |
| self.access_token = pbl[2] if pbl_len > 2 else None |
| |
| |
| def _SendRecv(): |
| """Communicate with the Developer Shell server socket.""" |
| |
| port = int(os.getenv(DEVSHELL_ENV, 0)) |
| if port == 0: |
| raise NoDevshellServer() |
| |
| import socket |
| |
| sock = socket.socket() |
| sock.connect(('localhost', port)) |
| |
| data = CREDENTIAL_INFO_REQUEST_JSON |
| msg = '%s\n%s' % (len(data), data) |
| sock.sendall(msg.encode()) |
| |
| header = sock.recv(6).decode() |
| if '\n' not in header: |
| raise CommunicationError('saw no newline in the first 6 bytes') |
| len_str, json_str = header.split('\n', 1) |
| to_read = int(len_str) - len(json_str) |
| if to_read > 0: |
| json_str += sock.recv(to_read, socket.MSG_WAITALL).decode() |
| |
| return CredentialInfoResponse(json_str) |
| |
| |
| class DevshellCredentials(client.GoogleCredentials): |
| """Credentials object for Google Developer Shell environment. |
| |
| This object will allow a Google Developer Shell session to identify its user |
| to Google and other OAuth 2.0 servers that can verify assertions. It can be |
| used for the purpose of accessing data stored under the user account. |
| |
| This credential does not require a flow to instantiate because it represents |
| a two legged flow, and therefore has all of the required information to |
| generate and refresh its own access tokens. |
| """ |
| |
| def __init__(self, user_agent=None): |
| super(DevshellCredentials, self).__init__( |
| None, # access_token, initialized below |
| None, # client_id |
| None, # client_secret |
| None, # refresh_token |
| None, # token_expiry |
| None, # token_uri |
| user_agent) |
| self._refresh(None) |
| |
| def _refresh(self, http_request): |
| self.devshell_response = _SendRecv() |
| self.access_token = self.devshell_response.access_token |
| |
| @property |
| def user_email(self): |
| return self.devshell_response.user_email |
| |
| @property |
| def project_id(self): |
| return self.devshell_response.project_id |
| |
| @classmethod |
| def from_json(cls, json_data): |
| raise NotImplementedError( |
| 'Cannot load Developer Shell credentials from JSON.') |
| |
| @property |
| def serialization_data(self): |
| raise NotImplementedError( |
| 'Cannot serialize Developer Shell credentials.') |