def make_native_app_challenge(verifier=None): """ Produce a challenge and verifier for the Native App flow. The verifier is an unhashed secret, and the challenge is a hashed version of it. The challenge is sent at the start of the flow, and the secret is sent at the end, proving that the same client that started the flow is continuing it. Hashing is always done with simple SHA256. See RFC 7636 for details. :param verifier: The code verifier string used to construct the code challenge. Must be at least 43 characters long and not longer than 128 characters. Must only contain the following characters: [a-zA-Z0-9~_.-]. :type verifier: str, optional """ if verifier: if not 43 <= len(verifier) <= 128: raise GlobusSDKUsageError( "verifier must be 43-128 characters long: {}".format(len(verifier)) ) if bool(re.search(r"[^a-zA-Z0-9~_.-]", verifier)): raise GlobusSDKUsageError("verifier contained invalid characters") else: logger.info( ( "Autogenerating verifier secret. On low-entropy systems " "this may be insecure" ) ) code_verifier = verifier or base64.urlsafe_b64encode(os.urandom(32)).decode( "utf-8" ).rstrip("=") # hash it, pull out a digest hashed_verifier = hashlib.sha256(code_verifier.encode("utf-8")).digest() # urlsafe base64 encode that hash and strip the padding code_challenge = ( base64.urlsafe_b64encode(hashed_verifier).decode("utf-8").rstrip("=") ) # return the verifier and the encoded hash return code_verifier, code_challenge
def __init__(self, client_id, **kwargs): if "authorizer" in kwargs: logger.error("ArgumentError(NativeAppClient.authorizer)") raise GlobusSDKUsageError("Cannot give a NativeAppAuthClient an authorizer") AuthClient.__init__( self, client_id=client_id, authorizer=NullAuthorizer(), **kwargs ) self.logger.info(f"Finished initializing client, client_id={client_id}")
def __init__( self, auth_client: "globus_sdk.AuthClient", requested_scopes: Optional[scopes._ScopeCollectionType] = None, redirect_uri: Optional[str] = None, state: str = "_default", verifier: Optional[str] = None, refresh_tokens: bool = False, prefill_named_grant: Optional[str] = None, ): self.auth_client = auth_client # set client_id, then check for validity self.client_id = auth_client.client_id if not self.client_id: logger.error( "Invalid auth_client ID to start Native App Flow: {}".format( self.client_id ) ) raise GlobusSDKUsageError( f'Invalid value for client_id. Got "{self.client_id}"' ) # convert scopes iterable to string immediately on load # and default to the default requested scopes self.requested_scopes = scopes.MutableScope.scopes2str( requested_scopes or DEFAULT_REQUESTED_SCOPES ) # default to `/v2/web/auth-code` on whatever environment we're looking # at -- most typically it will be `https://auth.globus.org/` self.redirect_uri = redirect_uri or ( utils.slash_join(auth_client.base_url, "/v2/web/auth-code") ) # make a challenge and secret to keep # if the verifier is provided, it will just be passed back to us, and # if not, one will be generated self.verifier, self.challenge = make_native_app_challenge(verifier) # store the remaining parameters directly, with no transformation self.refresh_tokens = refresh_tokens self.state = state self.prefill_named_grant = prefill_named_grant logger.debug("Starting Native App Flow with params:") logger.debug(f"auth_client.client_id={auth_client.client_id}") logger.debug(f"redirect_uri={self.redirect_uri}") logger.debug(f"refresh_tokens={refresh_tokens}") logger.debug(f"state={state}") logger.debug(f"requested_scopes={self.requested_scopes}") logger.debug(f"verifier=<REDACTED>,challenge={self.challenge}") if prefill_named_grant is not None: logger.debug(f"prefill_named_grant={self.prefill_named_grant}")
def __init__( self, auth_client, requested_scopes=None, redirect_uri=None, state="_default", verifier=None, refresh_tokens=False, prefill_named_grant=None, ): self.auth_client = auth_client # set client_id, then check for validity self.client_id = auth_client.client_id if not self.client_id: logger.error( "Invalid auth_client ID to start Native App Flow: {}".format( self.client_id ) ) raise GlobusSDKUsageError( 'Invalid value for client_id. Got "{0}"'.format(self.client_id) ) # default to the default requested scopes self.requested_scopes = requested_scopes or DEFAULT_REQUESTED_SCOPES # convert scopes iterable to string immediately on load if not isinstance(self.requested_scopes, six.string_types): self.requested_scopes = " ".join(self.requested_scopes) # default to `/v2/web/auth-code` on whatever environment we're looking # at -- most typically it will be `https://auth.globus.org/` self.redirect_uri = redirect_uri or ( slash_join(auth_client.base_url, "/v2/web/auth-code") ) # make a challenge and secret to keep # if the verifier is provided, it will just be passed back to us, and # if not, one will be generated self.verifier, self.challenge = make_native_app_challenge(verifier) # store the remaining parameters directly, with no transformation self.refresh_tokens = refresh_tokens self.state = state self.prefill_named_grant = prefill_named_grant logger.debug("Starting Native App Flow with params:") logger.debug("auth_client.client_id={}".format(auth_client.client_id)) logger.debug("redirect_uri={}".format(self.redirect_uri)) logger.debug("refresh_tokens={}".format(refresh_tokens)) logger.debug("state={}".format(state)) logger.debug("requested_scopes={}".format(self.requested_scopes)) logger.debug("verifier=<REDACTED>,challenge={}".format(self.challenge)) if prefill_named_grant is not None: logger.debug("prefill_named_grant={}".format(self.prefill_named_grant))
def _detect_config_dir(self) -> str: if _on_windows(): appdata = os.getenv("LOCALAPPDATA") if appdata is None: raise GlobusSDKUsageError( "LOCALAPPDATA not detected in Windows environment. " "Either ensure this variable is set or pass an explicit " "config_dir to LocalGlobusConnectPersonal" ) return os.path.join(appdata, "Globus Connect") return os.path.expanduser("~/.globusonline")
def __init__(self, client_id, client_secret, **kwargs): if "authorizer" in kwargs: logger.error('ArgumentError(ConfidentialAppClient.authorizer)') raise GlobusSDKUsageError( "Cannot give a ConfidentialAppAuthClient an authorizer") AuthClient.__init__( self, client_id=client_id, authorizer=BasicAuthorizer(client_id, client_secret), **kwargs) self.logger.info('Finished initializing client, client_id={}' .format(client_id))
def get_service_url(environment, service): logger.debug( f'Service URL Lookup for "{service}" under env "{environment}"') p = _get_parser() option = service + "_service" # TODO: validate with urlparse? url = p.get(option, environment=environment) if url is None: raise GlobusSDKUsageError( ('Failed to find a url for service "{}" in environment "{}". ' "Please double-check that GLOBUS_SDK_ENVIRONMENT is set " "correctly, or not set at all").format(service, environment)) logger.debug(f'Service URL Lookup Result: "{service}" is at "{url}"') return url
def endpoint_id(self): """ :type: string The endpoint ID of the local Globus Connect Personal endpoint installation. This value is loaded whenever it is first accessed, but saved after that. Usage: >>> from globus_sdk import TransferClient, LocalGlobusConnectPersonal >>> local_ep = LocalGlobusConnectPersonal() >>> ep_id = local_ep.endpoint_id >>> tc = TransferClient(...) # needs auth details >>> for f in tc.operation_ls(ep_id): >>> print("Local file: ", f["name"]) You can also reset the value, causing it to load again on next access, with ``del local_ep.endpoint_id`` """ if self._endpoint_id is None: try: if _on_windows(): appdata = os.getenv("LOCALAPPDATA") if appdata is None: raise GlobusSDKUsageError( "LOCALAPPDATA not detected in Windows environment") fname = os.path.join(appdata, "Globus Connect\\client-id.txt") else: fname = os.path.expanduser( "~/.globusonline/lta/client-id.txt") with open(fname) as fp: self._endpoint_id = fp.read().strip() except OSError as e: # no such file or directory if e.errno == 2: pass else: raise return self._endpoint_id
def _check_has_next_page(res): """ Check that the API says there are more results available. Additionally, update the PaginatedResource.maker or PaginatedResource.offset based on the response """ # if the paging style is LAST_KEY, check has_next_page if self.paging_style == self.PAGING_STYLE_LAST_KEY: self.next_marker = res.get("last_key") self.has_next_page = res["has_next_page"] return res["has_next_page"] # if the paging style is MARKER, look at the marker if self.paging_style == self.PAGING_STYLE_MARKER: # marker may be 0, null, or absent if no more results # API docs aren't 100% clear -- looks like 0 is what we should # expect, but we'll also accept null or absent to be safe self.next_marker = res.get("next_marker") return bool(self.next_marker) # start doing the offset maths and see if we have another page to # fetch # step size is the number of results per call -- we'll catch this # "walking off the end" of the requested results afterwards self.offset += self.max_results_per_call # if it's HAS_NEXT, the check is easy, as it's explicitly part of # the response if self.paging_style == self.PAGING_STYLE_HAS_NEXT: # just return the has_next_page value return res["has_next_page"] # if paging is TOTAL oriented, check if we've reached the total if self.paging_style == self.PAGING_STYLE_TOTAL: return self.offset < res["total"] logger.error("PaginatedResource.paging_style={} is invalid".format( self.paging_style)) raise GlobusSDKUsageError( "Invalid Paging Style Given to PaginatedResource")