8000 Merge with master · itsmokha/botbuilder-python@d2982c9 · GitHub
[go: up one dir, main page]

Skip to content

Commit d2982c9

Browse files
committed
Merge with master
2 parents 6f57afe + 259493c commit d2982c9

File tree

6 files changed

+96
-42
lines changed

6 files changed

+96
-42
lines changed

libraries/botframework-connector/botframework/connector/auth/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
# pylint: disable=missing-docstring
1212

1313
from .microsoft_app_credentials import *
14+
from .claims_identity import *
1415
from .jwt_token_validation import *
1516
from .credential_provider import *
1617
from .channel_validation import *
@@ -20,3 +21,4 @@
2021
from .authentication_constants import *
2122
from .channel_provider import *
2223
from .simple_channel_provider import *
24+
from .authentication_configuration import *
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
from typing import List
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from typing import Awaitable, Callable, Dict, List
25

36

47
class AuthenticationConfiguration:
5-
def __init__(self, required_endorsements: List[str] = None):
8+
def __init__(
9+
self,
10+
required_endorsements: List[str] = None,
11+
claims_validator: Callable[[List[Dict]], Awaitable] = None,
12+
):
613
self.required_endorsements = required_endorsements or []
14+
self.claims_validator = claims_validator

libraries/botframework-connector/botframework/connector/auth/endorsements_validator.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
class EndorsementsValidator:
88
@staticmethod
9-
def validate(channel_id: str, endorsements: List[str]):
9+
def validate(expected_endorsement: str, endorsements: List[str]):
1010
# If the Activity came in and doesn't have a Channel ID then it's making no
1111
# assertions as to who endorses it. This means it should pass.
12-
if not channel_id:
12+
if not expected_endorsement:
1313
return True
1414

1515
if endorsements is None:
@@ -31,5 +31,5 @@ def validate(channel_id: str, endorsements: List[str]):
3131
# of scope, tokens from WebChat have about 10 endorsements, and
3232
# tokens coming from Teams have about 20.
3333

34-
endorsement_present = channel_id in endorsements
34+
endorsement_present = expected_endorsement in endorsements
3535
return endorsement_present

libraries/botframework-connector/botframework/connector/auth/jwt_token_extractor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ async def get_identity(
4949
self,
5050
schema: str,
5151
parameter: str,
52-
channel_id,
52+
channel_id: str,
5353
required_endorsements: List[str] = None,
5454
) -> ClaimsIdentity:
5555
# No header in correct scheme or no token

libraries/botframework-connector/botframework/connector/auth/jwt_token_validation.py

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3-
from typing import Dict
3+
from typing import Dict, List
44

55
from botbuilder.schema import Activity
66

@@ -73,63 +73,82 @@ async def validate_auth_header(
7373
if not auth_header:
7474
raise ValueError("argument auth_header is null")
7575

76-
if SkillValidation.is_skill_token(auth_header):
77-
return await SkillValidation.authenticate_channel_token(
78-
auth_header,
79-
credentials,
80-
channel_service,
81-
channel_id,
82-
auth_configuration,
83-
)
84-
85-
if EmulatorValidation.is_token_from_emulator(auth_header):
86-
return await EmulatorValidation.authenticate_emulator_token(
87-
auth_header, credentials, channel_service, channel_id
88-
)
89-
90-
# If the channel is Public Azure
91-
if not channel_service:
92-
if service_url:
93-
return await ChannelValidation.authenticate_channel_token_with_service_url(
76+
async def get_claims() -> ClaimsIdentity:
77+
if SkillValidation.is_skill_token(auth_header):
78+
return await SkillValidation.authenticate_channel_token(
9479
auth_header,
9580
credentials,
96-
service_url,
81+
channel_service,
9782
channel_id,
9883
auth_configuration,
9984
)
10085

101-
return await ChannelValidation.authenticate_channel_token(
102-
auth_header, credentials, channel_id, auth_configuration
103-
)
86+
if EmulatorValidation.is_token_from_emulator(auth_header):
87+
return await EmulatorValidation.authenticate_emulator_token(
88+
auth_header, credentials, channel_service, channel_id
89+
)
90+
91+
# If the channel is Public Azure
92+
if not channel_service:
93+
if service_url:
94+
return await ChannelValidation.authenticate_channel_token_with_service_url(
95+
auth_header,
96+
credentials,
97+
service_url,
98+
channel_id,
99+
auth_configuration,
100+
)
101+
102+
return await ChannelValidation.authenticate_channel_token(
103+
auth_header, credentials, channel_id, auth_configuration
104+
)
104105

105-
if JwtTokenValidation.is_government(channel_service):
106+
if JwtTokenValidation.is_government(channel_service):
107+
if service_url:
108+
return await GovernmentChannelValidation.authenticate_channel_token_with_service_url(
109+
auth_header,
110+
credentials,
111+
service_url,
112+
channel_id,
113+
auth_configuration,
114+
)
115+
116+
return await GovernmentChannelValidation.authenticate_channel_token(
117+
auth_header, credentials, channel_id, auth_configuration
118+
)
119+
120+
# Otherwise use Enterprise Channel Validation
106121
if service_url:
107-
return await GovernmentChannelValidation.authenticate_channel_token_with_service_url(
122+
return await EnterpriseChannelValidation.authenticate_channel_token_with_service_url(
108123
auth_header,
109124
credentials,
110125
service_url,
111126
channel_id,
127+
channel_service,
112128
auth_configuration,
113129
)
114130

115-
return await GovernmentChannelValidation.authenticate_channel_token(
116-
auth_header, credentials, channel_id, auth_configuration
117-
)
118-
119-
# Otherwise use Enterprise Channel Validation
120-
if service_url:
121-
return await EnterpriseChannelValidation.authenticate_channel_token_with_service_url(
131+
return await EnterpriseChannelValidation.authenticate_channel_token(
122132
auth_header,
123133
credentials,
124-
service_url,
125134
channel_id,
126135
channel_service,
127136
auth_configuration,
128137
)
129138

130-
return await EnterpriseChannelValidation.authenticate_channel_token(
131-
auth_header, credentials, channel_id, channel_service, auth_configuration
132-
)
139+
claims = await get_claims()
140+
141+
if claims:
142+
await JwtTokenValidation.validate_claims(auth_configuration, claims.claims)
143+
144+
return claims
145+
146+
@staticmethod
147+
async def validate_claims(
148+
auth_config: AuthenticationConfiguration, claims: List[Dict]
149+
):
150+
if auth_config and auth_config.claims_validator:
151+
await auth_config.claims_validator(claims)
133152

134153
@staticmethod
135154
def is_government(channel_service: str) -> bool:

libraries/botframework-connector/tests/test_auth.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33
import uuid
4+
from typing import Dict, List
5+
from unittest.mock import Mock
6+
47
import pytest
58

69
from botbuilder.schema import Activity
710
from botframework.connector.auth import (
11+
AuthenticationConfiguration,
812
AuthenticationConstants,
913
JwtTokenValidation,
1014
SimpleCredentialProvider,
@@ -40,6 +44,27 @@ class TestAuth:
4044
True
4145
)
4246

47+
@pytest.mark.asyncio
48+
async def test_claims_validation(self):
49+
claims: List[Dict] = []
50+
default_auth_config = AuthenticationConfiguration()
51+
52+
# No validator should pass.
53+
await JwtTokenValidation.validate_claims(default_auth_config, claims)
54+
55+
# ClaimsValidator configured but no exception should pass.
56+
mock_validator = Mock()
57+
auth_with_validator = AuthenticationConfiguration(
58+
claims_validator=mock_validator
59+
)
60+
61+
# Configure IClaimsValidator to fail
62+
mock_validator.side_effect = PermissionError("Invalid claims.")
63+
with pytest.raises(PermissionError) as excinfo:
64+
await JwtTokenValidation.validate_claims(auth_with_validator, claims)
65+
66+
assert "Invalid claims." in str(excinfo.value)
67+
4368
@pytest.mark.asyncio
4469
async def test_connector_auth_header_correct_app_id_and_service_url_should_validate(
4570
self,

0 commit comments

Comments
 (0)
0