Skip to content

Commit

Permalink
Testing OIDC
Browse files Browse the repository at this point in the history
  • Loading branch information
prdpsvs committed Nov 26, 2024
1 parent fbe564d commit 46f0559
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 49 deletions.
103 changes: 58 additions & 45 deletions .github/workflows/integration-tests-azure.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
---
name: Integration tests on Fabric DW
on: # yamllint disable-line rule:truthy
on: # yamllint disable-line rule:truthy
workflow_dispatch:
pull_request:
branches:
- oidc_connect



jobs:
integration-tests-fabric-dw:
name: Regular
strategy:
fail-fast: false
max-parallel: 1
matrix:
profile: ["ci_azure_auto"]
profile: ["integration_tests"]
python_version: ["3.11"]
msodbc_version: ["17", "18"]
msodbc_version: ["18"]

runs-on: ubuntu-latest
permissions:
Expand All @@ -27,49 +24,65 @@ jobs:
container:
image: ghcr.io/${{ github.repository }}:CI-${{ matrix.python_version }}-msodbc${{ matrix.msodbc_version }}
steps:
# Azure login using federated credentials
- name: Azure login with OIDC
uses: azure/login@v2
with:
client-id: ${{ secrets.DBT_AZURE_SP_NAME }}
tenant-id: ${{ secrets.DBT_AZURE_TENANT }}
allow-no-subscriptions: true
federated-token: true
# Azure login using federated credentials
- name: Azure login with OIDC
uses: azure/login@v2
with:
client-id: ${{ secrets.DBT_AZURE_SP_NAME }}
tenant-id: ${{ secrets.DBT_AZURE_TENANT }}
allow-no-subscriptions: true
federated-token: true

- name: Connect to Fabric Warehouse to Retrieve Token
id: fetch_token
run: |
pip install azure-identity pyodbc azure-core
python - <<EOF
from azure.core.credentials import AccessToken
from azure.identity import DefaultAzureCredential
import pyodbc
import logging
import struct
try:
credential = DefaultAzureCredential()
token = credential.get_token("https://database.windows.net/.default")
- name: Connect to Azure SQL Database
run: |
pip install azure-identity pyodbc azure-core
connection_string = (
"Driver={ODBC Driver 18 for SQL Server};"
"Server=x6eps4xrq2xudenlfv6naeo3i4-6xw4uystlgdevluyqmndlcagwe.msit-datawarehouse.fabric.microsoft.com;"
"Database=permissionstest;"
)
print(f"::set-output name=access_token::{token.token}")
access_token_utf16 = token.token.encode('utf-16-le')
token_struct = struct.pack(f'<I{len(access_token_utf16)}s', len(access_token_utf16), access_token_utf16)
SQL_COPT_SS_ACCESS_TOKEN = 1256 # This connection option is defined by microsoft in msodbcsql.h
connection = pyodbc.connect(connection_string, attrs_before={SQL_COPT_SS_ACCESS_TOKEN: token_struct})
python - <<EOF
from azure.core.credentials import AccessToken
from azure.identity import DefaultAzureCredential
import pyodbc
import logging
import struct
try:
credential = DefaultAzureCredential()
token = credential.get_token("https://database.windows.net/.default")
cursor = connection.cursor()
connection_string = (
"Driver={ODBC Driver 18 for SQL Server};"
"Server=x6eps4xrq2xudenlfv6naeo3i4-6xw4uystlgdevluyqmndlcagwe.msit-datawarehouse.fabric.microsoft.com;"
"Database=permissionstest;"
)
cursor.execute("SELECT TOP 10 * FROM dbo.Trip")
rows = cursor.fetchall()
for row in rows:
print(row)
access_token = token.token.encode('utf-16-le')
token_struct = struct.pack(f'<I{len(access_token)}s', len(access_token), access_token)
SQL_COPT_SS_ACCESS_TOKEN = 1256 # This connection option is defined by microsoft in msodbcsql.h
connection = pyodbc.connect(connection_string, attrs_before={SQL_COPT_SS_ACCESS_TOKEN: token_struct})
connection.close()
except pyodbc.Error as e:
logging.error("Error occurred while connecting to the database.", exc_info=True)
EOF
print("token is ", token.token)
cursor = connection.cursor()
- uses: actions/checkout@v4

cursor.execute("SELECT TOP 10 * FROM dbo.Trip")
rows = cursor.fetchall()
for row in rows:
print(row)
- name: Install dependencies
run: pip install -r dev_requirements.txt

connection.close()
except pyodbc.Error as e:
logging.error("Error occurred while connecting to the database.", exc_info=True)
EOF
- name: Run functional tests
env:
DBT_AZURESQL_SERVER: ${{ secrets.DBT_AZURESQL_SERVER }}
DBT_AZURESQL_DB: ${{ secrets.DBT_AZURESQL_DB }}
FABRIC_INTEGRATION_TESTS_TOKEN: ${{ steps.fetch_token.outputs.access_token }}
FABRIC_TEST_DRIVER: 'ODBC Driver ${{ matrix.msodbc_version }} for SQL Server'
DBT_TEST_USER_1: dbo
DBT_TEST_USER_2: dbo
DBT_TEST_USER_3: dbo
run: pytest -ra -v tests/functional/adapter/test_empty.py --profile "${{ matrix.profile }}"
40 changes: 37 additions & 3 deletions dbt/adapters/fabric/fabric_connection_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def get_environment_access_token(credentials: FabricCredentials) -> AccessToken:
}


def get_pyodbc_attrs_before(credentials: FabricCredentials) -> Dict:
def get_pyodbc_attrs_before_credentials(credentials: FabricCredentials) -> Dict:
"""
Get the pyodbc attrs before.
Expand Down Expand Up @@ -220,6 +220,36 @@ def get_pyodbc_attrs_before(credentials: FabricCredentials) -> Dict:
return attrs_before


def get_pyodbc_attrs_before_accesstoken(accessToken: str) -> Dict:
"""
Get the pyodbc attrs before.
Parameters
----------
credentials : Access Token for Integration Tests
Credentials.
Returns
-------
out : Dict
The pyodbc attrs before.
Source
------
Authentication for SQL server with an access token:
https://docs.microsoft.com/en-us/sql/connect/odbc/using-azure-active-directory?view=sql-server-ver15#authenticating-with-an-access-token
"""

access_token_utf16 = accessToken.encode("utf-16-le")
token_struct = struct.pack(
f"<I{len(access_token_utf16)}s", len(access_token_utf16), access_token_utf16
)
sql_copt_ss_access_token = 1256 # see source in docstring
attrs_before = {sql_copt_ss_access_token: token_struct}

return attrs_before


def bool_to_connection_string_arg(key: str, value: bool) -> str:
"""
Convert a boolean to a connection string argument.
Expand Down Expand Up @@ -323,7 +353,7 @@ def open(cls, connection: Connection) -> Connection:

con_str.append(f"Database={credentials.database}")

#Enabling trace flag
# Enabling trace flag
if credentials.trace_flag:
con_str.append("SQL_ATTR_TRACE=SQL_OPT_TRACE_ON")
else:
Expand Down Expand Up @@ -395,7 +425,11 @@ def open(cls, connection: Connection) -> Connection:
def connect():
logger.debug(f"Using connection string: {con_str_display}")

attrs_before = get_pyodbc_attrs_before(credentials)
if credentials.authentication == "ActiveDirectoryAccessToken":
attrs_before = get_pyodbc_attrs_before_accesstoken(credentials.access_token)
else:
attrs_before = get_pyodbc_attrs_before_credentials(credentials)

handle = pyodbc.connect(
con_str_concat,
attrs_before=attrs_before,
Expand Down
1 change: 1 addition & 0 deletions dbt/adapters/fabric/fabric_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class FabricCredentials(Credentials):
tenant_id: Optional[str] = None
client_id: Optional[str] = None
client_secret: Optional[str] = None
access_token: Optional[str] = None
authentication: Optional[str] = "ActiveDirectoryServicePrincipal"
encrypt: Optional[bool] = True # default value in MS ODBC Driver 18 as well
trust_cert: Optional[bool] = False # default value in MS ODBC Driver 18 as well
Expand Down
19 changes: 18 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def dbt_profile_target(request: FixtureRequest, dbt_profile_target_update):
target = _profile_ci_azure_environment()
elif profile == "user_azure":
target = _profile_user_azure()
elif profile == "integration_tests":
target = _profile_integration_tests()
else:
raise ValueError(f"Unknown profile: {profile}")

Expand Down Expand Up @@ -55,7 +57,7 @@ def _profile_ci_azure_base():
"database": os.getenv("DBT_AZURESQL_DB"),
"encrypt": True,
"trust_cert": True,
"trace_flag":False,
"trace_flag": False,
},
}

Expand Down Expand Up @@ -104,6 +106,21 @@ def _profile_user_azure():
return profile


def _profile_integration_tests():
profile = {
**_all_profiles_base(),
**{
"host": os.getenv("FABRIC_TEST_HOST"),
"authentication": os.getenv("FABRIC_TEST_AUTH", "ActiveDirectoryAccessToken"),
"encrypt": True,
"trust_cert": True,
"database": os.getenv("FABRIC_TEST_DBNAME"),
"access_token": os.getenv("FABRIC_INTEGRATION_TESTS_TOKEN"),
},
}
return profile


@pytest.fixture(autouse=True)
def skip_by_profile_type(request: FixtureRequest):
profile_type = request.config.getoption("--profile")
Expand Down

0 comments on commit 46f0559

Please sign in to comment.