Skip to content

FIX: Add ActiveDirectoryMSI support for bulk copy#573

Open
bewithgaurav wants to merge 1 commit into
mainfrom
bewithgaurav/gh534-bulkcopy-msi
Open

FIX: Add ActiveDirectoryMSI support for bulk copy#573
bewithgaurav wants to merge 1 commit into
mainfrom
bewithgaurav/gh534-bulkcopy-msi

Conversation

@bewithgaurav
Copy link
Copy Markdown
Collaborator

@bewithgaurav bewithgaurav commented May 12, 2026

What

Adds Authentication=ActiveDirectoryMSI support to bulk copy. Partial fix for #534.

  • Zero-arg ManagedIdentityCredential() for system-assigned MSI
  • ManagedIdentityCredential(client_id=UID) for user-assigned MSI — matches ODBC's convention where UID carries the identity's client_id under MSI
  • Threads optional credential_kwargs through get_auth_token / get_raw_token / _acquire_token so future auth methods that need constructor args plug in via the same channel
  • Credential cache key stays a plain string for zero-arg auth types and becomes a tuple (auth_type, sorted_kwargs) when kwargs are present, so different client_ids get separate cached credentials

Why

mssql-py-core (the Rust path used by bulk copy) doesn't itself acquire Entra tokens — Python has to pre-acquire a JWT and pass it as access_token. Today this works for Default, DeviceCode, and Interactive. MSI was missing, which is the most common Azure-hosted-service auth method.

ServicePrincipal and Password are explicitly out of scope for this PR — they need different design work.

Files

File Change
mssql_python/constants.py Add AuthType.MSI = "activedirectorymsi"
mssql_python/auth.py Add ManagedIdentityCredential to credential map, thread credential_kwargs through _acquire_token / get_token / get_raw_token / get_auth_token, add _extract_msi_client_id and public extract_credential_kwargs helpers, update cache key
mssql_python/cursor.py Bulk copy path: extract client_id from connection string and pass to get_raw_token
tests/test_008_auth.py Mock ManagedIdentityCredential in fixture, 10 new tests covering enum, parser, credential construction, cache isolation, and end-to-end process_connection_string
CHANGELOG.md Unreleased entry

Test

$ python -m pytest tests/test_008_auth.py -q
58 passed in 0.08s

10 new tests:

  • test_auth_type_constants extended for AuthType.MSI
  • TestProcessAuthParameters::test_msi_auth, test_msi_auth_case_insensitive
  • TestExtractAuthType::test_msi
  • TestManagedIdentity::test_get_token_system_assigned_msi
  • TestManagedIdentity::test_get_raw_token_system_assigned_msi
  • TestManagedIdentity::test_get_token_user_assigned_msi
  • TestManagedIdentity::test_msi_separate_cache_entries_per_client_id
  • TestManagedIdentity::test_extract_credential_kwargs_system_assigned / _user_assigned / _non_msi / _empty_uid
  • TestManagedIdentity::test_process_connection_string_msi_strips_uid

Connection string examples

# System-assigned MSI
"Server=tcp:foo.database.windows.net;Authentication=ActiveDirectoryMSI;Database=mydb;"

# User-assigned MSI
"Server=tcp:foo.database.windows.net;Authentication=ActiveDirectoryMSI;UID=<client_id_guid>;Database=mydb;"

Adds Authentication=ActiveDirectoryMSI to the auth pipeline:

- Zero-arg ManagedIdentityCredential() for system-assigned MSI.
- ManagedIdentityCredential(client_id=UID) for user-assigned MSI,
  matching ODBC's convention where UID carries the identity's
  client_id under MSI.
- Threads optional credential_kwargs through get_auth_token /
  get_raw_token / _acquire_token so future auth methods that need
  constructor args (e.g. ClientSecretCredential) can plug in via
  the same channel.
- Cache key remains a plain string for zero-arg auth types and
  becomes a tuple when kwargs are present, so different client_ids
  get separate cached credentials.

Partial fix for #534. ServicePrincipal and
Password to follow as separate PRs.
Copilot AI review requested due to automatic review settings May 12, 2026 12:09
@bewithgaurav bewithgaurav changed the title Add ActiveDirectoryMSI support for bulk copy FIX: Add ActiveDirectoryMSI support for bulk copy May 12, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for Authentication=ActiveDirectoryMSI (managed identity) token acquisition for the bulk copy (mssql-py-core) path, including user-assigned MSI via UID=<client_id>.

Changes:

  • Add AuthType.MSI (activedirectorymsi) and map it to ManagedIdentityCredential.
  • Thread optional credential_kwargs through token acquisition and update the credential-instance cache key to incorporate kwargs.
  • Attempt to extract MSI client_id from the connection string and pass it into bulk copy token acquisition; add tests and a changelog entry.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
mssql_python/constants.py Adds AuthType.MSI enum value.
mssql_python/auth.py Adds MSI credential support, credential kwargs plumbing, and cache key changes; adds extract_credential_kwargs.
mssql_python/cursor.py Bulk copy now tries to extract MSI credential kwargs and pass them to get_raw_token.
tests/test_008_auth.py Adds tests for MSI auth type parsing, credential construction, cache isolation, and connection-string processing.
CHANGELOG.md Documents MSI support for bulk copy.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mssql_python/cursor.py
Comment on lines +2915 to +2923
from mssql_python.auth import AADAuth, extract_credential_kwargs

credential_kwargs = extract_credential_kwargs(
self.connection.connection_str, self.connection._auth_type
)
try:
raw_token = AADAuth.get_raw_token(self.connection._auth_type)
raw_token = AADAuth.get_raw_token(
self.connection._auth_type, credential_kwargs or None
)
Comment thread mssql_python/auth.py
Comment on lines +302 to +313
def extract_credential_kwargs(
connection_string: str, auth_type: Optional[str]
) -> Dict[str, str]:
"""Extract credential constructor kwargs for the given auth type.

For ActiveDirectoryMSI: returns ``{"client_id": uid}`` when UID is
set (user-assigned MSI) and ``{}`` for system-assigned MSI.
"""
if auth_type != "msi":
return {}
client_id = _extract_msi_client_id(connection_string.split(";"))
return {"client_id": client_id} if client_id else {}
Comment thread tests/test_008_auth.py
Comment on lines +528 to +529


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants