def create_session(self): """ Initializes a session with the testing service. """ url = '{service_root_url}/sessions'.format( service_root_url=SERVICE_ROOT_URL) data = {'language': SERVICE_LANGUAGE} response = requests.post(url, data=json.dumps(data), headers={ 'Content-Type': 'application/json', 'Accept': 'text/plain' }) assert response.status_code == 200, 'Failed to create session. Response from testing service: {}'.format( response.content) session_id_response = response.content.decode('UTF-8') try: int(session_id_response) except ValueError as e: print( 'Failed to create session, did not receive valid session ID from testing service: {}' .format(session_id_response)) raise e self.session_id = session_id_response print('Created session: {}'.format(self.session_id)) return self.session_id
def refresh(ctx): client_config = cli_util.build_config(ctx.obj) security_token_file = client_config.get('security_token_file') if not security_token_file: click.echo( "ERROR: Cannot refresh a profile that does not have a value for 'security_token_file'", file=sys.stderr) sys.exit(1) expanded_security_token_location = os.path.expanduser(security_token_file) if not os.path.exists(expanded_security_token_location): click.echo( "ERROR: Cannot refresh token, 'security_token_file' does not exist: {}" .format(expanded_security_token_location)) sys.exit(1) with open(expanded_security_token_location, 'r') as security_token_file: token = security_token_file.read() try: private_key = oci.signer.load_private_key_from_file( client_config.get('key_file'), client_config.get('pass_phrase')) except oci.exceptions.MissingPrivateKeyPassphrase: client_config['pass_phrase'] = cli_util.prompt_for_passphrase() private_key = oci.signer.load_private_key_from_file( client_config.get('key_file'), client_config.get('pass_phrase')) auth = oci.auth.signers.SecurityTokenSigner(token, private_key) refresh_url = "{endpoint}/v1/authentication/refresh".format( endpoint=oci.regions.endpoint_for("auth", client_config.get('region'))) click.echo("Attempting to refresh token from {refresh_url}".format( refresh_url=refresh_url), file=sys.stderr) response = requests.post(refresh_url, headers={'content-type': 'application/json'}, data=json.dumps({'currentToken': token}), auth=auth) if response.status_code == 200: refreshed_token = json.loads(response.content.decode('UTF-8'))['token'] with open(expanded_security_token_location, 'w') as security_token_file: security_token_file.write(refreshed_token) cli_util.apply_user_only_access_permissions( expanded_security_token_location) click.echo("Successfully refreshed token", file=sys.stderr) elif response.status_code == 401: click.echo( "Your session is no longer valid and cannot be refreshed. Please use 'oci session authenticate' to create a new session.", file=sys.stderr) sys.exit(1) else: click.echo("ERROR: Failed to refresh sesison. Error: {}".format( str(response.content.decode('UTF-8')))) sys.exit(1)
def refresh(ctx): ctx.obj['config_file'] = cli_setup.DEFAULT_CONFIG_LOCATION client_config = cli_util.build_config(ctx.obj) security_token_file = client_config.get('security_token_file') if not security_token_file: click.echo("ERROR: Cannot refresh a profile that does not have a value for 'security_token_file'", file=sys.stderr) sys.exit(1) expanded_security_token_location = os.path.expanduser(security_token_file) if not os.path.exists(expanded_security_token_location): click.echo("ERROR: Cannot refresh token, 'security_token_file' does not exist: {}".format(expanded_security_token_location)) sys.exit(1) with open(expanded_security_token_location, 'r') as security_token_file: token = security_token_file.read() auth = oci.auth.signers.SecurityTokenSigner(token, oci.signer.load_private_key_from_file(client_config.get('key_file'), client_config.get('pass_phrase'))) refresh_url = "https://auth.{region}.oraclecloud.com/v1/authentication/refresh".format(region=client_config.get('region')) click.echo("Attempting to refresh token from {refresh_url}".format(refresh_url=refresh_url), file=sys.stderr) response = requests.post( refresh_url, headers={ 'content-type': 'application/json' }, data=json.dumps({ 'currentToken': token }), auth=auth ) if response.status_code == 200: refreshed_token = json.loads(response.content.decode('UTF-8'))['token'] with open(expanded_security_token_location, 'w') as security_token_file: security_token_file.write(refreshed_token) click.echo("Successfully refreshed token", file=sys.stderr) elif response.status_code == 401: click.echo("Your session is no longer valid and cannot be refreshed. Please use 'oci session authenticate' to create a new session.", file=sys.stderr) sys.exit(1) else: click.echo("ERROR: Failed to refresh sesison. Error: {}".format(str(response.content.decode('UTF-8')))) sys.exit(1)
def validate_result(self, service_name, api_name, container_id, request, result, data_field_name, is_delete_operation): """ Calls the testing service to validate a CLI command result. :param str service_name The name of the service to request input parameters for. The testing service uses the following template to construct the Java SDK request object: String.format("com.oracle.bmc.%s.requests.%sRequest", serviceName, apiName); :param str api_name The name of the API to request input parameters for. The testing service uses the following template to construct the Java SDK request object: String.format("com.oracle.bmc.%s.requests.%sRequest", serviceName, apiName); :param str container_id: The ID of the current container. :param str request: The json content of the request object. :param Result result: The result object from click.testing.CliRunner. :param str data_field_name: The CLI returns the main resource under the field name 'data' but we need to convert it to the name used in the Java SDK (e.g. for GetGroup data -> group) :return: None """ # standardize service name to convention for Java SDK model namespaces (all lower case one word) java_package_name = service_name.replace('_', '').lower() request_class = 'com.oracle.bmc.{java_package_name}.requests.{api_name}Request'.format( java_package_name=java_package_name, api_name=api_name) response_class = 'com.oracle.bmc.{java_package_name}.responses.{api_name}Response'.format( java_package_name=java_package_name, api_name=api_name) success_url = '{service_root_url}/response'.format( service_root_url=SERVICE_ROOT_URL) error_url = '{service_root_url}/error'.format( service_root_url=SERVICE_ROOT_URL) data = { 'containerId': container_id, 'requestClass': request_class, 'requestJson': json.dumps(request), } params = {"sessionId": self.session_id, "lang": SERVICE_LANGUAGE} if result.exit_code == 0: # remove known warnings from output that would break JSON parsing output = result.output.replace(LIST_NOT_ALL_ITEMS_RETURNED_WARNING, '') # list and delete CLI commands can return an empty string so specially handle those cases if len(output) == 0: if api_name.lower().startswith('list'): normalized_response_json = {data_field_name: []} elif is_delete_operation: normalized_response_json = {} else: raise ValueError('CLI output was empty') else: try: normalized_response_json = json.loads(output) except ValueError as e: print( 'Failed to parse CLI output as valid JSON. Output: {}'. format(output)) raise e # convert the CLI response (keys with '-') to camelcase so that testing service can validate complex_parameter_type = { 'module': service_name, 'class': data_field_name[0].upper() + data_field_name[1:] } # remove 'data' temporarily to camelize all other fields response_data = normalized_response_json.pop('data', None) # camelize the rest of the response fields (data is removed so there is no corresponding complex type definition for the top level object) normalized_response_json = make_dict_keys_camel_case( normalized_response_json) # camelize response['data'] independently and specify the corresponding complex_parameter_type # this will correctly skip camelizing keys of dictionaries nested within the response such as 'metadata' or 'defined-tags' if response_data: normalized_response_json[ data_field_name] = make_dict_keys_camel_case( response_data, complex_parameter_type=complex_parameter_type) # right now we only return the opc-request-id for errors but the validator looks for it # tempporarily bypassing this check by hardcoding one here normalized_response_json[ 'opcRequestId'] = use_or_generate_request_id(None) data['responseJson'] = json.dumps(normalized_response_json) data['responseClass'] = response_class response = requests.post(success_url, params=params, data=json.dumps(data)) assert response.status_code == 200, response.content return response.content.decode("UTF-8") else: error = json.loads(result.output.replace('ServiceError:', '')) error['statusCode'] = error.pop('status') error['opcRequestId'] = error.pop('opc-request-id') data['errorJson'] = json.dumps(error) print('errorToValidate: {}'.format(json.dumps(data, indent=2))) response = requests.post(error_url, params=params, data=json.dumps(data)) assert response.status_code == 200, response.content return response.content.decode("UTF-8")
def validate_result(self, service_name, api_name, container_id, request, result, data_field_name, is_delete_operation, is_binary_operation=False, filename=None, is_list_operation=False): """ Calls the testing service to validate a CLI command result. :param str service_name The name of the service to request input parameters for. The testing service uses the following template to construct the Java SDK request object: String.format("com.oracle.bmc.%s.requests.%sRequest", serviceName, apiName); :param str api_name The name of the API to request input parameters for. The testing service uses the following template to construct the Java SDK request object: String.format("com.oracle.bmc.%s.requests.%sRequest", serviceName, apiName); :param str container_id: The ID of the current container. :param str request: The json content of the request object. :param Result result: The result object from click.testing.CliRunner. :param str data_field_name: The CLI returns the main resource under the field name 'data' but we need to convert it to the name used in the Java SDK (e.g. for GetGroup data -> group) :param is_delete_operation: Field to indicate if the operation is a delete operation :param is_binary_operation: Field to indicate if the operation produces binary output (default: False) :param filename: Field for the file to read from, for binary data (default: None) :return: None """ # standardize service name to convention for Java SDK model namespaces (all lower case one word) java_package_name = service_name.replace('_', '').lower() request_class = 'com.oracle.bmc.{java_package_name}.requests.{api_name}Request'.format( java_package_name=java_package_name, api_name=api_name) response_class = 'com.oracle.bmc.{java_package_name}.responses.{api_name}Response'.format( java_package_name=java_package_name, api_name=api_name) success_url = '{service_root_url}/response'.format( service_root_url=SERVICE_ROOT_URL) error_url = '{service_root_url}/error'.format( service_root_url=SERVICE_ROOT_URL) data = { 'containerId': container_id, 'requestClass': request_class, 'requestJson': json.dumps(request), } params = {"sessionId": self.session_id, "lang": SERVICE_LANGUAGE} if is_list_operation: data['responseJson'] = [] for result_item in result: # remove known warnings from output that would break JSON parsing output = result_item.output.replace( LIST_NOT_ALL_ITEMS_RETURNED_WARNING, '') if len(output) == 0: normalized_response_json = {data_field_name: []} else: normalized_response_json = self.process_output( output, service_name, data_field_name) if "opcTotalItems" in normalized_response_json and int( normalized_response_json["opcTotalItems"]) == 0: normalized_response_json = {data_field_name: []} normalized_response_json[ 'opcRequestId'] = use_or_generate_request_id(None) data['responseJson'].append(normalized_response_json) data['responseJson'] = json.dumps(data['responseJson']) data['listResponse'] = True data['responseClass'] = response_class response = requests.post(success_url, params=params, data=json.dumps(data)) assert response.status_code == 200, response.content return response.content.decode("UTF-8") if result.exit_code == 0: # remove known warnings from output that would break JSON parsing output = result.output.replace(LIST_NOT_ALL_ITEMS_RETURNED_WARNING, '') # list and delete CLI commands can return an empty string so specially handle those cases if is_binary_operation: with open(filename, 'rb') as f: content = f.read() normalized_response_json = { "inputStream": str(base64.b64encode(content).decode('utf-8')), "contentLength": len(content) } elif len(output) == 0: if api_name.lower().startswith('list'): normalized_response_json = {data_field_name: []} else: normalized_response_json = {} else: normalized_response_json = self.process_output( output, service_name, data_field_name) normalized_response_json[ 'opcRequestId'] = use_or_generate_request_id(None) # right now we only return the opc-request-id for errors but the validator looks for it # temporarily bypassing this check by hardcoding one here data['responseJson'] = json.dumps(normalized_response_json) data['responseClass'] = response_class response = requests.post(success_url, params=params, data=json.dumps(data)) assert response.status_code == 200, response.content return response.content.decode("UTF-8") else: error = json.loads(result.output.replace('ServiceError:', '')) error['statusCode'] = error.pop('status') error['opcRequestId'] = error.pop('opc-request-id') data['errorJson'] = json.dumps(error) print('errorToValidate: {}'.format(json.dumps(data, indent=2))) response = requests.post(error_url, params=params, data=json.dumps(data)) assert response.status_code == 200, response.content return response.content.decode("UTF-8")
def _get_security_token_from_auth_service(self): request_payload = { 'certificate': auth_utils.sanitize_certificate_string( self.leaf_certificate_retriever.get_certificate_raw()), 'publicKey': auth_utils.sanitize_certificate_string( self.session_key_supplier.get_key_pair() ['public'].public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)) } if self.intermediate_certificate_retrievers: retrieved_certs = [] for retriever in self.intermediate_certificate_retrievers: retrieved_certs.append( auth_utils.sanitize_certificate_string( retriever.get_certificate_raw())) request_payload['intermediateCertificates'] = retrieved_certs fingerprint = crypto.load_certificate( crypto.FILETYPE_PEM, self.leaf_certificate_retriever.get_certificate_raw()).digest( 'sha1').decode('utf-8') signer = AuthTokenRequestSigner(self.tenancy_id, fingerprint, self.leaf_certificate_retriever) if self.cert_bundle_verify: response = requests.post(self.federation_endpoint, json=request_payload, auth=signer, verify=self.cert_bundle_verify) else: response = requests.post(self.federation_endpoint, json=request_payload, auth=signer) parsed_response = None try: parsed_response = response.json() except ValueError: error_text = 'Unable to parse response from auth service ({}): {}'.format( self.federation_endpoint, response.text) # If the response was a 2xx but unparseable, raise it straight away because it implies a potential service issue. If # we have a non-2xx but it is not parseable that is a more ambiguous scenario (e.g. could have been an issue with a # proxy or LB and those generally won't emit a JSON response) so throw it out a ServiceError so it can fall into # retry logic (depending on the error code) if response.ok: raise RuntimeError(error_text) else: raise oci.exceptions.ServiceError(response.status_code, response.reason, response.headers, error_text) if not response.ok: raise oci.exceptions.ServiceError(response.status_code, parsed_response.get('code'), response.headers, parsed_response.get('message')) else: if 'token' in parsed_response: self.security_token = SecurityTokenContainer( self.session_key_supplier, response.json()['token']) else: raise RuntimeError( 'Could not find token in response from auth service ({}): {}' .format(self.federation_endpoint, parsed_response))