Skip to content

Commit

Permalink
feat: M2M (client id+secret) auth for Databricks
Browse files Browse the repository at this point in the history
JIRA: LX-617
risk: low
  • Loading branch information
chrisbonilla95 committed Nov 25, 2024
1 parent 4879db7 commit 293a318
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 3 deletions.
24 changes: 21 additions & 3 deletions docs/content/en/latest/data/data-source/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ CatalogDataSourceMsSql(

### Databricks

Using Machine-to-Machine (M2M) authentication (client_id + client_secret):
```python
CatalogDataSourceDatabricks(
id=data_source_id,
Expand All @@ -193,9 +194,26 @@ CatalogDataSourceDatabricks(
),
schema=xyz,
parameters=[{"name":"catalog", "value": os.environ["DATABRICKS_CATALOG"]}],
credentials=BasicCredentials(
username=os.environ["DATABRICKS_USER"],
password=os.environ["DATABRICKS_PASSWORD"],
credentials=ClientSecretCredentials(
client_id=os.environ["DATABRICKS_CLIENT_ID"],
client_secret=os.environ["DATABRICKS_CLIENT_SECRET"],
),
)
```

Using personal access token authentication:
```python
CatalogDataSourceDatabricks(
id=data_source_id,
name=data_source_name,
db_specific_attributes=DatabricksAttributes(
host=os.environ["DATABRICKS_HOST"],
http_path=os.environ["DATABRICKS_HTTP_PATH"]
),
schema=xyz,
parameters=[{"name":"catalog", "value": os.environ["DATABRICKS_CATALOG"]}],
credentials=TokenCredentials(
token=os.environ["DATABRICKS_PERSONAL_ACCESS_TOKEN"]
),
)
```
1 change: 1 addition & 0 deletions gooddata-sdk/gooddata_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
from gooddata_sdk.catalog.entity import (
AttrCatalogEntity,
BasicCredentials,
ClientSecretCredentials,
KeyPairCredentials,
TokenCredentialsFromEnvVar,
TokenCredentialsFromFile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ def to_test_request(
token: Optional[str] = None,
private_key: Optional[str] = None,
private_key_passphrase: Optional[str] = None,
client_id: Optional[str] = None,
client_secret: Optional[str] = None,
) -> TestDefinitionRequest:
kwargs: dict[str, Any] = {"schema": self.schema}
if password is not None:
Expand All @@ -123,6 +125,10 @@ def to_test_request(
kwargs["private_key"] = private_key
if private_key_passphrase is not None:
kwargs["private_key_passphrase"] = private_key
if client_id is not None:
kwargs["client_id"] = client_id
if client_secret is not None:
kwargs["client_secret"] = client_secret
return TestDefinitionRequest(type=self.type, url=self.url, **kwargs)

@staticmethod
Expand All @@ -141,6 +147,8 @@ def to_api(
token: Optional[str] = None,
private_key: Optional[str] = None,
private_key_passphrase: Optional[str] = None,
client_id: Optional[str] = None,
client_secret: Optional[str] = None,
) -> DeclarativeDataSource:
dictionary = self._get_snake_dict()
if password is not None:
Expand All @@ -151,6 +159,10 @@ def to_api(
dictionary["private_key"] = private_key
if private_key_passphrase is not None:
dictionary["private_key_passphrase"] = private_key_passphrase
if client_id is not None:
dictionary["client_id"] = client_id
if client_secret is not None:
dictionary["client_secret"] = client_secret
return self.client_class().from_dict(dictionary)

def store_to_disk(self, data_sources_folder: Path) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from gooddata_sdk.catalog.base import Base, value_in_allowed
from gooddata_sdk.catalog.entity import (
BasicCredentials,
ClientSecretCredentials,
Credentials,
KeyPairCredentials,
TokenCredentials,
Expand All @@ -34,6 +35,7 @@ def db_attrs_with_template(instance: CatalogDataSource, *args: Any) -> None:
class CatalogDataSourceBase(Base):
_SUPPORTED_CREDENTIALS: ClassVar[list[type[Credentials]]] = [
BasicCredentials,
ClientSecretCredentials,
TokenCredentials,
TokenCredentialsFromFile,
KeyPairCredentials,
Expand Down
22 changes: 22 additions & 0 deletions gooddata-sdk/gooddata_sdk/catalog/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ class Credentials(Base):
PASSWORD_KEY: ClassVar[str] = "password"
PRIVATE_KEY: ClassVar[str] = "private_key"
PRIVATE_KEY_PASSPHRASE: ClassVar[str] = "private_key_passphrase"
CLIENT_ID: ClassVar[str] = "client_id"
CLIENT_SECRET: ClassVar[str] = "client_secret"

def to_api_args(self) -> dict[str, Any]:
return attr.asdict(self)
Expand Down Expand Up @@ -252,3 +254,23 @@ def from_api(cls, attributes: dict[str, Any]) -> KeyPairCredentials:
# You have to fill it to keep it or update it
private_key="",
)


@attr.s(auto_attribs=True, kw_only=True)
class ClientSecretCredentials(Credentials):
client_id: str
client_secret: str = attr.field(repr=lambda value: "***")

@classmethod
def is_part_of_api(cls, entity: dict[str, Any]) -> bool:
return cls.CLIENT_ID in entity and cls.CLIENT_SECRET in entity

@classmethod
def from_api(cls, attributes: dict[str, Any]) -> ClientSecretCredentials:
# Credentials are not returned for security reasons
return cls(
client_id=attributes[cls.CLIENT_ID],
# Client secret is not returned from API (security)
# You have to fill it to keep it or update it
client_secret="",
)

0 comments on commit 293a318

Please sign in to comment.