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