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}")
Example #3
0
    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))
Example #5
0
 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))
Example #7
0
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
Example #8
0
    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
Example #9
0
        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")