1# Copyright 2015 Google Inc. All Rights Reserved. 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 utitilies for Google Developer Shell environment.""" 16 17import json 18import os 19 20from oauth2client import client 21 22 23DEVSHELL_ENV = 'DEVSHELL_CLIENT_PORT' 24 25 26class Error(Exception): 27 """Errors for this module.""" 28 pass 29 30 31class CommunicationError(Error): 32 """Errors for communication with the Developer Shell server.""" 33 34 35class NoDevshellServer(Error): 36 """Error when no Developer Shell server can be contacted.""" 37 38 39# The request for credential information to the Developer Shell client socket is 40# always an empty PBLite-formatted JSON object, so just define it as a constant. 41CREDENTIAL_INFO_REQUEST_JSON = '[]' 42 43 44class CredentialInfoResponse(object): 45 """Credential information response from Developer Shell server. 46 47 The credential information response from Developer Shell socket is a 48 PBLite-formatted JSON array with fields encoded by their index in the array: 49 * Index 0 - user email 50 * Index 1 - default project ID. None if the project context is not known. 51 * Index 2 - OAuth2 access token. None if there is no valid auth context. 52 """ 53 54 def __init__(self, json_string): 55 """Initialize the response data from JSON PBLite array.""" 56 pbl = json.loads(json_string) 57 if not isinstance(pbl, list): 58 raise ValueError('Not a list: ' + str(pbl)) 59 pbl_len = len(pbl) 60 self.user_email = pbl[0] if pbl_len > 0 else None 61 self.project_id = pbl[1] if pbl_len > 1 else None 62 self.access_token = pbl[2] if pbl_len > 2 else None 63 64 65def _SendRecv(): 66 """Communicate with the Developer Shell server socket.""" 67 68 port = int(os.getenv(DEVSHELL_ENV, 0)) 69 if port == 0: 70 raise NoDevshellServer() 71 72 import socket 73 74 sock = socket.socket() 75 sock.connect(('localhost', port)) 76 77 data = CREDENTIAL_INFO_REQUEST_JSON 78 msg = '%s\n%s' % (len(data), data) 79 sock.sendall(msg.encode()) 80 81 header = sock.recv(6).decode() 82 if '\n' not in header: 83 raise CommunicationError('saw no newline in the first 6 bytes') 84 len_str, json_str = header.split('\n', 1) 85 to_read = int(len_str) - len(json_str) 86 if to_read > 0: 87 json_str += sock.recv(to_read, socket.MSG_WAITALL).decode() 88 89 return CredentialInfoResponse(json_str) 90 91 92class DevshellCredentials(client.GoogleCredentials): 93 """Credentials object for Google Developer Shell environment. 94 95 This object will allow a Google Developer Shell session to identify its user 96 to Google and other OAuth 2.0 servers that can verify assertions. It can be 97 used for the purpose of accessing data stored under the user account. 98 99 This credential does not require a flow to instantiate because it represents 100 a two legged flow, and therefore has all of the required information to 101 generate and refresh its own access tokens. 102 """ 103 104 def __init__(self, user_agent=None): 105 super(DevshellCredentials, self).__init__( 106 None, # access_token, initialized below 107 None, # client_id 108 None, # client_secret 109 None, # refresh_token 110 None, # token_expiry 111 None, # token_uri 112 user_agent) 113 self._refresh(None) 114 115 def _refresh(self, http_request): 116 self.devshell_response = _SendRecv() 117 self.access_token = self.devshell_response.access_token 118 119 @property 120 def user_email(self): 121 return self.devshell_response.user_email 122 123 @property 124 def project_id(self): 125 return self.devshell_response.project_id 126 127 @classmethod 128 def from_json(cls, json_data): 129 raise NotImplementedError( 130 'Cannot load Developer Shell credentials from JSON.') 131 132 @property 133 def serialization_data(self): 134 raise NotImplementedError( 135 'Cannot serialize Developer Shell credentials.') 136 137