def login_user(
        self,
        sudo_id: int,
        transport_options: Optional[transport.TransportOptions] = None,
    ) -> None:
        """Authenticate using settings credentials and sudo as sudo_id.

        Make API calls as if authenticated as sudo_id. The sudo_id
        token is automatically renewed when it expires. In order to
        subsequently login_user() as another user you must first logout()
        """
        if self._sudo_id is None:
            self._sudo_id = sudo_id
            try:
                self._login_sudo(transport_options or {})
            except error.SDKError:
                self._sudo_id = None
                raise

        else:
            if self._sudo_id != sudo_id:
                raise error.SDKError(
                    f"Another user ({self._sudo_id}) "
                    "is already logged in. Log them out first."
                )
            elif not self.is_sudo_authenticated:
                self._login_sudo(transport_options or {})
Пример #2
0
    def _login(self) -> None:
        client_id = self.settings.read_config().get("client_id")
        client_secret = self.settings.read_config().get("client_secret")
        if not (client_id and client_secret):
            raise error.SDKError("Required auth credentials not found.")

        serialized = urllib.parse.urlencode({
            "client_id":
            cast(str, client_id),
            "client_secret":
            cast(str, client_secret),
        }).encode("utf-8")

        response = self._ok(
            self.transport.request(
                transport.HttpMethod.POST,
                f"{self.settings.base_url}/api/{self.version}/login",
                body=serialized,
                headers={"Content-Type": "application/x-www-form-urlencoded"},
            ))

        # ignore type: mypy bug doesn't recognized kwarg `structure` to partial func
        access_token = self.deserialize(
            data=response, structure=self.token_model)  # type: ignore
        assert isinstance(access_token, token_model_isinstances)
        self.token = auth_token.AuthToken(access_token)
Пример #3
0
    def _login_admin(self) -> None:
        config_data = self.settings.read_ini(self.settings._filename,
                                             self.settings._section)
        client_id = os.getenv(f"{versions.environment_prefix}_CLIENT_ID"
                              ) or config_data.get("client_id")
        client_secret = os.getenv(
            f"{versions.environment_prefix}_CLIENT_SECRET") or config_data.get(
                "client_secret")

        if not (client_id and client_secret):
            raise error.SDKError("Required auth credentials not found.")

        serialized = urllib.parse.urlencode({
            "client_id":
            cast(str, client_id),
            "client_secret":
            cast(str, client_secret),
        }).encode("utf-8")

        response = self._ok(
            self.transport.request(
                transport.HttpMethod.POST,
                "/login",
                body=serialized,
                headers={"Content-Type": "application/x-www-form-urlencoded"},
            ))

        access_token = self.deserialize(response, models.AccessToken)
        assert isinstance(access_token, models.AccessToken)
        self.admin_token = auth_token.AuthToken(access_token)
Пример #4
0
    def __init__(
        self,
        *,
        settings: api_settings.PApiSettings,
        transport: transport.Transport,
        deserialize: serialize.TDeserialize,
        serialize: serialize.TSerialize,
        crypto: CryptoHash,
        version: str,
    ):
        super().__init__(settings, transport, deserialize, version)
        self.crypto = crypto
        self.serialize = serialize
        config_data = self.settings.read_config()
        for required in ["client_id", "redirect_uri", "looker_url"]:
            if required not in config_data:
                raise error.SDKError(
                    f"Missing required configuration value {required}")

        # would have prefered using setattr(self, required, ...) in loop above
        # but mypy can't follow it
        self.client_id = config_data["client_id"]
        self.redirect_uri = config_data["redirect_uri"]
        self.looker_url = config_data["looker_url"]
        self.code_verifier = ""
Пример #5
0
    def test_scrape_dashboard_should_raise_sdk_error_on_failure(self):
        sdk = self.__scraper.__dict__['_MetadataScraper__sdk']
        sdk.dashboard.side_effect = error.SDKError('SDK error')

        self.assertRaises(error.SDKError, self.__scraper.scrape_dashboard,
                          'dashboard-id')
        sdk.dashboard.assert_called_once()
        sdk.dashboard.assert_called_with(dashboard_id='dashboard-id')
Пример #6
0
def test_write_connections_new(mocker):
    conn_list = [models.WriteDBConnection(name="Taco")]

    mocker.patch.object(sdk,
                        "connection",
                        side_effect=error.SDKError("mocked error"))
    mocker.patch.object(sdk, "create_connection")

    deploy_connections.write_connections(conn_list, sdk)
    sdk.create_connection.assert_called_once_with(conn_list[0])
Пример #7
0
    def test_scrape_model_explore_should_raise_sdk_error_on_failure(self):
        sdk = self.__scraper.__dict__['_MetadataScraper__sdk']
        sdk.lookml_model_explore.side_effect = error.SDKError('SDK error')

        self.assertRaises(error.SDKError,
                          self.__scraper.scrape_lookml_model_explore,
                          'test-model', 'test-view')
        sdk.lookml_model_explore.assert_called_once()
        sdk.lookml_model_explore.assert_called_with(
            lookml_model_name='test-model', explore_name='test-view')
Пример #8
0
 def _return(self, response: transport.Response, structure: TStructure) -> TReturn:
     if not response.ok:
         raise error.SDKError(response.value)
     ret: TReturn
     if structure is None:
         ret = None
     elif structure is str:
         ret = response.value
     else:
         ret = self.deserialize(response.value, structure)
     return ret
Пример #9
0
def test_write_connections_update_pw_new(mocker):
    conn_list = [models.WriteDBConnection(name="Taco")]
    conf = {"Taco": "Cat"}

    mocker.patch.object(sdk,
                        "connection",
                        side_effect=error.SDKError("mocked error"))
    mocker.patch.object(sdk, "create_connection")

    deploy_connections.write_connections(conn_list, sdk, conf)
    sdk.create_connection.assert_called_once_with(
        models.WriteDBConnection(name="Taco", password="******"))
Пример #10
0
    def _request_token(
        self, grant_type: Union[AuthCodeGrantTypeParams, RefreshTokenGrantTypeParams]
    ) -> Union[models31.AccessToken, models40.AccessToken]:
        response = self.transport.request(
            transport.HttpMethod.POST,
            urllib.parse.urljoin(self.settings.base_url, "/api/token"),
            body=self.serialize(grant_type),
        )
        if not response.ok:
            raise error.SDKError(response.value.decode(encoding=response.encoding))

        # ignore type: mypy bug doesn't recognized kwarg `structure` to partial func
        return self.deserialize(
            data=response.value, structure=self.token_model
        )  # type: ignore
Пример #11
0
 def __init__(
     self,
     settings: api_settings.PApiSettings,
     transport: transport.Transport,
     deserialize: serialize.TDeserialize,
 ):
     if not settings.is_configured():
         raise error.SDKError(
             "Missing required configuration values like base_url and api_version."
         )
     self.settings = settings
     self.user_token: auth_token.AuthToken = auth_token.AuthToken()
     self.admin_token: auth_token.AuthToken = auth_token.AuthToken()
     self._sudo_id: Optional[int] = None
     self.transport = transport
     self.deserialize = deserialize
Пример #12
0
 def _return(self, response: transport.Response, structure: TStructure) -> TReturn:
     encoding = response.encoding
     if not response.ok:
         raise error.SDKError(response.value.decode(encoding=encoding))
     ret: TReturn
     if structure is None:
         ret = None
     elif response.response_mode == transport.ResponseMode.BINARY:
         ret = response.value
     else:
         value = response.value.decode(encoding=encoding)
         if structure is Union[str, bytes] or structure is str:  # type: ignore
             ret = value
         else:
             ret = self.deserialize(value, structure)
     return ret
Пример #13
0
 def _return(self, response: transport.Response,
             structure: TStructure) -> TReturn:
     encoding = response.encoding
     if not response.ok:
         raise error.SDKError(response.value.decode(encoding=encoding))
     ret: TReturn
     if structure is None:
         ret = None
     elif response.response_mode == transport.ResponseMode.BINARY:
         ret = response.value
     else:
         value = response.value.decode(encoding=encoding)
         if structure is Union[str,
                               bytes] or structure is str:  # type: ignore
             ret = value
         else:
             # ignore type: mypy bug doesn't recognized kwarg
             # `structure` to partial func
             ret = self.deserialize(data=value,
                                    structure=structure)  # type: ignore
     return ret
Пример #14
0
    def configure(cls,
                  filename: str = "looker.ini",
                  section: Optional[str] = None) -> "ApiSettings":
        """Configure using a config file and/or environment variables.

        Environment variables will override config file settings. Neither
        is necessary but some combination must supply the minimum to
        instantiate ApiSettings.

        ENV variables map like this:
            <package-prefix>_API_VERSION -> api_version
            <package-prefix>_BASE_URL -> base_url
            <package-prefix>_VERIFY_SSL -> verify_ssl
        """

        config_data = cls.read_ini(filename, section)

        env_api_version = cast(
            str, os.getenv(f"{constants.environment_prefix}_API_VERSION"))
        if env_api_version:
            config_data["api_version"] = env_api_version

        env_base_url = cast(
            str, os.getenv(f"{constants.environment_prefix}_BASE_URL"))
        if env_base_url:
            config_data["base_url"] = env_base_url

        env_verify_ssl = cast(
            str, os.getenv(f"{constants.environment_prefix}_VERIFY_SSL"))
        if env_verify_ssl:
            config_data["verify_ssl"] = env_verify_ssl

        if not config_data.get("base_url"):
            raise error.SDKError(f"Required parameter base_url not found.")

        converter = cattr.Converter()
        converter.register_structure_hook(bool, _convert_bool)
        settings: ApiSettings = converter.structure(config_data, cls)
        return settings
Пример #15
0
    def test_run_recursive_dashboard_should_handle_sdk_error(
            self, mock_mapper, mock_cleaner, mock_ingestor):  # noqa: E125

        scraper = self.__synchronizer.__dict__[
            '_MetadataSynchronizer__metadata_scraper']

        lookml_folder = self.__make_fake_folder()
        lookml_folder.dashboards = [self.__make_fake_dashboard(lookml_folder)]
        scraper.scrape_folder.return_value = lookml_folder
        scraper.scrape_dashboard.side_effect = error.SDKError('SDK error')

        self.__synchronizer.run()

        scraper.scrape_dashboard.assert_called_once()

        mapper = mock_mapper.return_value
        mapper.fulfill_tag_fields.assert_called_once()

        cleaner = mock_cleaner.return_value
        cleaner.delete_obsolete_metadata.assert_called_once()

        ingestor = mock_ingestor.return_value
        ingestor.ingest_metadata.assert_called_once()
Пример #16
0
 def __init__(
     self,
     settings: api_settings.PApiSettings,
     transport: transport.Transport,
     deserialize: serialize.TDeserialize,
     version: str,
 ):
     if not settings.is_configured():
         raise error.SDKError(
             "Missing required configuration values like base_url and api_version."
         )
     self.settings = settings
     self.sudo_token: auth_token.AuthToken = auth_token.AuthToken()
     self.token: auth_token.AuthToken = auth_token.AuthToken()
     self._sudo_id: Optional[int] = None
     self.transport = transport
     self.deserialize = deserialize
     self.version = version
     self.token_model: Union[Type[models31.AccessToken], Type[models40.AccessToken]]
     if self.version == "3.1":
         self.token_model = models31.AccessToken
     elif self.version == "4.0":
         self.token_model = models40.AccessToken
Пример #17
0
    def _login_admin(self) -> None:
        client_id = self.settings.get_client_id()
        client_secret = self.settings.get_client_secret()
        if not (client_id and client_secret):
            raise error.SDKError("Required auth credentials not found.")

        serialized = urllib.parse.urlencode({
            "client_id":
            cast(str, client_id),
            "client_secret":
            cast(str, client_secret),
        }).encode("utf-8")

        response = self._ok(
            self.transport.request(
                transport.HttpMethod.POST,
                "/login",
                body=serialized,
                headers={"Content-Type": "application/x-www-form-urlencoded"},
            ))

        access_token = self.deserialize(data=response, structure=token_model)
        assert isinstance(access_token, token_model_isinstances)
        self.admin_token = auth_token.AuthToken(access_token)
Пример #18
0
 def _ok(self, response: transport.Response) -> transport.TResponseValue:
     if not response.ok:
         raise error.SDKError(response.value)
     return response.value
Пример #19
0
 def _ok(self, response: transport.Response) -> str:
     if not response.ok:
         raise error.SDKError(response.value)
     return response.value.decode(encoding="utf-8")
Пример #20
0
 def is_configured(self) -> bool:
     if not self.base_url:
         raise error.SDKError(
             "Missing required configuration value: base_url")