-
Notifications
You must be signed in to change notification settings - Fork 176
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PY] feat: SSO - Deduplication (#1890)
## Linked issues closes: #1873 ## Details This PR focuses on the sso_dialog operations. Specifically dialogs use a 2 step waterfall process to manage token acquisition and deduplication. These features generally exist in the JS version of this process, but are handled pretty differently there. Some tests are written to provide basic coverage of the file sso_dialog.py. #### Change details Extended the file sso_dialog.py Added test_sso_dialog.py General fmt, lint and option config adjustments. **code snippets**: **screenshots**: ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (updating the doc strings in the code is sufficient) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes ### Additional information > Feel free to add other relevant information below
- Loading branch information
1 parent
4333547
commit ac0f3df
Showing
4 changed files
with
154 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
""" | ||
Copyright (c) Microsoft Corporation. All rights reserved. | ||
Licensed under the MIT License. | ||
""" | ||
|
||
import unittest | ||
from unittest.mock import AsyncMock, MagicMock | ||
|
||
from botbuilder.dialogs import DialogTurnResult, DialogTurnStatus | ||
from msal import ConfidentialClientApplication | ||
|
||
from teams.auth import ConfidentialClientApplicationOptions, SsoDialog, SsoOptions | ||
from teams.state import TurnState | ||
|
||
|
||
class TestSsoDialog(unittest.IsolatedAsyncioTestCase): | ||
async def asyncSetUp(self): | ||
self.storage_mock = AsyncMock() | ||
self.msal_config = ConfidentialClientApplicationOptions( | ||
client_id="client_id", | ||
authority="https://login.microsoftonline.com/common", | ||
client_secret="client_secret", | ||
) | ||
|
||
self.options = SsoOptions( | ||
scopes=["User.Read"], | ||
msal_config=self.msal_config, | ||
sign_in_link="https://login.microsoftonline.com/common/oauth2/v2.0/authorize", | ||
timeout=900000, | ||
end_on_invalid_message=True, | ||
storage=self.storage_mock, | ||
) | ||
|
||
self.msal_app = MagicMock(spec=ConfidentialClientApplication) | ||
self.sso_dialog = SsoDialog("test_sso", self.options, self.msal_app) | ||
|
||
self.context = self.create_mock_context() | ||
self.state = await TurnState.load(self.context) | ||
|
||
def create_mock_context(self): | ||
context = MagicMock() | ||
activity = MagicMock() | ||
activity.type = "message" | ||
activity.text = "dummy_text" | ||
activity.channel_id = "msteams" | ||
activity.from_property = MagicMock(id="user_id", aad_object_id="aad_object_id") | ||
activity.conversation = MagicMock(id="conversation_id") | ||
activity.value = {"token": "dummy_token", "id": "dummy_id"} | ||
context.activity = activity | ||
return context | ||
|
||
async def test_step_one(self): | ||
waterfall_step_context = MagicMock() | ||
waterfall_step_context.begin_dialog = AsyncMock( | ||
return_value=DialogTurnResult(DialogTurnStatus.Waiting) | ||
) | ||
|
||
result = await self.sso_dialog._step_one(waterfall_step_context) | ||
|
||
waterfall_step_context.begin_dialog.assert_called_once_with("TeamsSsoPrompt") | ||
self.assertEqual(result.status, DialogTurnStatus.Waiting) | ||
|
||
async def test_step_two_no_dedup_conflict(self): | ||
waterfall_step_context = MagicMock() | ||
waterfall_step_context.result = {"token": "new_access_token"} | ||
|
||
class TempState: | ||
duplicate_token_exchange = False | ||
|
||
waterfall_step_context.context.state.temp = TempState() | ||
|
||
self.sso_dialog._should_dedup = AsyncMock(return_value=True) | ||
waterfall_step_context.end_dialog = AsyncMock( | ||
return_value=DialogTurnResult(DialogTurnStatus.Waiting) | ||
) | ||
|
||
result = await self.sso_dialog._step_two(waterfall_step_context) | ||
|
||
self.sso_dialog._should_dedup.assert_called_once_with(waterfall_step_context.context) | ||
self.assertFalse(waterfall_step_context.context.state.temp.duplicate_token_exchange) | ||
self.assertEqual(result.status, DialogTurnStatus.Waiting) | ||
|
||
async def test_step_two_dedup_conflict(self): | ||
waterfall_step_context = MagicMock() | ||
waterfall_step_context.result = {"token": "new_access_token"} | ||
|
||
class TempState: | ||
duplicate_token_exchange = True | ||
|
||
waterfall_step_context.context.state.temp = TempState() | ||
|
||
self.sso_dialog._should_dedup = AsyncMock(return_value=True) | ||
waterfall_step_context.end_dialog = AsyncMock( | ||
return_value=DialogTurnResult(DialogTurnStatus.Waiting) | ||
) | ||
|
||
result = await self.sso_dialog._step_two(waterfall_step_context) | ||
|
||
self.sso_dialog._should_dedup.assert_called_once_with(waterfall_step_context.context) | ||
self.assertTrue(waterfall_step_context.context.state.temp.duplicate_token_exchange) | ||
self.assertEqual(result.status, DialogTurnStatus.Waiting) |