def __get_at_by_psm_app(self): """ Acquiring account access token using PSMApp provisioned by Oracle for each tenant. Keeping this path to remain the backward compatibility if users are using the client id and secret from Application ANDC. This path shouldn't work after IDCS hides client secret of Oracle-created Applications. This will be deprecated eventually. """ # 1. acquire IDCS AT result = self.__get_at_by_password( DefaultAccessTokenProvider._IDCS_SCOPE) if result is None: raise IllegalStateException( 'Error acquiring Identity Cloud Service access token, unable ' + 'to get metadata to proceed acquiring account access token.') # 2. look up audience, client id and secret of PSMApp auth_header = 'Bearer ' + result psm_info = self.__get_psm_app(auth_header) if psm_info is None: raise IllegalStateException( 'Error finding required metadata from Identity Cloud Service,' + ' unable to proceed acquiring account access token.') # 3. acquire PSM AT auth_header = self.__get_auth_header(psm_info.client_id, psm_info.client_secret) psm_fqs = psm_info.audience + Utils.PSM_SCOPE user_creds = self.__get_user_creds() replaced = str.format(DefaultAccessTokenProvider._RO_GRANT_FORMAT, user_creds.get_credential_alias(), psm_fqs) payload = replaced + user_creds.get_secret() return self.__get_access_token(auth_header, payload, psm_fqs)
def __add_app(self, auth, payload): # Add the custom OAuth client response = self.__request_utils.do_post_request( self.__idcs_url + Utils.APP_ENDPOINT, Utils.scim_headers(self.__host, auth), payload, self.__timeout_ms) self.__check_not_none(response, 'response of adding OAuth client') response_code = response.get_status_code() content = response.get_content() if response_code == codes.conflict: raise IllegalStateException( 'OAuth Client ' + self.__name + ' already exists. To ' + 'recreate, run with ' + OAuthClient._DELETE_FLAG + '. To ' + 'verify if existing client is configured correctly, run with ' + OAuthClient._VERIFY_FLAG) elif response_code >= codes.multiple_choices: OAuthClient.__idcs_errors(response, 'Adding custom client') app_id = 'id' oauth_id = 'name' secret = 'clientSecret' app_id_value = Utils.get_field(content, app_id) oauth_id_value = Utils.get_field(content, oauth_id) secret_value = Utils.get_field(content, secret) if (app_id_value is None or oauth_id_value is None or secret_value is None): raise IllegalStateException( str.format('Unable to find {0} or {1} or {2} in ,' + content, app_id, oauth_id, secret)) return OAuthClient.Client(app_id_value, oauth_id_value, secret_value)
def __do_verify(self, errors): self.__output('Verifying OAuth Client ' + self.__name) try: auth = 'Bearer ' + self.__get_bootstrap_token() response = self.__request_utils.do_get_request( self.__idcs_url + OAuthClient._CLIENT_EP + self.__name + '%22', Utils.scim_headers(self.__host, auth), self.__timeout_ms) self.__check_not_none(response, 'client metadata') response_code = response.get_status_code() content = response.get_content() if response_code >= codes.multiple_choices: OAuthClient.__idcs_errors( response, 'Getting client ' + self.__name) grants = Utils.get_field(content, 'allowedGrants') if grants is None: # No results in response raise IllegalStateException( 'OAuth Client ' + self.__name + ' doesn\'t exist, or the ' + 'token file is invalid, user who downloads the token ' + 'must have Identity Domain Administrator role') # Verify if client has required grants self.__verify_grants(grants, errors) # Verify if client has PSM and ANDC FQS self.__verify_scopes( Utils.get_field(content, 'allowedScopes', 'fqs'), errors) # Verify if client has ANDC role self.__verify_role( Utils.get_field(content, 'grantedAppRoles', 'display'), errors) if len(errors) > 0: return self.__output('Verification succeed') except Exception as e: self.__output('Verification failed of OAuth client ' + self.__name) raise e
def get_service_access_token(self): self._ensure_creds_provider() if self._andc_fqs is None: self._find_oauth_scopes() if self._andc_fqs is None: raise IllegalStateException( 'Unable to find service scope from OAuth Client, retry with ' + 'service entitlement id.') return self._get_at_by_password(self._andc_fqs)
def __get_psm_app(self, auth_header): """ Get PSMApp metadata from IDCS. The secret of PSMApp will be hidden by IDCS, if no secret, return an error to ask users create custom client. This will be deprecated eventually. """ # Get PSMApp metadata from IDCS. response = self.__request_utils.do_get_request( self.__idcs_url + Utils.APP_ENDPOINT + DefaultAccessTokenProvider._PSM_APP_FILTER, Utils.token_headers(self.__host, auth_header), self.__timeout_ms) if response is None: raise IllegalStateException( 'Error getting required metadata from Identity Cloud Service,' + ' unable to acquire account access token, no response') response_code = response.get_status_code() content = response.get_content() if response_code >= codes.multiple_choices: Utils.handle_idcs_errors( response, 'Getting account metadata', 'Please grant user Identity Domain Administrator or ' + 'Application Administrator role') oauth_id = 'name' audience = 'audience' secret = 'clientSecret' try: oauth_id_value = Utils.get_field(content, oauth_id) audience_value = Utils.get_field(content, audience) secret_value = Utils.get_field(content, secret) except IllegalStateException as ise: raise UnauthorizedException( 'Please grant user Identity Domain Administrator or ' + 'Application Administrator role. ' + str(ise)) if oauth_id_value is None or audience_value is None: raise IllegalStateException( 'Account metadata response contains invalid value: ' + content) if secret_value is None: raise IllegalStateException( 'Account metadata doesn\'t have a secret, unable to acquire ' + 'account access token. Must create the custom OAuth Client ' + 'first. Account metadata: ' + content) return DefaultAccessTokenProvider.PSMAppInfo(oauth_id_value, secret_value, audience_value)
def get_service_access_token(self): self.__ensure_creds_provider() if self.__andc_fqs is None: self.__find_oauth_scopes() if self.__andc_fqs is None: raise IllegalStateException( 'Unable to find service scope, OAuth client isn\'t ' + 'configured properly, run OAuthClient utility to verify and ' + 'recreate.') return self.__get_at_by_password(self.__andc_fqs)
def __get_bootstrap_token(self): # Read access token from given file with open(self.__at_file, 'r') as at_file: content = at_file.read() bootstrap_token = loads(content) field = 'app_access_token' app_access_token = bootstrap_token.get(field) if app_access_token is None: raise IllegalStateException( 'Access token file contains invalid value: ' + content) return app_access_token
def __verify_role(self, roles, errors): if roles is None: raise IllegalStateException( 'OAuth client ' + self.__name + ' doesn\'t have roles') self.__log_verbose('OAuth client allowed roles: ' + str(roles)) match = 0 for role in roles: if role == 'ANDC_FullAccessRole': match += 1 if match != 1: errors.append('Missing required role ANDC_FullAccessRole') self.__log_verbose('Role verification succeed')
def _get_access_token(self, auth_header, payload, fqs): response = self._request_utils.do_post_request( self._idcs_url + Utils.TOKEN_ENDPOINT, Utils.token_headers( self._host, auth_header), payload, self._timeout_ms) if response is None: raise IllegalStateException('Error acquiring access token with ' 'scope ' + fqs + ', no response') response_code = response.get_status_code() content = response.get_content() if response_code >= codes.multiple_choices: self._handle_token_error_response(response_code, content) return self._parse_access_token_response(content)
def __parse_access_token_response(self, response): """ A valid response from IDCS is in JSON format and must contains the field "access_token" and "expires_in". """ response = loads(response) access_token = response.get(DefaultAccessTokenProvider._AT_FIELD) if access_token is None: raise IllegalStateException( 'Access token response contains invalid value, response: ' + str(response)) self.__logutils.log_debug('Acquired access token ' + access_token) return access_token
def get_field(content, field, sub_field=None, allow_none=True): content_str = content content = loads(content) value = content.get(field) values = None if value is None: value = Utils.__get_field_recursively(content, field) if not allow_none and value is None: raise IllegalStateException(field + ' doesn\'t exist in ' + content_str) if isinstance(value, list) and sub_field is not None: values = list() for item in value: if isinstance(item, dict): values.append(item.get(sub_field)) return value if sub_field is None else values
def __get_andc_info(self, auth): # Get App ANDC metadata from IDCS response = self.__request_utils.do_get_request( self.__idcs_url + OAuthClient._ANDC_APP_EP, Utils.scim_headers(self.__host, auth), self.__timeout_ms) self.__check_not_none(response, 'getting service metadata') content = response.get_content() if response.get_status_code() >= codes.multiple_choices: OAuthClient.__idcs_errors(response, 'Getting service metadata') audience = 'audience' app_id = 'id' audience_value = Utils.get_field(content, audience) app_id_value = Utils.get_field(content, app_id) if audience_value is None or app_id_value is None: raise IllegalStateException( str.format('Unable to find {0} or {1} in ,' + content, audience, app_id)) return OAuthClient.ANDC(app_id_value, audience_value)
def handle_idcs_errors(response, action, unauthorized_msg): """ Possible error codes returned from IDCS for SCIM endpoints. Unexpected case:\n 307, 308 - redirect-related errors\n 400 - bad request, indicates code error\n 403 - request operation is not allowed\n 404 - this PSMApp doesn't exist\n 405 - method not allowed\n 409 - version mismatch\n 412 - precondition failed for update op\n 413 - request too long\n 415 - not acceptable\n 501 - this method not implemented Security failure case:\n 401 - no permission Service unavailable:\n 500 - internal server error\n 503 - service unavailable\n """ response_content = response.get_content() response_code = response.get_status_code() if response_code == codes.unauthorized: raise UnauthorizedException(action + ' is unauthorized. ' + unauthorized_msg + '. Error response: ' + response_content) elif (response_code == codes.server_error or response_code == codes.unavailable): raise RequestTimeoutException( action + ' error, expect to retry, error response: ' + response_content + ', status code: ' + str(response_code)) else: raise IllegalStateException(action + ' error, IDCS error response: ' + response_content)
def _find_oauth_scopes(self): # Find PSM and ANDC FQS from allowed scopes of OAuth client. if self._andc_fqs is not None and self._psm_fqs is not None: return creds = self._get_client_creds() oauth_id = creds.get_credential_alias() auth = self._get_auth_header(oauth_id, creds.get_secret()) try: auth = 'Bearer ' + self._get_access_token( auth, DefaultAccessTokenProvider._CLIENT_GRANT_PAYLOAD, DefaultAccessTokenProvider._IDCS_SCOPE) except InvalidAuthorizationException as iae: self._logutils.log_info( str.format('Unable to find FQS from OAuth client {0}: {1}.', oauth_id, str(iae))) return response = self._request_utils.do_get_request( self._idcs_url + Utils.APP_ENDPOINT + DefaultAccessTokenProvider._CLIENT_FILTER + '%22' + oauth_id + '%22', Utils.scim_headers(self._host, auth), self._timeout_ms) if response is None: raise IllegalStateException( 'Error getting client metadata from Identity Cloud Service, ' + 'no response.') if response.get_status_code() >= codes.multiple_choices: Utils.handle_idcs_errors( response, 'Getting client metadata', 'Please verify if the OAuth client is configured properly.') fqs_list = Utils.get_field( response.get_content(), 'allowedScopes', 'fqs') if fqs_list is None: return for fqs in fqs_list: if fqs.startswith(AccessTokenProvider.ANDC_AUD_PREFIX): self._andc_fqs = fqs elif fqs.endswith(Utils.PSM_SCOPE): self._psm_fqs = fqs
def __check_not_none(self, response, action): if response is None: raise IllegalStateException( 'Error ' + action + ' from Oracle Identity Cloud Service, ' + 'no response')