diff --git a/outlook_web/controllers/token_tool.py b/outlook_web/controllers/token_tool.py index 73d02915..9289ef4a 100644 --- a/outlook_web/controllers/token_tool.py +++ b/outlook_web/controllers/token_tool.py @@ -215,8 +215,9 @@ def save_to_account() -> Any: if compatibility_error: return build_error_response("OAUTH_CONFIG_INVALID", compatibility_error, status=400) - validation_scope = (data.get("scope") or "").strip() or COMPATIBLE_SCOPE - if validation_scope == LEGACY_GRAPH_SCOPE: + explicit_scope = (data.get("scope") or data.get("requested_scope") or data.get("granted_scope") or "").strip() + validation_scope = explicit_scope or COMPATIBLE_SCOPE + if not explicit_scope and validation_scope == LEGACY_GRAPH_SCOPE: validation_scope = COMPATIBLE_SCOPE valid, error_msg, new_rt = graph_service.test_refresh_token_with_rotation( diff --git a/static/js/features/token_tool.js b/static/js/features/token_tool.js index c6fde9ef..02d96ecd 100644 --- a/static/js/features/token_tool.js +++ b/static/js/features/token_tool.js @@ -434,6 +434,7 @@ async function confirmSaveToAccount() { mode, refresh_token: resultData.refresh_token, client_id: resultData.client_id, + scope: resultData.requested_scope || resultData.granted_scope || '', }; if (mode === 'update') { diff --git a/tests/test_oauth_tool.py b/tests/test_oauth_tool.py index 9e62c664..cb43a67e 100644 --- a/tests/test_oauth_tool.py +++ b/tests/test_oauth_tool.py @@ -898,7 +898,31 @@ def test_save_uses_consumers_and_imap_scope_for_validation(self, mock_test_rt): ) @patch("outlook_web.services.graph.test_refresh_token_with_rotation") - def test_save_maps_legacy_graph_scope_to_imap_validation_scope(self, mock_test_rt): + def test_save_uses_explicit_token_scope_for_validation(self, mock_test_rt): + mock_test_rt.return_value = (True, None, None) + with self.app.test_client() as client: + self._login(client) + resp = client.post( + "/api/token-tool/save", + json={ + "mode": "create", + "email": "explicit-scope@oauth-test.com", + "client_id": "new-cid", + "refresh_token": "new-rt", + "scope": "Mail.Read User.Read offline_access openid profile", + }, + ) + self.assertEqual(resp.status_code, 200) + + _args, kwargs = mock_test_rt.call_args + self.assertEqual(kwargs.get("tenant"), "consumers") + self.assertEqual( + kwargs.get("scope"), + "Mail.Read User.Read offline_access openid profile", + ) + + @patch("outlook_web.services.graph.test_refresh_token_with_rotation") + def test_save_preserves_explicit_graph_default_scope_for_validation(self, mock_test_rt): mock_test_rt.return_value = (True, None, None) with self.app.test_client() as client: self._login(client) @@ -918,7 +942,7 @@ def test_save_maps_legacy_graph_scope_to_imap_validation_scope(self, mock_test_r self.assertEqual(kwargs.get("tenant"), "consumers") self.assertEqual( kwargs.get("scope"), - "offline_access https://outlook.office.com/IMAP.AccessAsUser.All", + "offline_access https://graph.microsoft.com/.default", ) @patch("outlook_web.services.graph.test_refresh_token_with_rotation")