def test_slash_join(a, b):
    """
    slash_joins a's with and without trailing "/"
    to b's with and without leading "/"
    Confirms all have the same correct slash_join output
    """
    assert slash_join(a, b) == "a/b"
Exemple #2
0
    def func(service,
             path,
             method=responses.GET,
             adding_headers=None,
             replace=False,
             match_querystring=False,
             **kwargs):
        base_url_map = {
            "auth": "https://auth.globus.org/",
            "nexus": "https://nexus.api.globusonline.org/",
            "transfer": "https://transfer.api.globus.org/v0.10",
            "search": "https://search.api.globus.org/",
        }
        assert service in base_url_map
        base_url = base_url_map.get(service)
        full_url = slash_join(base_url, path)

        # can set it to `{}` explicitly to clear the default
        if adding_headers is None:
            adding_headers = {"Content-Type": "application/json"}

        if replace:
            responses.replace(method,
                              full_url,
                              headers=adding_headers,
                              match_querystring=match_querystring,
                              **kwargs)
        else:
            responses.add(method,
                          full_url,
                          headers=adding_headers,
                          match_querystring=match_querystring,
                          **kwargs)
def test_slash_join(a, b):
    """
    slash_joins a's with and without trailing "/"
    to b's with and without leading "/"
    Confirms all have the same correct slash_join output
    """
    assert slash_join(a, b) == "a/b"
    def get_authorize_url(self, additional_params=None):
        """
        Start a Authorization Code flow by getting the authorization URL to
        which users should be sent.

        :param additional_params: Additional parameters to include in the authorize URL.
            Primarily for internal use
        :type additional_params: dict, optional
        :rtype: ``string``

        The returned URL string is encoded to be suitable to display to users
        in a link or to copy into their browser. Users will be redirected
        either to your provided ``redirect_uri`` or to the default location,
        with the ``auth_code`` embedded in a query parameter.
        """
        authorize_base_url = slash_join(self.auth_client.base_url,
                                        "/v2/oauth2/authorize")
        logger.debug("Building authorization URI. Base URL: {}".format(
            authorize_base_url))
        logger.debug("additional_params={}".format(additional_params))

        params = {
            "client_id": self.client_id,
            "redirect_uri": self.redirect_uri,
            "scope": self.requested_scopes,
            "state": self.state,
            "response_type": "code",
            "access_type": (self.refresh_tokens and "offline") or "online",
        }
        if additional_params:
            params.update(additional_params)

        params = urlencode(params)
        return "{0}?{1}".format(authorize_base_url, params)
Exemple #5
0
def register_api_route(service,
                       path,
                       method=httpretty.GET,
                       adding_headers=None,
                       **kwargs):
    """
    Handy wrapper for adding URIs to the HTTPretty state.
    """
    assert httpretty.is_enabled()
    base_url_map = {
        "auth": "https://auth.globus.org/",
        "nexus": "https://nexus.api.globusonline.org/",
        "transfer": "https://transfer.api.globus.org/v0.10",
        "search": "https://search.api.globus.org/",
    }
    assert service in base_url_map
    base_url = base_url_map.get(service)
    full_url = slash_join(base_url, path)

    # can set it to `{}` explicitly to clear the default
    if adding_headers is None:
        adding_headers = {"Content-Type": "application/json"}

    httpretty.register_uri(method,
                           full_url,
                           adding_headers=adding_headers,
                           **kwargs)
Exemple #6
0
    def send_custom_request(self, method, path, headers=None,
                            allow_redirects=False, response_class=None,
                            retry_401=True, **kwargs):
        rheaders = dict(self._headers)
        # expand
        if headers is not None:
            rheaders.update(headers)

        # add Authorization header, or (if it's a NullAuthorizer) possibly
        # explicitly remove the Authorization header
        if self.authorizer is not None:
            self.logger.debug(
                "request will have authorization of type {}".format(
                    type(self.authorizer)
                )
            )
            self.authorizer.set_authorization_header(rheaders)

        if path.startswith('https://') or path.startswith('http://'):
            url = path
        else:
            url = slash_join(self.base_url, path)
        self.logger.debug("request will hit URL:{}".format(url))

        # because a 401 can trigger retry, we need to wrap the retry-able thing
        # in a method
        def send_request():
            try:
                return self._session.request(
                    method=method,
                    url=url,
                    headers=rheaders,
                    allow_redirects=allow_redirects,
                    verify=self._verify,
                    timeout=self._http_timeout,
                    **kwargs
                )
            except requests.RequestException as e:
                self.logger.error("NetworkError on request")
                raise exc.convert_request_exception(e)

        # initial request
        r = send_request()

        self.logger.debug("Request made to URL: {}".format(r.url))

        # potential 401 retry handling
        if r.status_code == 401 and retry_401 and self.authorizer is not None:
            self.logger.debug("request got 401, checking retry-capability")
            # note that although handle_missing_authorization returns a T/F
            # value, it may actually mutate the state of the authorizer and
            # therefore change the value set by the `set_authorization_header`
            # method
            if self.authorizer.handle_missing_authorization():
                self.logger.debug("request can be retried")
                self.authorizer.set_authorization_header(rheaders)
                r = send_request()

        return self.handle_response(r, response_class)
Exemple #7
0
 def test_slash_join(self):
     """
     slash_joins a's with and without trailing "/"
     to b's with and without leading "/"
     Confirms all have the same correct slash_join output
     """
     for a in ["a", "a/"]:
         for b in ["b", "/b"]:
             self.assertEqual(slash_join(a, b), "a/b")
Exemple #8
0
    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(
                f'Invalid value for client_id. Got "{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, str):
            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(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))
Exemple #10
0
    def publish_servable(self, model):
        """Submit a servable to DLHub

        If this servable has not been published before, it will be assigned a unique identifier.

        If it has been published before (DLHub detects if it has an identifier), then DLHub
        will update the servable to the new version.

        Args:
            model (BaseMetadataModel): Servable to be submitted
        Returns:
            (string): Task ID of this submission, used for checking for success
        """

        # Get the metadata
        metadata = model.to_dict(simplify_paths=True)

        # Mark the method used to submit the model
        metadata['dlhub']['transfer_method'] = {'POST': 'file'}

        # Validate against the servable schema
        validate_against_dlhub_schema(metadata, 'servable')

        # Wipe the fx cache so we don't keep reusing an old servable
        self.clear_funcx_cache()

        # Get the data to be submitted as a ZIP file
        fp, zip_filename = mkstemp('.zip')
        os.close(fp)
        os.unlink(zip_filename)
        try:
            model.get_zip_file(zip_filename)

            # Get the authorization headers
            headers = {}
            self.authorizer.set_authorization_header(headers)

            # Submit data to DLHub service
            with open(zip_filename, 'rb') as zf:
                reply = requests.post(slash_join(self.base_url, 'publish'),
                                      headers=headers,
                                      files={
                                          'json':
                                          ('dlhub.json', json.dumps(metadata),
                                           'application/json'),
                                          'file': ('servable.zip', zf,
                                                   'application/octet-stream')
                                      })

            # Return the task id
            if reply.status_code != 200:
                raise Exception(reply.text)
            return reply.json()['task_id']
        finally:
            os.unlink(zip_filename)
Exemple #11
0
    def search(self,
               q,
               limit=None,
               offset=None,
               query_template=None,
               index=None,
               advanced=None,
               **params):
        """
        Perform a simple ``GET`` based search.

        Does not support all of the behaviors and parameters of advanced
        searches.

        **Parameters**

          ``q`` (*string*)
            The user-query string. Required for simple searches (and most
            advanced searches).

          ``index`` (*string*)
            Optional unless ``default_index`` was not set.
            The index to query.

          ``limit`` (*int*)
            Optional. The number of results to return.

          ``offset`` (*int*)
            Optional. An offset into the total result set for paging.

          ``query_template`` (*string*)
            Optional. A query_template name as defined within the Search
            service.

          ``advanced`` (*bool*)
            Use simple query parsing vs. advanced query syntax when
            interpreting ``q``. Defaults to False.

          ``params``
            Any additional query params to pass. For internal use only.
        """
        uri = slash_join(self._base_index_uri(index), 'search')
        merge_params(params,
                     q=q,
                     limit=limit,
                     offset=offset,
                     query_template=query_template,
                     advanced=advanced)
        return self.get(uri, params=params)
Exemple #12
0
    def ingest(self, data, index=None, **params):
        """
        Perform a simple ``POST`` based ingest op.

        **Parameters**

          ``data`` (*dict*)
            A valid GIngest document to index.

          ``index`` (*string*)
            Optional unless ``default_index`` was not set.
            The search index to send data into.

          ``params``
            Any additional query params to pass. For internal use only.
        """
        uri = slash_join(self._base_index_uri(index), 'ingest')
        return self.post(uri, json_body=data, params=params)
    def get_authorize_url(self, additional_params=None):
        """
        Start a Native App flow by getting the authorization URL to which users
        should be sent.

        **Parameters**

            ``additional_params`` (*dict*)
              A ``dict`` or ``None``, which specifies additional query
              parameters to include in the authorize URL. Primarily for
              internal use

        :rtype: ``string``

        The returned URL string is encoded to be suitable to display to users
        in a link or to copy into their browser. Users will be redirected
        either to your provided ``redirect_uri`` or to the default location,
        with the ``auth_code`` embedded in a query parameter.
        """
        authorize_base_url = slash_join(
            self.auth_client.base_url, "/v2/oauth2/authorize"
        )
        logger.debug(
            "Building authorization URI. Base URL: {}".format(authorize_base_url)
        )
        logger.debug("additional_params={}".format(additional_params))

        params = {
            "client_id": self.client_id,
            "redirect_uri": self.redirect_uri,
            "scope": self.requested_scopes,
            "state": self.state,
            "response_type": "code",
            "code_challenge": self.challenge,
            "code_challenge_method": "S256",
            "access_type": (self.refresh_tokens and "offline") or "online",
        }
        if self.prefill_named_grant is not None:
            params["prefill_named_grant"] = self.prefill_named_grant
        if additional_params:
            params.update(additional_params)

        params = urlencode(params)
        return "{0}?{1}".format(authorize_base_url, params)
Exemple #14
0
    def get_authorize_url(self, additional_params=None):
        """
        Start a Native App flow by getting the authorization URL to which users
        should be sent.

        **Parameters**

            ``additional_params`` (*dict*)
              A ``dict`` or ``None``, which specifies additional query
              parameters to include in the authorize URL. Primarily for
              internal use

        :rtype: ``string``

        The returned URL string is encoded to be suitable to display to users
        in a link or to copy into their browser. Users will be redirected
        either to your provided ``redirect_uri`` or to the default location,
        with the ``auth_code`` embedded in a query parameter.
        """
        authorize_base_url = slash_join(self.auth_client.base_url,
                                        '/v2/oauth2/authorize')
        logger.debug('Building authorization URI. Base URL: {}'.format(
            authorize_base_url))
        logger.debug('additional_params={}'.format(additional_params))

        params = {
            'client_id': self.client_id,
            'redirect_uri': self.redirect_uri,
            'scope': self.requested_scopes,
            'state': self.state,
            'response_type': 'code',
            'code_challenge': self.challenge,
            'code_challenge_method': 'S256',
            'access_type': (self.refresh_tokens and 'offline') or 'online'
        }
        if self.prefill_named_grant is not None:
            params['prefill_named_grant'] = self.prefill_named_grant
        if additional_params:
            params.update(additional_params)

        params = urlencode(params)
        return '{0}?{1}'.format(authorize_base_url, params)
Exemple #15
0
    def structured_search(self, data, index=None, **params):
        """
        Perform a structured, ``POST``-based, search.

        **Parameters**

          ``data`` (*dict*)
            A valid GSearchRequest document to execute.

          ``index`` (*string*)
            Optional unless ``default_index`` was not set.
            The index to query.

          ``advanced`` (*bool*)
            Use simple query parsing vs. advanced query syntax when
            interpreting the query string. Defaults to False.

          ``params``
            Any additional query params to pass. For internal use only.
        """
        uri = slash_join(self._base_index_uri(index), 'search')
        return self.post(uri, json_body=data, params=params)
Exemple #16
0
def register_api_route(
    service, path, method=httpretty.GET, adding_headers=None, **kwargs
):
    """
    Handy wrapper for adding URIs to the HTTPretty state.
    """
    assert httpretty.is_enabled()
    base_url_map = {
        "auth": "https://auth.globus.org/",
        "nexus": "https://nexus.api.globusonline.org/",
        "transfer": "https://transfer.api.globus.org/v0.10",
        "search": "https://search.api.globus.org/",
    }
    assert service in base_url_map
    base_url = base_url_map.get(service)
    full_url = slash_join(base_url, path)

    # can set it to `{}` explicitly to clear the default
    if adding_headers is None:
        adding_headers = {"Content-Type": "application/json"}

    httpretty.register_uri(method, full_url, adding_headers=adding_headers, **kwargs)
    def __init__(self, auth_client, requested_scopes=None,
                 redirect_uri=None, state='_default', verifier=None,
                 refresh_tokens=False):
        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 ValueError(
                '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

        # 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

        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))
Exemple #18
0
    def get_authorize_url(self, additional_params=None):
        """
        Start a Native App flow by getting the authorization URL to which users
        should be sent.

        :param additional_params: Additional query parameters to include in the
            authorize URL. Primarily for internal use
        :type additional_params: dict, optional
        :rtype: ``string``

        The returned URL string is encoded to be suitable to display to users
        in a link or to copy into their browser. Users will be redirected
        either to your provided ``redirect_uri`` or to the default location,
        with the ``auth_code`` embedded in a query parameter.
        """
        authorize_base_url = slash_join(
            self.auth_client.base_url, "/v2/oauth2/authorize"
        )
        logger.debug(f"Building authorization URI. Base URL: {authorize_base_url}")
        logger.debug(f"additional_params={additional_params}")

        params = {
            "client_id": self.client_id,
            "redirect_uri": self.redirect_uri,
            "scope": self.requested_scopes,
            "state": self.state,
            "response_type": "code",
            "code_challenge": self.challenge,
            "code_challenge_method": "S256",
            "access_type": (self.refresh_tokens and "offline") or "online",
        }
        if self.prefill_named_grant is not None:
            params["prefill_named_grant"] = self.prefill_named_grant
        if additional_params:
            params.update(additional_params)

        params = urllib.parse.urlencode(params)
        return f"{authorize_base_url}?{params}"
Exemple #19
0
 def remove(self, subject, index=None, **params):
     uri = slash_join(self._base_index_uri(index), "subject")
     params["subject"] = subject
     return self.delete(uri, params=params)