def check_required_parameters(self: "JwtCredentialsProvider") -> None:
     if self.role_arn is None or self.role_arn == "":
         raise InterfaceError("Missing required property: {}".format(
             JwtCredentialsProvider.KEY_ROLE_ARN))
     elif self.jwt is None or self.jwt == "":
         raise InterfaceError("Missing required property: {}".format(
             JwtCredentialsProvider.KEY_WEB_IDENTITY_TOKEN))
예제 #2
0
    def get_credentials(
        self: "AWSCredentialsProvider",
    ) -> typing.Union[AWSDirectCredentialsHolder, AWSProfileCredentialsHolder]:
        """
        Retrieves a :class`ABCCredentialsHolder` from cache or builds one.

        Returns
        -------
        An `AWSCredentialsHolder` object containing end-user specified AWS credential information: :class`ABCAWSCredentialsHolder`
        """
        key: int = self.get_cache_key()
        if key not in self.cache:
            try:
                self.refresh()
            except Exception as e:
                _logger.error("refresh failed: {}".format(str(e)))
                raise InterfaceError(e)

        credentials: typing.Union[
            AWSDirectCredentialsHolder,
            AWSProfileCredentialsHolder] = self.cache[key]

        if credentials is None:
            raise InterfaceError("Unable to load AWS credentials")

        return credentials
 def check_required_parameters(self: "SamlCredentialsProvider") -> None:
     if self.user_name == "" or self.user_name is None:
         raise InterfaceError("Missing required property: user_name")
     if self.password == "" or self.password is None:
         raise InterfaceError("Missing required property: password")
     if self.idp_host == "" or self.idp_host is None:
         raise InterfaceError("Missing required property: idp_host")
    def run_server(self: "BrowserAzureCredentialsProvider",
                   listen_socket: socket.socket, idp_response_timeout: int,
                   state: str) -> str:
        conn, addr = listen_socket.accept()
        conn.settimeout(float(idp_response_timeout))
        size: int = 102400
        with conn:
            while True:
                part: bytes = conn.recv(size)
                decoded_part = part.decode()
                state_idx: int = decoded_part.find("state=")

                if state_idx > -1:
                    received_state: str = decoded_part[state_idx +
                                                       6:decoded_part.
                                                       find("&", state_idx)]

                    if received_state != state:
                        raise InterfaceError(
                            "Incoming state {received} does not match the outgoing state {expected}"
                            .format(received=received_state, expected=state))

                    code_idx: int = decoded_part.find("code=")

                    if code_idx < 0:
                        raise InterfaceError("No code found")

                    received_code: str = decoded_part[code_idx +
                                                      5:decoded_part.
                                                      find("&", code_idx)]

                    if received_code == "":
                        raise InterfaceError("No valid code found")
                    conn.send(self.close_window_http_resp())
                    return received_code
    def get_saml_assertion(self: "BrowserAzureCredentialsProvider") -> str:

        if self.idp_tenant == "" or self.idp_tenant is None:
            raise InterfaceError("Missing required property: idp_tenant")
        if self.client_id == "" or self.client_id is None:
            raise InterfaceError("Missing required property: client_id")

        if self.idp_response_timeout < 10:
            raise InterfaceError(
                "idp_response_timeout should be 10 seconds or greater.")

        listen_socket: socket.socket = self.get_listen_socket()
        self.redirectUri = "http://localhost:{port}/redshift/".format(
            port=self.listen_port)
        _logger.debug("Listening for connection on port {}".format(
            self.listen_port))

        try:
            token: str = self.fetch_authorization_token(listen_socket)
            saml_assertion: str = self.fetch_saml_response(token)
        except Exception as e:
            raise e
        finally:
            listen_socket.close()
        _logger.debug("Got SAML assertion")
        return self.wrap_and_encode_assertion(saml_assertion)
예제 #6
0
    def run_server(
        self: "BrowserAzureOAuth2CredentialsProvider",
        listen_socket: socket.socket,
        idp_response_timeout: int,
        state: int,
    ):
        """
        Runs a server on localhost to listen for the IdP's response to our HTTP POST request for JWT assertion.

        Parameters
        ----------

        :param listen_socket: socket.socket
            The socket on which the method listens for a response
        :param idp_response_timeout: int
            The maximum time to listen on the socket, specified in seconds
        :param state: str
            The state generated by the client. This must match the state received from the IdP server

        Returns
        -------
        The IdP's response, including JWT assertion
        """
        conn, addr = listen_socket.accept()
        conn.settimeout(float(idp_response_timeout))
        size: int = 102400
        with conn:
            while True:
                part: bytes = conn.recv(size)
                decoded_part = part.decode()
                state_idx: int = decoded_part.find(
                    "{}=".format(BrowserAzureOAuth2CredentialsProvider.
                                 OAuthParamNames.STATE.value))

                if state_idx > -1:
                    received_state: str = decoded_part[state_idx +
                                                       6:decoded_part.
                                                       find("&", state_idx)]

                    if received_state != state:
                        raise InterfaceError(
                            "Incoming state {received} does not match the outgoing state {expected}"
                            .format(received=received_state, expected=state))

                    code_idx: int = decoded_part.find(
                        "{}=".format(BrowserAzureOAuth2CredentialsProvider.
                                     OAuthParamNames.IDP_CODE.value))

                    if code_idx < 0:
                        raise InterfaceError("No code found")
                    received_code: str = decoded_part[code_idx + 5:state_idx -
                                                      1]

                    if received_code == "":
                        raise InterfaceError("No valid code found")
                    conn.send(self.close_window_http_resp())
                    return received_code
예제 #7
0
    def get_saml_assertion(self: "BrowserSamlCredentialsProvider") -> str:

        if self.login_url == "" or self.login_url is None:
            raise InterfaceError("Missing required property: login_url")

        if self.idp_response_timeout < 10:
            raise InterfaceError("idp_response_timeout should be 10 seconds or greater.")
        if self.listen_port < 1 or self.listen_port > 65535:
            raise InterfaceError("Invalid property value: listen_port")

        return self.authenticate()
    def get_credentials(self: "SamlCredentialsProvider") -> CredentialsHolder:
        key: str = self.get_cache_key()
        if key not in self.cache or self.cache[key].is_expired():
            try:
                self.refresh()
            except Exception as e:
                _logger.error("refresh failed: {}".format(str(e)))
                raise InterfaceError(e)

        if key not in self.cache or self.cache[key] is None:
            raise InterfaceError("Unable to load AWS credentials from IDP")

        return self.cache[key]
예제 #9
0
 def check_required_parameters(
         self: "BrowserAzureOAuth2CredentialsProvider") -> None:
     super().check_required_parameters()
     if not self.idp_tenant:
         raise InterfaceError(
             "BrowserAzureOauth2CredentialsProvider requires idp_tenant")
     if not self.client_id:
         raise InterfaceError(
             "BrowserAzureOauth2CredentialsProvider requires client_id")
     if not self.idp_response_timeout or self.idp_response_timeout < 10:
         raise InterfaceError(
             "BrowserAzureOauth2CredentialsProvider requires idp_response_timeout to be 10 seconds or greater"
         )
    def fetch_saml_response(self: "BrowserAzureCredentialsProvider", token):
        url: str = "https://login.microsoftonline.com/{tenant}/oauth2/token".format(
            tenant=self.idp_tenant)
        # headers to pass with POST request
        headers: typing.Dict[str, str] = azure_headers

        # required parameters to pass in POST body
        payload: typing.Dict[str, typing.Optional[str]] = {
            "code": token,
            "requested_token_type": "urn:ietf:params:oauth:token-type:saml2",
            "grant_type": "authorization_code",
            "scope": "openid",
            "resource": self.client_id,
            "client_id": self.client_id,
            "client_secret": self.client_secret,
            "redirect_uri": self.redirectUri,
        }
        try:
            response = requests.post(url, data=payload, headers=headers)
            response.raise_for_status()
        except requests.exceptions.HTTPError as e:
            logger.error(
                "Request for authentication from Microsoft was unsuccessful. {}"
                .format(str(e)))
            raise InterfaceError(e)
        except requests.exceptions.Timeout as e:
            logger.error(
                "A timeout occurred when requesting authentication from Azure")
            raise InterfaceError(e)
        except requests.exceptions.TooManyRedirects as e:
            logger.error(
                "A error occurred when requesting authentication from Azure. Verify RedshiftProperties are correct"
            )
            raise InterfaceError(e)
        except requests.exceptions.RequestException as e:
            logger.error(
                "A unknown error occurred when requesting authentication from Azure"
            )
            raise InterfaceError(e)

        try:
            saml_assertion: str = response.json()["access_token"]
        except TypeError as e:
            logger.error("Failed to decode saml assertion returned from Azure")
            raise InterfaceError(e)
        except KeyError as e:
            logger.error("Azure access_token was not found in saml assertion")
            raise InterfaceError(e)
        except Exception as e:
            raise InterfaceError(e)
        if saml_assertion == "":
            raise InterfaceError("Azure access_token is empty")

        missing_padding: int = 4 - len(saml_assertion) % 4
        if missing_padding:
            saml_assertion += "=" * missing_padding

        return str(base64.urlsafe_b64decode(saml_assertion))
    def set_iam_properties(info: RedshiftProperty) -> RedshiftProperty:
        """
        Helper function to handle connection properties and ensure required parameters are specified.
        Parameters
        """
        # set properties present for both IAM, Native authentication
        IamHelper.set_auth_properties(info)

        if info.is_serverless_host and info.iam:
            raise ProgrammingError("This feature is not yet available")
            # if Version(pkg_resources.get_distribution("boto3").version) <= Version("1.20.22"):
            #     raise pkg_resources.VersionConflict(
            #         "boto3 >= XXX required for authentication with Amazon Redshift serverless. "
            #         "Please upgrade the installed version of boto3 to use this functionality."
            #     )

        if info.is_serverless_host:
            info.set_account_id_from_host()
            info.set_region_from_host()

        if info.iam is True:
            if info.cluster_identifier is None and not info.is_serverless_host:
                raise InterfaceError(
                    "Invalid connection property setting. cluster_identifier must be provided when IAM is enabled"
                )
            IamHelper.set_iam_credentials(info)
        # Check for Browser based OAuth Native authentication
        NativeAuthPluginHelper.set_native_auth_plugin_properties(info)
        return info
예제 #12
0
    def open_browser(self: "BrowserSamlCredentialsProvider") -> None:
        import webbrowser

        url: typing.Optional[str] = self.login_url
        if url is None:
            raise InterfaceError("the login_url could not be empty")
        webbrowser.open(url)
예제 #13
0
def test_redshiftconnector_get_status_with_error_port(mock_port,
                                                      redshift_connector):
    mock_port.side_effect = InterfaceError('error mock')
    result = redshift_connector.get_status()
    assert type(result.error) == str
    assert result.status is False
    assert str(result.error) == 'error mock'
    def get_saml_assertion(self: "OktaCredentialsProvider") -> str:
        self.check_required_parameters()
        if self.app_id == "" or self.app_id is None:
            raise InterfaceError("Missing required property: app_id")

        okta_session_token: str = self.okta_authentication()
        return self.handle_saml_assertion(okta_session_token)
    def get_saml_assertion(self: "AzureCredentialsProvider") -> str:
        # idp_tenant, client_secret, and client_id are
        # all required parameters to be able to authenticate with Microsoft Azure.
        # user and password are also required and need to be set to the username and password of the
        # Microsoft Azure account that is logging in.
        if self.user_name == "" or self.user_name is None:
            raise InterfaceError("Missing required property: user_name")
        if self.password == "" or self.password is None:
            raise InterfaceError("Missing required property: password")
        if self.idp_tenant == "" or self.idp_tenant is None:
            raise InterfaceError("Missing required property: idp_tenant")
        if self.client_secret == "" or self.client_secret is None:
            raise InterfaceError("Missing required property: client_secret")
        if self.client_id == "" or self.client_id is None:
            raise InterfaceError("Missing required property: client_id")

        return self.azure_oauth_based_authentication()
    def handle_saml_assertion(self: "OktaCredentialsProvider",
                              okta_session_token: str) -> str:
        import bs4  # type: ignore
        import requests

        url: str = "https://{host}/home/{app_name}/{app_id}?onetimetoken={session_token}".format(
            host=self.idp_host,
            app_name=self.app_name,
            app_id=self.app_id,
            session_token=okta_session_token)
        try:
            response: "requests.Response" = requests.get(
                url, verify=self.do_verify_ssl_cert())
            response.raise_for_status()
        except requests.exceptions.HTTPError as e:
            _logger.error(
                "Request for SAML assertion from Okta was unsuccessful. {}".
                format(str(e)))
            raise InterfaceError(e)
        except requests.exceptions.Timeout as e:
            _logger.error(
                "A timeout occurred when requesting SAML assertion from Okta")
            raise InterfaceError(e)
        except requests.exceptions.TooManyRedirects as e:
            _logger.error(
                "A error occurred when requesting SAML assertion from Okta. Verify RedshiftProperties are correct"
            )
            raise InterfaceError(e)
        except requests.exceptions.RequestException as e:
            _logger.error(
                "A unknown error occurred when requesting SAML assertion from Okta"
            )
            raise InterfaceError(e)

        text: str = response.text

        try:
            soup = bs4.BeautifulSoup(text, "html.parser")
            saml_response: str = soup.find("input",
                                           {"name": "SAMLResponse"})["value"]
            return saml_response
        except Exception as e:
            _logger.error(
                "An error occurred while parsing SAML response: {}".format(
                    str(e)))
            raise InterfaceError(e)
예제 #17
0
    def get_saml_assertion(
            self: "AdfsCredentialsProvider") -> typing.Optional[str]:
        if self.idp_host == "" or self.idp_host is None:
            raise InterfaceError("Missing required property: idp_host")

        if self.user_name == "" or self.user_name is None or self.password == "" or self.password is None:
            return self.windows_integrated_authentication()

        return self.form_based_authentication()
예제 #18
0
    def extract_jwt_assertion(self: "BrowserAzureOAuth2CredentialsProvider",
                              content: str) -> str:
        """
        Returns encoded JWT assertion extracted from IdP response content
        """
        import json

        response_content: typing.Dict[str, str] = json.loads(content)

        if "access_token" not in response_content:
            raise InterfaceError("Failed to find access_token")

        encoded_jwt_assertion: str = response_content["access_token"]

        if not encoded_jwt_assertion:
            raise InterfaceError("Invalid access_token value")

        return encoded_jwt_assertion
    def get_credentials(self: "SamlCredentialsProvider") -> CredentialsHolder:
        key: str = self.get_cache_key()
        if key not in self.cache or self.cache[key].is_expired():
            try:
                self.refresh()
            except Exception as e:
                logger.error("refresh failed: {}".format(str(e)))
                raise InterfaceError(e)
        # if the SAML response has db_user argument, it will be picked up at this point.
        credentials: CredentialsHolder = self.cache[key]

        if credentials is None:
            raise InterfaceError("Unable to load AWS credentials from IDP")

        # if db_user argument has been passed in the connection string, add it to metadata.
        if self.db_user:
            credentials.metadata.set_db_user(self.db_user)

        return credentials
예제 #20
0
    def read_auth_profile(
        auth_profile: str,
        iam_access_key_id: str,
        iam_secret_key: str,
        iam_session_token: typing.Optional[str],
        info: RedshiftProperty,
    ) -> RedshiftProperty:
        import json

        import boto3
        from botocore.exceptions import ClientError

        # 1st phase - authenticate with boto3 client for Amazon Redshift via IAM
        # credentials provided by end user
        creds: typing.Dict[str, str] = {
            "aws_access_key_id": iam_access_key_id,
            "aws_secret_access_key": iam_secret_key,
            "region_name": typing.cast(str, info.region),
        }

        for opt_key, opt_val in (
            ("aws_session_token", iam_session_token),
            ("endpoint_url", info.endpoint_url),
        ):
            if opt_val is not None and opt_val != "":
                creds[opt_key] = opt_val

        try:
            _logger.debug("Initial authentication with boto3...")
            client = boto3.client(service_name="redshift", **creds)
            _logger.debug("Requesting authentication profiles")
            # 2nd phase - request Amazon Redshift authentication profiles and record contents for retrieving
            # temporary credentials for the Amazon Redshift cluster specified by end user
            response = client.describe_authentication_profiles(
                AuthenticationProfileName=auth_profile)
        except ClientError:
            raise InterfaceError(
                "Unable to retrieve contents of Redshift authentication profile from server"
            )

        _logger.debug("Received {} authentication profiles".format(
            len(response["AuthenticationProfiles"])))
        # the first matching authentication profile will be used
        profile_content: typing.Union[str] = response[
            "AuthenticationProfiles"][0]["AuthenticationProfileContent"]

        try:
            profile_content_dict: typing.Dict = json.loads(profile_content)
            return RedshiftProperty(**profile_content_dict)
        except ValueError:
            raise ProgrammingError(
                "Unable to decode the JSON content of the Redshift authentication profile: {}"
                .format(auth_profile))
    def refresh(self: "JwtCredentialsProvider") -> None:
        import boto3  # type: ignore

        client = boto3.client("sts")

        try:
            _logger.debug("JWT: {}".format(self.jwt))
            if self.jwt is None:
                raise InterfaceError("Unable to refresh, no jwt provided")

            jwt: str = self.process_jwt(self.jwt)
            decoded_jwt: typing.Optional[typing.List[typing.Union[
                str, bytes]]] = self.decode_jwt(self.jwt)

            response = client.assume_role_with_web_identity(
                RoleArn=self.role_arn,
                RoleSessionName=self.role_session_name,
                WebIdentityToken=jwt,
                DurationSeconds=self.duration if
                (self.duration is not None) and (self.duration > 0) else None,
            )

            stscred: typing.Dict[str, typing.Any] = response["Credentials"]
            credentials: CredentialsHolder = CredentialsHolder(stscred)
            key: str = self.get_cache_key()
            self.cache[key] = credentials

        except client.exceptions.MalformedPolicyDocumentException as e:
            _logger.error("MalformedPolicyDocumentException: %s", e)
            raise e
        except client.exceptions.PackedPolicyTooLargeException as e:
            _logger.error("PackedPolicyTooLargeException: %s", e)
            raise e
        except client.exceptions.IDPRejectedClaimException as e:
            _logger.error("IDPRejectedClaimException: %s", e)
            raise e
        except client.exceptions.InvalidIdentityTokenException as e:
            _logger.error("InvalidIdentityTokenException: %s", e)
            raise e
        except client.exceptions.ExpiredTokenException as e:
            _logger.error("ExpiredTokenException: %s", e)
            raise e
        except client.exceptions.RegionDisabledException as e:
            _logger.error("RegionDisabledException: %s", e)
            raise e
        except Exception as e:
            _logger.error("other Exception: %s", e)
            raise e
    def okta_authentication(self: "OktaCredentialsProvider") -> str:
        import requests

        # HTTP Post request to Okta API for session token
        url: str = "https://{host}/api/v1/authn".format(host=self.idp_host)
        headers: typing.Dict[str, str] = okta_headers
        payload: typing.Dict[str, typing.Optional[str]] = {
            "username": self.user_name,
            "password": self.password
        }
        try:
            response: "requests.Response" = requests.post(
                url,
                data=json.dumps(payload),
                headers=headers,
                verify=self.do_verify_ssl_cert())
            response.raise_for_status()
        except requests.exceptions.HTTPError as e:
            _logger.error(
                "Request for authentication from Okta was unsuccessful. {}".
                format(str(e)))
            raise InterfaceError(e)
        except requests.exceptions.Timeout as e:
            _logger.error(
                "A timeout occurred when requesting authentication from Okta")
            raise InterfaceError(e)
        except requests.exceptions.TooManyRedirects as e:
            _logger.error(
                "A error occurred when requesting authentication from Okta. Verify RedshiftProperties are correct"
            )
            raise InterfaceError(e)
        except requests.exceptions.RequestException as e:
            _logger.error(
                "A unknown error occurred when requesting authentication from Okta"
            )
            raise InterfaceError(e)

        # Retrieve and parse the Okta response for session token
        if response is None:
            raise InterfaceError(
                "Request for authentication returned empty payload")
        response_payload: typing.Dict[str, typing.Any] = response.json()
        if "status" not in response_payload:
            raise InterfaceError(
                "Request for authentication retrieved malformed payload.")
        elif response_payload["status"] != "SUCCESS":
            raise InterfaceError(
                "Request for authentication received non success response.")
        else:
            return str(response_payload["sessionToken"])
예제 #23
0
    def refresh(self: "JwtCredentialsProvider") -> None:
        jwt: str = self.get_jwt_assertion()
        _logger.debug("JWT: {}".format(jwt))

        if jwt is None:
            raise InterfaceError("Unable to refresh, no jwt provided")

        credentials: NativeTokenHolder = NativeTokenHolder(access_token=jwt,
                                                           expiration=None)
        credentials.refresh = True

        _logger.debug("disable_cache={}".format(str(self.disable_cache)))
        if not self.disable_cache:
            self.cache[self.get_cache_key()] = credentials

        else:
            self.last_refreshed_credentials = credentials
예제 #24
0
    def open_browser(self: "BrowserAzureOAuth2CredentialsProvider",
                     state: str) -> None:
        """
        Opens the default browser to allow user authentication with the IdP

        Parameters
        ----------
        :param state: str
            The state generated by the client

        Returns
        -------
        None
        """
        import webbrowser

        url: str = self.get_authorization_token_url(state=state)

        _logger.debug("SSO URI: {}".format(url))

        if url is None:
            raise InterfaceError("the login_url could not be empty")
        webbrowser.open(url)
    def refresh(self: "SamlCredentialsProvider") -> None:
        import boto3  # type: ignore
        import bs4  # type: ignore

        try:
            # get SAML assertion from specific identity provider
            saml_assertion = self.get_saml_assertion()
        except Exception as e:
            _logger.error("Get saml assertion failed: {}".format(str(e)))
            raise InterfaceError(e)
        # decode SAML assertion into xml format
        doc: bytes = base64.b64decode(saml_assertion)
        soup = bs4.BeautifulSoup(doc, "xml")
        attrs = soup.findAll("Attribute")
        # extract RoleArn adn PrincipleArn from SAML assertion
        role_pattern = re.compile(r"arn:aws:iam::\d*:role/\S+")
        provider_pattern = re.compile(r"arn:aws:iam::\d*:saml-provider/\S+")
        roles: typing.Dict[str, str] = {}
        for attr in attrs:
            name: str = attr.attrs["Name"]
            values: typing.Any = attr.findAll("AttributeValue")
            if name == "https://aws.amazon.com/SAML/Attributes/Role":
                for value in values:
                    arns = value.contents[0].split(",")
                    role: str = ""
                    provider: str = ""
                    for arn in arns:
                        arn = arn.strip(
                        )  # remove trailing or leading whitespace
                        if role_pattern.match(arn):
                            role = arn
                        if provider_pattern.match(arn):
                            provider = arn
                    if role != "" and provider != "":
                        roles[role] = provider

        if len(roles) == 0:
            raise InterfaceError("No role found in SamlAssertion")
        role_arn: str = ""
        principle: str = ""
        if self.preferred_role:
            role_arn = self.preferred_role
            if role_arn not in roles:
                raise InterfaceError(
                    "Preferred role not found in SamlAssertion")
            principle = roles[role_arn]
        else:
            role_arn = random.choice(list(roles))
            principle = roles[role_arn]

        client = boto3.client("sts")

        try:
            response = client.assume_role_with_saml(
                RoleArn=role_arn,  # self.preferred_role,
                PrincipalArn=principle,  # self.principal,
                SAMLAssertion=saml_assertion,
            )

            stscred: typing.Dict[str, typing.Any] = response["Credentials"]
            credentials: CredentialsHolder = CredentialsHolder(stscred)
            # get metadata from SAML assertion
            credentials.set_metadata(self.read_metadata(doc))
            key: str = self.get_cache_key()
            self.cache[key] = credentials
        except AttributeError as e:
            _logger.error("AttributeError: %s", e)
            raise e
        except KeyError as e:
            _logger.error("KeyError: %s", e)
            raise e
        except client.exceptions.MalformedPolicyDocumentException as e:
            _logger.error("MalformedPolicyDocumentException: %s", e)
            raise e
        except client.exceptions.PackedPolicyTooLargeException as e:
            _logger.error("PackedPolicyTooLargeException: %s", e)
            raise e
        except client.exceptions.IDPRejectedClaimException as e:
            _logger.error("IDPRejectedClaimException: %s", e)
            raise e
        except client.exceptions.InvalidIdentityTokenException as e:
            _logger.error("InvalidIdentityTokenException: %s", e)
            raise e
        except client.exceptions.ExpiredTokenException as e:
            _logger.error("ExpiredTokenException: %s", e)
            raise e
        except client.exceptions.RegionDisabledException as e:
            _logger.error("RegionDisabledException: %s", e)
            raise e
        except Exception as e:
            _logger.error("Other Exception: %s", e)
            raise e
def set_iam_properties(
    info: RedshiftProperty,
    user: str,
    host: str,
    database: str,
    port: int,
    password: str,
    source_address: typing.Optional[str],
    unix_sock: typing.Optional[str],
    ssl: bool,
    sslmode: str,
    timeout: typing.Optional[int],
    max_prepared_statements: int,
    tcp_keepalive: bool,
    application_name: typing.Optional[str],
    replication: typing.Optional[str],
    idp_host: typing.Optional[str],
    db_user: typing.Optional[str],
    iam: bool,
    app_id: typing.Optional[str],
    app_name: str,
    preferred_role: typing.Optional[str],
    principal_arn: typing.Optional[str],
    credentials_provider: typing.Optional[str],
    region: typing.Optional[str],
    cluster_identifier: typing.Optional[str],
    client_id: typing.Optional[str],
    idp_tenant: typing.Optional[str],
    client_secret: typing.Optional[str],
    partner_sp_id: typing.Optional[str],
    idp_response_timeout: int,
    listen_port: int,
    login_url: typing.Optional[str],
    auto_create: bool,
    db_groups: typing.Optional[typing.List[str]],
    force_lowercase: bool,
    allow_db_user_override: bool,
) -> None:
    if info is None:
        raise InterfaceError(
            "Invalid connection property setting. info must be specified")
    # IAM requires an SSL connection to work.
    # Make sure that is set to SSL level VERIFY_CA or higher.
    info.ssl = ssl
    if info.ssl is True:
        if sslmode == SSLMode.VERIFY_CA.value:
            info.sslmode = SSLMode.VERIFY_CA.value
        elif sslmode == SSLMode.VERIFY_FULL.value:
            info.sslmode = SSLMode.VERIFY_FULL.value
        else:
            info.sslmode = SSLMode.VERIFY_CA.value
    else:
        info.sslmode = ""

    if (info.ssl is False) and (iam is True):
        raise InterfaceError(
            "Invalid connection property setting. SSL must be enabled when using IAM"
        )
    else:
        info.iam = iam

    if (info.iam is False) and (credentials_provider is not None):
        raise InterfaceError(
            "Invalid connection property setting. IAM must be enabled when using credentials "
            "via identity provider")
    elif (info.iam is True) and (credentials_provider is None):
        raise InterfaceError(
            "Invalid connection property setting. "
            "Credentials provider cannot be None when IAM is enabled")
    else:
        info.credentials_provider = credentials_provider

    if user is None:
        raise InterfaceError(
            "Invalid connection property setting. user must be specified")
    if host is None:
        raise InterfaceError(
            "Invalid connection property setting. host must be specified")
    if database is None:
        raise InterfaceError(
            "Invalid connection property setting. database must be specified")
    if port is None:
        raise InterfaceError(
            "Invalid connection property setting. port must be specified")
    if password is None:
        raise InterfaceError(
            "Invalid connection property setting. password must be specified")

    # basic driver parameters
    info.user_name = user
    info.host = host
    info.db_name = database
    info.port = port
    info.password = password
    info.source_address = source_address
    info.unix_sock = unix_sock
    info.timeout = timeout
    info.max_prepared_statements = max_prepared_statements
    info.tcp_keepalive = tcp_keepalive
    info.application_name = application_name
    info.replication = replication

    # Idp parameters
    info.idp_host = idp_host
    info.db_user = db_user
    info.app_id = app_id
    info.app_name = app_name
    info.preferred_role = preferred_role
    info.principal = principal_arn
    # Regions.fromName(string) requires the string to be lower case and in this format:
    # E.g. "us-west-2"
    info.region = region
    # cluster_identifier parameter is required
    info.cluster_identifier = cluster_identifier
    info.auto_create = auto_create
    info.db_groups = db_groups
    info.force_lowercase = force_lowercase
    info.allow_db_user_override = allow_db_user_override

    # Azure specified parameters
    info.client_id = client_id
    info.idp_tenant = idp_tenant
    info.client_secret = client_secret

    # Browser idp parameters
    info.idp_response_timeout = idp_response_timeout
    info.listen_port = listen_port
    info.login_url = login_url
    info.partner_sp_id = partner_sp_id

    if info.iam is True:
        set_iam_credentials(info)
    else:
        return
    def azure_oauth_based_authentication(
            self: "AzureCredentialsProvider") -> str:
        import requests

        # endpoint to connect with Microsoft Azure to get SAML Assertion token
        url: str = "https://login.microsoftonline.com/{tenant}/oauth2/token".format(
            tenant=self.idp_tenant)
        # headers to pass with POST request
        headers: typing.Dict[str, str] = azure_headers
        # required parameters to pass in POST body
        payload: typing.Dict[str, typing.Optional[str]] = {
            "grant_type": "password",
            "requested_token_type": "urn:ietf:params:oauth:token-type:saml2",
            "username": self.user_name,
            "password": self.password,
            "client_secret": self.client_secret,
            "client_id": self.client_id,
            "resource": self.client_id,
        }

        try:
            response: "requests.Response" = requests.post(
                url,
                data=payload,
                headers=headers,
                verify=self.do_verify_ssl_cert())
            response.raise_for_status()
        except requests.exceptions.HTTPError as e:
            if "response" in vars():
                _logger.debug(
                    "azure_oauth_based_authentication https response: {}".
                    format(response.text)  # type: ignore
                )
            else:
                _logger.debug(
                    "azure_oauth_based_authentication could not receive https response due to an error"
                )
            _logger.error(
                "Request for authentication from Azure was unsuccessful. {}".
                format(str(e)))
            raise InterfaceError(e)
        except requests.exceptions.Timeout as e:
            _logger.error(
                "A timeout occurred when requesting authentication from Azure")
            raise InterfaceError(e)
        except requests.exceptions.TooManyRedirects as e:
            _logger.error(
                "A error occurred when requesting authentication from Azure. Verify RedshiftProperties are correct"
            )
            raise InterfaceError(e)
        except requests.exceptions.RequestException as e:
            _logger.error(
                "A unknown error occurred when requesting authentication from Azure."
            )
            raise InterfaceError(e)

        # parse the JSON response to grab access_token field which contains Base64 encoded SAML
        # Assertion and decode it
        saml_assertion: str = ""
        try:
            saml_assertion = response.json()["access_token"]
        except Exception as e:
            _logger.error(
                "Failed to authenticate with Azure. Response from Azure did not include access_token."
            )
            raise InterfaceError(e)
        if saml_assertion == "":
            raise InterfaceError("Azure access_token is empty")

        missing_padding: int = 4 - len(saml_assertion) % 4
        if missing_padding:
            saml_assertion += "=" * missing_padding

        # decode the SAML Assertion to a String to add XML tags to form a SAML Response
        decoded_saml_assertion: str = ""
        try:
            decoded_saml_assertion = str(
                base64.urlsafe_b64decode(saml_assertion))
        except TypeError as e:
            _logger.error(
                "Failed to decode saml assertion returned from Azure")
            raise InterfaceError(e)

        # SAML Response is required to be sent to base class. We need to provide a minimum of:
        # 1) samlp:Response XML tag with xmlns:samlp protocol value
        # 2) samlp:Status XML tag and samlpStatusCode XML tag with Value indicating Success
        # 3) followed by Signed SAML Assertion
        saml_response: str = (
            '<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">'
            "<samlp:Status>"
            '<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>'
            "</samlp:Status>"
            "{decoded_saml_assertion}"
            "</samlp:Response>".format(
                decoded_saml_assertion=decoded_saml_assertion[2:-1]))

        # re-encode the SAML Response in Base64 and return this to the base class
        saml_response = str(base64.b64encode(
            saml_response.encode("utf-8")))[2:-1]

        return saml_response
예제 #28
0
def connect(
    user: typing.Optional[str] = None,
    database: typing.Optional[str] = None,
    password: typing.Optional[str] = None,
    port: typing.Optional[int] = None,
    host: typing.Optional[str] = None,
    source_address: typing.Optional[str] = None,
    unix_sock: typing.Optional[str] = None,
    ssl: typing.Optional[bool] = None,
    sslmode: typing.Optional[str] = None,
    timeout: typing.Optional[int] = None,
    max_prepared_statements: typing.Optional[int] = None,
    tcp_keepalive: typing.Optional[bool] = None,
    application_name: typing.Optional[str] = None,
    replication: typing.Optional[str] = None,
    idp_host: typing.Optional[str] = None,
    db_user: typing.Optional[str] = None,
    app_id: typing.Optional[str] = None,
    app_name: typing.Optional[str] = None,
    preferred_role: typing.Optional[str] = None,
    principal_arn: typing.Optional[str] = None,
    access_key_id: typing.Optional[str] = None,
    secret_access_key: typing.Optional[str] = None,
    session_token: typing.Optional[str] = None,
    profile: typing.Optional[str] = None,
    credentials_provider: typing.Optional[str] = None,
    region: typing.Optional[str] = None,
    cluster_identifier: typing.Optional[str] = None,
    iam: typing.Optional[bool] = None,
    client_id: typing.Optional[str] = None,
    idp_tenant: typing.Optional[str] = None,
    client_secret: typing.Optional[str] = None,
    partner_sp_id: typing.Optional[str] = None,
    idp_response_timeout: typing.Optional[int] = None,
    listen_port: typing.Optional[int] = None,
    login_url: typing.Optional[str] = None,
    auto_create: typing.Optional[bool] = None,
    db_groups: typing.Optional[typing.List[str]] = None,
    force_lowercase: typing.Optional[bool] = None,
    allow_db_user_override: typing.Optional[bool] = None,
    client_protocol_version: typing.Optional[int] = None,
    database_metadata_current_db_only: typing.Optional[bool] = None,
    ssl_insecure: typing.Optional[bool] = None,
    web_identity_token: typing.Optional[str] = None,
    role_session_name: typing.Optional[str] = None,
    role_arn: typing.Optional[str] = None,
    iam_disable_cache: typing.Optional[bool] = None,
    auth_profile: typing.Optional[str] = None,
    endpoint_url: typing.Optional[str] = None,
    provider_name: typing.Optional[str] = None,
    scope: typing.Optional[str] = None,
) -> Connection:
    """
    Establishes a :class:`Connection` to an Amazon Redshift cluster. This function validates user input, optionally authenticates using an identity provider plugin, then constructs a :class:`Connection` object.

    Parameters
    ----------
    user : Optional[str]
        The username to use for authentication with the Amazon Redshift cluster.
    password : Optional[str]
        The password to use for authentication with the Amazon Redshift cluster.
    database : Optional[str]
        The name of the database instance to connect to.
    host : Optional[str]
        The hostname of the Amazon Redshift cluster.
    port : Optional[int]
        The port number of the Amazon Redshift cluster. Default value is 5439.
    source_address : typing.Optional[str]
    unix_sock : Optional[str]
    ssl : Optional[bool]
        Is SSL enabled. Default value is ``True``. SSL must be enabled when authenticating using IAM.
    sslmode : Optional[str]
        The security of the connection to the Amazon Redshift cluster. 'verify-ca' and 'verify-full' are supported.
    timeout : Optional[int]
        The number of seconds before the connection to the server will timeout. By default there is no timeout.
    max_prepared_statements : Optional[int]
    tcp_keepalive : Optional[bool]
        Is `TCP keepalive <https://en.wikipedia.org/wiki/Keepalive#TCP_keepalive>`_ used. The default value is ``True``.
    application_name : Optional[str]
        Sets the application name. The default value is None.
    replication : Optional[str]
        Used to run in `streaming replication mode <https://www.postgresql.org/docs/12/protocol-replication.html>`_.
    idp_host : Optional[str]
        The hostname of the IdP.
    db_user : Optional[str]
        The user ID to use with Amazon Redshift
    app_id : Optional[str]
    app_name : Optional[str]
        The name of the identity provider (IdP) application used for authentication.
    preferred_role : Optional[str]
        The IAM role preferred for the current connection.
    principal_arn : Optional[str]
        The ARN of the IAM entity (user or role) for which you are generating a policy.
    credentials_provider : Optional[str]
        The class name of the IdP that will be used for authenticating with the Amazon Redshift cluster.
    region : Optional[str]
        The AWS region where the Amazon Redshift cluster is located.
    cluster_identifier : Optional[str]
        The cluster identifier of the Amazon Redshift cluster.
    iam : Optional[bool]
        If IAM authentication is enabled. Default value is False. IAM must be True when authenticating using an IdP.
    client_id : Optional[str]
        The client id from Azure IdP.
    idp_tenant : Optional[str]
        The IdP tenant.
    client_secret : Optional[str]
        The client secret from Azure IdP.
    partner_sp_id : Optional[str]
        The Partner SP Id used for authentication with Ping.
    idp_response_timeout : Optional[int]
        The timeout for retrieving SAML assertion from IdP. Default value is `120`.
    listen_port : Optional[int]
        The listen port the IdP will send the SAML assertion to. Default value is `7890`.
    login_url : Optional[str]
        The SSO url for the IdP.
    auto_create : Optional[bool]
        Indicates whether the user should be created if they do not exist. Default value is `False`.
    db_groups : Optional[str]
        A comma-separated list of existing database group names that the `db_user` joins for the current session.
    force_lowercase : Optional[bool]
    allow_db_user_override : Optional[bool]
        Specifies if the driver uses the `db_user` value from the SAML assertion. TDefault value is `False`.
    client_protocol_version : Optional[int]
         The requested server protocol version. The default value is 2 representing `BINARY`. If the requested server protocol cannot be satisfied a warning will be displayed to the user and the driver will default to the highest supported protocol. See `ClientProtocolVersion` for more details.
    database_metadata_current_db_only : Optional[bool]
        Is `datashare <https://docs.aws.amazon.com/redshift/latest/dg/datashare-overview.html>`_ disabled. Default value is True, implying datasharing will not be used.
    ssl_insecure : Optional[bool]
        Specifies if IdP host's server certificate will be verified. Default value is True
    web_identity_token: Optional[str]
        A web identity token used for authentication with JWT.
    role_session_name: Optional[str]
        An identifier for the assumed role session used for authentication with JWT.
    role_arn: Optional[str]
        The role ARN used for authentication with JWT. This parameter is required when using a JWTCredentialsProvider.
    iam_disable_cache: Optional[bool]
        This option specifies whether the IAM credentials are cached. By default caching is enabled.
    auth_profile: Optional[str]
        The name of an Amazon Redshift Authentication profile having connection properties as JSON. See :class:RedshiftProperty to learn how connection properties should be named.
    endpoint_url: Optional[str]
        The Amazon Redshift endpoint url. This option is only used by AWS internal teams.
    provider_name: Optional[str]
        The name of the Redshift Native Auth Provider.
    scope: Optional[str]
        Scope for BrowserAzureOauth2CredentialsProvider authentication.
    Returns
    -------
    A Connection object associated with the specified Amazon Redshift cluster: :class:`Connection`
    """
    info: RedshiftProperty = RedshiftProperty()
    info.put("access_key_id", access_key_id)
    info.put("allow_db_user_override", allow_db_user_override)
    info.put("app_id", app_id)
    info.put("app_name", app_name)
    info.put("application_name", application_name)
    info.put("auth_profile", auth_profile)
    info.put("auto_create", auto_create)
    info.put("client_id", client_id)
    info.put("client_protocol_version", client_protocol_version)
    info.put("client_secret", client_secret)
    info.put("cluster_identifier", cluster_identifier)
    info.put("credentials_provider", credentials_provider)
    info.put("database_metadata_current_db_only",
             database_metadata_current_db_only)
    info.put("db_groups", db_groups)
    info.put("db_name", database)
    info.put("db_user", db_user)
    info.put("endpoint_url", endpoint_url)
    info.put("force_lowercase", force_lowercase)
    info.put("host", host)
    info.put("iam", iam)
    info.put("iam_disable_cache", iam_disable_cache)
    info.put("idp_host", idp_host)
    info.put("idp_response_timeout", idp_response_timeout)
    info.put("idp_tenant", idp_tenant)
    info.put("listen_port", listen_port)
    info.put("login_url", login_url)
    info.put("max_prepared_statements", max_prepared_statements)
    info.put("partner_sp_id", partner_sp_id)
    info.put("password", password)
    info.put("port", port)
    info.put("preferred_role", preferred_role)
    info.put("principal", principal_arn)
    info.put("profile", profile)
    info.put("provider_name", provider_name)
    info.put("region", region)
    info.put("replication", replication)
    info.put("role_arn", role_arn)
    info.put("role_session_name", role_session_name)
    info.put("scope", scope)
    info.put("secret_access_key", secret_access_key)
    info.put("session_token", session_token)
    info.put("source_address", source_address)
    info.put("ssl", ssl)
    info.put("ssl_insecure", ssl_insecure)
    info.put("sslmode", sslmode)
    info.put("tcp_keepalive", tcp_keepalive)
    info.put("timeout", timeout)
    info.put("unix_sock", unix_sock)
    info.put("user_name", user)
    info.put("web_identity_token", web_identity_token)

    _logger.debug(make_divider_block())
    _logger.debug("User provided connection arguments")
    _logger.debug(make_divider_block())
    _logger.debug(mask_secure_info_in_props(info).__str__())
    _logger.debug(make_divider_block())

    if (info.ssl is False) and (info.iam is True):
        raise InterfaceError(
            "Invalid connection property setting. SSL must be enabled when using IAM"
        )

    if (info.iam is False) and (info.ssl_insecure is False):
        raise InterfaceError(
            "Invalid connection property setting. IAM must be enabled when using ssl_insecure"
        )

    if info.client_protocol_version not in ClientProtocolVersion.list():
        raise InterfaceError(
            "Invalid connection property setting. client_protocol_version must be in: {}"
            .format(ClientProtocolVersion.list()))

    redshift_native_auth: bool = False
    if info.iam:
        if info.credentials_provider == "BasicJwtCredentialsProvider":
            redshift_native_auth = True
            _logger.debug("redshift_native_auth enabled")

    if not redshift_native_auth:
        IamHelper.set_iam_properties(info)

    _logger.debug(make_divider_block())
    _logger.debug(
        "Connection arguments following validation and IAM auth (if applicable)"
    )
    _logger.debug(make_divider_block())
    _logger.debug(mask_secure_info_in_props(info).__str__())
    _logger.debug(make_divider_block())

    return Connection(
        user=info.user_name,
        host=info.host,
        database=info.db_name,
        port=info.port,
        password=info.password,
        source_address=info.source_address,
        unix_sock=info.unix_sock,
        ssl=info.ssl,
        sslmode=info.sslmode,
        timeout=info.timeout,
        max_prepared_statements=info.max_prepared_statements,
        tcp_keepalive=info.tcp_keepalive,
        application_name=info.application_name,
        replication=info.replication,
        client_protocol_version=info.client_protocol_version,
        database_metadata_current_db_only=info.
        database_metadata_current_db_only,
        credentials_provider=info.credentials_provider,
        provider_name=info.provider_name,
        web_identity_token=info.web_identity_token,
    )
예제 #29
0
    def form_based_authentication(self: "AdfsCredentialsProvider") -> str:
        import bs4  # type: ignore
        import requests

        url: str = "https://{host}:{port}/adfs/ls/IdpInitiatedSignOn.aspx?loginToRp=urn:amazon:webservices".format(
            host=self.idp_host, port=str(self.idpPort))
        try:
            response: "requests.Response" = requests.get(
                url, verify=self.do_verify_ssl_cert())
            response.raise_for_status()
        except requests.exceptions.HTTPError as e:
            if "response" in vars():
                _logger.debug(
                    "form_based_authentication https response: {}".format(
                        response.text))  # type: ignore
            else:
                _logger.debug(
                    "form_based_authentication could not receive https response due to an error"
                )
            _logger.error(
                "Request for SAML assertion when refreshing credentials was unsuccessful. {}"
                .format(str(e)))
            raise InterfaceError(e)
        except requests.exceptions.Timeout as e:
            _logger.error("A timeout occurred when requesting SAML assertion")
            raise InterfaceError(e)
        except requests.exceptions.TooManyRedirects as e:
            _logger.error(
                "A error occurred when requesting SAML assertion to refresh credentials. "
                "Verify RedshiftProperties are correct")
            raise InterfaceError(e)
        except requests.exceptions.RequestException as e:
            _logger.error(
                "A unknown error occurred when requesting SAML assertion to refresh credentials"
            )
            raise InterfaceError(e)

        try:
            soup = bs4.BeautifulSoup(response.text, features="lxml")
        except Exception as e:
            _logger.error(
                "An error occurred while parsing response: {}".format(str(e)))
            raise InterfaceError(e)

        payload: typing.Dict[str, typing.Optional[str]] = {}

        for inputtag in soup.find_all(re.compile("(INPUT|input)")):
            name: str = inputtag.get("name", "")
            value: str = inputtag.get("value", "")
            if "username" in name.lower():
                payload[name] = self.user_name
            elif "authmethod" in name.lower():
                payload[name] = value
            elif "password" in name.lower():
                payload[name] = self.password
            elif name != "":
                payload[name] = value

        action: typing.Optional[str] = self.get_form_action(soup)
        if action and action.startswith("/"):
            url = "https://{host}:{port}{action}".format(host=self.idp_host,
                                                         port=str(
                                                             self.idpPort),
                                                         action=action)

        try:
            response = requests.post(url,
                                     data=payload,
                                     verify=self.do_verify_ssl_cert())
            response.raise_for_status()
        except requests.exceptions.HTTPError as e:
            _logger.error(
                "Request to refresh credentials was unsuccessful. {}".format(
                    str(e)))
            raise InterfaceError(e)
        except requests.exceptions.Timeout as e:
            _logger.error(
                "A timeout occurred when attempting to refresh credentials")
            raise InterfaceError(e)
        except requests.exceptions.TooManyRedirects as e:
            _logger.error(
                "A error occurred when refreshing credentials. Verify RedshiftProperties are correct"
            )
            raise InterfaceError(e)
        except requests.exceptions.RequestException as e:
            _logger.error(
                "A unknown error occurred when refreshing credentials")
            raise InterfaceError(e)

        try:
            soup = bs4.BeautifulSoup(response.text, features="lxml")
        except Exception as e:
            _logger.error(
                "An error occurred while parsing response: {}".format(str(e)))
            raise InterfaceError(e)
        assertion: str = ""

        for inputtag in soup.find_all("input"):
            if inputtag.get("name") == "SAMLResponse":
                assertion = inputtag.get("value")

        if assertion == "":
            raise InterfaceError("Failed to find Adfs access_token")

        return assertion
예제 #30
0
    def set_auth_properties(info: RedshiftProperty):
        """
        Helper function to handle IAM and Native Auth connection properties and ensure required parameters are specified.
        Parameters
        """
        import pkg_resources
        from packaging.version import Version

        if info is None:
            raise InterfaceError(
                "Invalid connection property setting. info must be specified")

        # IAM requires an SSL connection to work.
        # Make sure that is set to SSL level VERIFY_CA or higher.
        if info.ssl is True:
            if info.sslmode not in SupportedSSLMode.list():
                info.put("sslmode", SupportedSSLMode.default())
                _logger.debug(
                    "A non-supported value: {} was provides for sslmode. Falling back to default value: {}"
                    .format(info.sslmode, SupportedSSLMode.default()))
        else:
            info.put("sslmode", "")

        # elif (info.iam is False) and any(
        #     (info.credentials_provider, info.access_key_id, info.secret_access_key, info.session_token, info.profile)
        # ):
        #     raise InterfaceError(
        #         "Invalid connection property setting. IAM must be enabled when using credential_provider, "
        #         "AWS credentials, Amazon Redshift authentication profile, or AWS profile"
        #     )
        if info.iam is True:
            _logger.debug("boto3 version: {}".format(
                Version(pkg_resources.get_distribution("boto3").version)))
            _logger.debug("botocore version: {}".format(
                Version(pkg_resources.get_distribution("botocore").version)))

            if info.cluster_identifier is None and not info.is_serverless_host:
                raise InterfaceError(
                    "Invalid connection property setting. cluster_identifier must be provided when IAM is enabled"
                )

            if info.credentials_provider is not None:
                if info.auth_profile is None and any(
                    (info.access_key_id, info.secret_access_key,
                     info.session_token, info.profile)):
                    raise InterfaceError(
                        "Invalid connection property setting. It is not valid to provide both Credentials provider and "
                        "AWS credentials or AWS profile")
                elif not isinstance(info.credentials_provider, str):
                    raise InterfaceError(
                        "Invalid connection property setting. It is not valid to provide a non-string value to "
                        "credentials_provider.")
            elif info.profile is not None:
                if info.auth_profile is None and any(
                    (info.access_key_id, info.secret_access_key,
                     info.session_token)):
                    raise InterfaceError(
                        "Invalid connection property setting. It is not valid to provide any of access_key_id, "
                        "secret_access_key, or session_token when profile is provided"
                    )
            elif info.access_key_id is not None:

                if info.secret_access_key is not None:
                    pass
                elif info.password != "":
                    info.put("secret_access_key", info.password)
                    _logger.debug(
                        "Value of password will be used for secret_access_key")
                else:
                    raise InterfaceError(
                        "Invalid connection property setting. "
                        "secret access key must be provided in either secret_access_key or password field"
                    )

                _logger.debug(
                    "AWS Credentials access_key_id: {} secret_access_key: {} session_token: {}"
                    .format(bool(info.access_key_id),
                            bool(info.secret_access_key),
                            bool(info.session_token)))
            elif info.secret_access_key is not None:
                raise InterfaceError(
                    "Invalid connection property setting. access_key_id is required when secret_access_key is "
                    "provided")
            elif info.session_token is not None:
                raise InterfaceError(
                    "Invalid connection property setting. access_key_id and secret_access_key are  required when "
                    "session_token is provided")

        if info.db_groups and info.force_lowercase:
            info.put("db_groups", [group.lower() for group in info.db_groups])

        # Check for IAM keys and AuthProfile first
        if info.auth_profile is not None:
            if Version(pkg_resources.get_distribution(
                    "boto3").version) < Version("1.17.111"):
                raise pkg_resources.VersionConflict(
                    "boto3 >= 1.17.111 required for authentication via Amazon Redshift authentication profile. "
                    "Please upgrade the installed version of boto3 to use this functionality."
                )

            if not all(
                (info.access_key_id, info.secret_access_key, info.region)):
                raise InterfaceError(
                    "Invalid connection property setting. access_key_id, secret_access_key, and region are required "
                    "for authentication via Redshift auth_profile")
            else:
                # info.put("region", info.region)
                # info.put("endpoint_url", info.endpoint_url)

                resp = IdpAuthHelper.read_auth_profile(
                    auth_profile=typing.cast(str, info.auth_profile),
                    iam_access_key_id=typing.cast(str, info.access_key_id),
                    iam_secret_key=typing.cast(str, info.secret_access_key),
                    iam_session_token=info.session_token,
                    info=info,
                )
                info.put_all(resp)