Skip to content

Commit 9b3a04e

Browse files
feat(teams): port TeamsAuthCertificate config shape + startup throw
Ports the upstream `TeamsAuthCertificate` interface (adapter-teams/src/types.ts:3-10) as a Python dataclass and re-exports it from `chat_sdk.adapters.teams`. Updates the adapter's startup throw to match upstream (adapter-teams/src/config.ts:13-18) verbatim, including the camelCase `appPassword` reference. Pure config-shape parity — upstream does not implement cert auth either. Refs #58. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d23b6d9 commit 9b3a04e

6 files changed

Lines changed: 97 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Upstream parity
6+
7+
- **Teams: `TeamsAuthCertificate` config shape** (Issue #58). Ports the
8+
upstream `TeamsAuthCertificate` interface (`adapter-teams/src/types.ts:3-10`)
9+
as a Python dataclass with `certificate_private_key`, `certificate_thumbprint`,
10+
and `x5c` fields. `TeamsAdapterConfig(certificate=...)` is accepted and
11+
re-exported from `chat_sdk.adapters.teams` so consumers can code against the
12+
shape ahead of MS Teams SDK support. Passing a non-`None` value still throws
13+
at adapter startup — the error message is now verbatim with
14+
`adapter-teams/src/config.ts:13-18` (`"Certificate-based authentication is
15+
not yet supported by the Teams SDK adapter. Use appPassword (client secret)
16+
or federated (workload identity) authentication instead."`). Not a functional
17+
implementation; upstream does not implement cert auth either.
18+
319
## 0.4.26.1 (2026-04-23)
420

521
Python-only follow-up on `0.4.26`. Still alpha — APIs may change.
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
"""Teams adapter for chat-sdk."""
22

33
from chat_sdk.adapters.teams.adapter import TeamsAdapter, create_teams_adapter
4+
from chat_sdk.adapters.teams.types import (
5+
TeamsAdapterConfig,
6+
TeamsAuthCertificate,
7+
)
48

5-
__all__ = ["TeamsAdapter", "create_teams_adapter"]
9+
__all__ = [
10+
"TeamsAdapter",
11+
"TeamsAdapterConfig",
12+
"TeamsAuthCertificate",
13+
"create_teams_adapter",
14+
]

src/chat_sdk/adapters/teams/adapter.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,13 @@ def __init__(self, config: TeamsAdapterConfig | None = None) -> None:
162162
self._app_password = config.app_password or os.environ.get("TEAMS_APP_PASSWORD", "")
163163
self._app_tenant_id = config.app_tenant_id or os.environ.get("TEAMS_APP_TENANT_ID", "")
164164

165-
if config.certificate:
165+
if config.certificate is not None:
166+
# Exact parity with upstream adapter-teams/src/config.ts:13-18.
167+
# ``appPassword`` is referenced in camelCase to match upstream text.
166168
raise ValidationError(
167169
"teams",
168-
"Certificate-based authentication is not yet supported. "
169-
"Use app_password (client secret) or federated (workload identity) authentication instead.",
170+
"Certificate-based authentication is not yet supported by the Teams SDK adapter. "
171+
"Use appPassword (client secret) or federated (workload identity) authentication instead.",
170172
)
171173

172174
if not self._app_id:

src/chat_sdk/adapters/teams/types.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,24 @@
1616
# =============================================================================
1717

1818

19-
class TeamsAuthCertificate(TypedDict, total=False):
20-
"""Certificate-based authentication config (not yet supported)."""
19+
@dataclass
20+
class TeamsAuthCertificate:
21+
"""Certificate-based authentication config.
22+
23+
.. deprecated::
24+
Certificate auth is not yet supported by the Teams SDK. Setting
25+
``certificate`` on :class:`TeamsAdapterConfig` raises at adapter
26+
startup. Ported for shape parity with upstream
27+
``adapter-teams/src/types.ts`` so consumers can code against the
28+
config shape ahead of MS Teams SDK support.
29+
"""
2130

2231
# PEM-encoded certificate private key
2332
certificate_private_key: str
2433
# Hex-encoded certificate thumbprint (optional when x5c is provided)
25-
certificate_thumbprint: str
34+
certificate_thumbprint: str | None = None
2635
# Public certificate for subject-name validation (optional)
27-
x5c: str
36+
x5c: str | None = None
2837

2938

3039
class TeamsAuthFederated(TypedDict, total=False):
@@ -53,7 +62,9 @@ class TeamsAdapterConfig:
5362
app_tenant_id: str | None = None
5463
# Microsoft App Type.
5564
app_type: str | None = None # "MultiTenant" | "SingleTenant"
56-
# Certificate auth (not yet supported by the Teams SDK).
65+
# Deprecated: certificate auth is not yet supported by the Teams SDK.
66+
# Passing a non-None value raises at adapter startup — kept for shape
67+
# parity with upstream adapter-teams/src/types.ts.
5768
certificate: TeamsAuthCertificate | None = None
5869
# Federated (workload identity) authentication.
5970
federated: TeamsAuthFederated | None = None

tests/test_teams_coverage.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,11 @@
2727
TeamsAdapter,
2828
_validate_service_url,
2929
)
30-
from chat_sdk.adapters.teams.types import TeamsAdapterConfig, TeamsThreadId
30+
from chat_sdk.adapters.teams.types import (
31+
TeamsAdapterConfig,
32+
TeamsAuthCertificate,
33+
TeamsThreadId,
34+
)
3135
from chat_sdk.shared.errors import (
3236
AuthenticationError,
3337
NetworkError,
@@ -1490,7 +1494,10 @@ def test_certificate_raises_validation_error(self):
14901494
TeamsAdapterConfig(
14911495
app_id="test",
14921496
app_password="test",
1493-
certificate={"thumbprint": "abc", "private_key": "key"},
1497+
certificate=TeamsAuthCertificate(
1498+
certificate_private_key="key",
1499+
certificate_thumbprint="abc",
1500+
),
14941501
)
14951502
)
14961503

tests/test_teams_extended.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@
2828
TeamsAdapter,
2929
_handle_teams_error,
3030
)
31-
from chat_sdk.adapters.teams.types import TeamsAdapterConfig, TeamsThreadId
31+
from chat_sdk.adapters.teams.types import (
32+
TeamsAdapterConfig,
33+
TeamsAuthCertificate,
34+
TeamsThreadId,
35+
)
3236
from chat_sdk.shared.errors import (
3337
AdapterPermissionError,
3438
AdapterRateLimitError,
@@ -542,10 +546,45 @@ def test_raises_validation_error(self):
542546
TeamsAdapterConfig(
543547
app_id="app",
544548
app_password="pass",
545-
certificate={"certificate_private_key": "key", "certificate_thumbprint": "thumb"},
549+
certificate=TeamsAuthCertificate(
550+
certificate_private_key="key",
551+
certificate_thumbprint="thumb",
552+
),
546553
)
547554
)
548555

556+
def test_raises_with_exact_upstream_message(self):
557+
"""Startup throw message matches upstream adapter-teams/src/config.ts:13-18 verbatim.
558+
559+
Upstream references ``appPassword`` (camelCase TS field name); we preserve
560+
that in the error text so consumers tailing upstream logs see identical
561+
output. Protects against well-meaning rewording to ``app_password``.
562+
"""
563+
expected = (
564+
"Certificate-based authentication is not yet supported by the Teams SDK adapter. "
565+
"Use appPassword (client secret) or federated (workload identity) authentication instead."
566+
)
567+
with pytest.raises(ValidationError) as exc_info:
568+
TeamsAdapter(
569+
TeamsAdapterConfig(
570+
certificate=TeamsAuthCertificate(certificate_private_key="key"),
571+
)
572+
)
573+
assert expected in str(exc_info.value)
574+
575+
def test_minimal_certificate_only_requires_private_key(self):
576+
"""``certificate_thumbprint`` and ``x5c`` are optional per upstream types.ts:7-9.
577+
578+
A ``TeamsAuthCertificate`` constructed with only ``certificate_private_key``
579+
must still trigger the startup throw (i.e. the adapter checks presence, not
580+
shape).
581+
"""
582+
cert = TeamsAuthCertificate(certificate_private_key="pem-key")
583+
assert cert.certificate_thumbprint is None
584+
assert cert.x5c is None
585+
with pytest.raises(ValidationError, match="Certificate-based"):
586+
TeamsAdapter(TeamsAdapterConfig(certificate=cert))
587+
549588

550589
# ---------------------------------------------------------------------------
551590
# Stream via post+edit

0 commit comments

Comments
 (0)