Skip to content

Commit c519d8e

Browse files
feat: Add browser authentication (#46)
# Description * Copy code over from target-snowflake MeltanoLabs/target-snowflake#260, now there is parity between how the target & tap can connect to snowflake (3 ways: password, key pair & browser authentications) * This refactor uses `Enum` for `auth_method` for greater type safety. --------- Co-authored-by: Edgar Ramírez Mondragón <[email protected]>
1 parent 86a73b2 commit c519d8e

File tree

2 files changed

+34
-5
lines changed

2 files changed

+34
-5
lines changed

Diff for: tap_snowflake/client.py

+24-5
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ def patched_conform(
5454
singer_sdk.helpers._typing._conform_primitive_property = patched_conform
5555

5656

57+
class SnowflakeAuthMethod(Enum):
58+
"""Supported methods to authenticate to snowflake"""
59+
60+
BROWSER = 1
61+
PASSWORD = 2
62+
KEY_PAIR = 3
63+
64+
5765
class ProfileStats(Enum):
5866
"""Profile Statistics Enum."""
5967

@@ -111,14 +119,23 @@ def get_private_key(self):
111119
)
112120

113121
@cached_property
114-
def auth_method(self):
122+
def auth_method(self) -> SnowflakeAuthMethod:
115123
"""Validate & return the authentication method based on config."""
124+
if self.config.get("use_browser_authentication"):
125+
return SnowflakeAuthMethod.BROWSER
126+
116127
valid_auth_methods = {"private_key", "private_key_path", "password"}
117128
config_auth_methods = [x for x in self.config if x in valid_auth_methods]
118129
if len(config_auth_methods) != 1:
119-
msg = f"One of {valid_auth_methods} must be specified"
130+
msg = (
131+
"Neither password nor private key was provided for "
132+
"authentication. For password-less browser authentication via SSO, "
133+
"set use_browser_authentication config option to True."
134+
)
120135
raise ConfigValidationError(msg)
121-
return config_auth_methods[0]
136+
if config_auth_methods[0] in ["private_key", "private_key_path"]:
137+
return SnowflakeAuthMethod.KEY_PAIR
138+
return SnowflakeAuthMethod.PASSWORD
122139

123140
def get_sqlalchemy_url(self, config: dict) -> str:
124141
"""Concatenate a SQLAlchemy URL for use in connecting to the source."""
@@ -127,7 +144,9 @@ def get_sqlalchemy_url(self, config: dict) -> str:
127144
"user": config["user"],
128145
}
129146

130-
if self.auth_method == "password":
147+
if self.auth_method == SnowflakeAuthMethod.BROWSER:
148+
params["authenticator"] = "externalbrowser"
149+
elif self.auth_method == SnowflakeAuthMethod.PASSWORD:
131150
params["password"] = config["password"]
132151

133152
for option in ["database", "schema", "warehouse", "role"]:
@@ -143,7 +162,7 @@ def create_engine(self) -> sqlalchemy.engine.Engine:
143162
A SQLAlchemy engine.
144163
"""
145164
connect_args = {}
146-
if self.auth_method in ["private_key", "private_key_path"]:
165+
if self.auth_method == SnowflakeAuthMethod.KEY_PAIR:
147166
connect_args["private_key"] = self.get_private_key()
148167
return sqlalchemy.create_engine(
149168
self.sqlalchemy_url,

Diff for: tap_snowflake/tap.py

+10
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ class TapSnowflake(SQLTap):
5959
secret=True,
6060
description="The passprhase used to protect the private key",
6161
),
62+
th.Property(
63+
"use_browser_authentication",
64+
th.BooleanType,
65+
required=False,
66+
default=False,
67+
description=(
68+
"If authentication should be done using SSO (via external browser). "
69+
"See SSO browser authentication."
70+
)
71+
),
6272
th.Property(
6373
"account",
6474
th.StringType,

0 commit comments

Comments
 (0)