|
26 | 26 | from unittest.mock import ANY, AsyncMock, Mock |
27 | 27 | from urllib.parse import parse_qs |
28 | 28 |
|
| 29 | +from parameterized import parameterized_class |
29 | 30 | from signedjson.key import ( |
30 | 31 | encode_verify_key_base64, |
31 | 32 | generate_signing_key, |
|
48 | 49 | from synapse.rest import admin |
49 | 50 | from synapse.rest.client import account, devices, keys, login, logout, register |
50 | 51 | from synapse.server import HomeServer |
51 | | -from synapse.types import JsonDict, UserID |
| 52 | +from synapse.types import JsonDict, UserID, create_requester |
52 | 53 | from synapse.util import Clock |
53 | 54 |
|
54 | 55 | from tests.server import FakeChannel |
@@ -109,12 +110,7 @@ async def get_json(url: str) -> JsonDict: |
109 | 110 | class MSC3861OAuthDelegation(HomeserverTestCase): |
110 | 111 | servlets = [ |
111 | 112 | account.register_servlets, |
112 | | - devices.register_servlets, |
113 | 113 | keys.register_servlets, |
114 | | - register.register_servlets, |
115 | | - login.register_servlets, |
116 | | - logout.register_servlets, |
117 | | - admin.register_servlets, |
118 | 114 | ] |
119 | 115 |
|
120 | 116 | def default_config(self) -> Dict[str, Any]: |
@@ -635,6 +631,170 @@ def test_cross_signing(self) -> None: |
635 | 631 |
|
636 | 632 | self.assertEqual(channel.code, HTTPStatus.UNAUTHORIZED, channel.json_body) |
637 | 633 |
|
| 634 | + def test_admin_token(self) -> None: |
| 635 | + """The handler should return a requester with admin rights when admin_token is used.""" |
| 636 | + self._set_introspection_returnvalue({"active": False}) |
| 637 | + |
| 638 | + request = Mock(args={}) |
| 639 | + request.args[b"access_token"] = [b"admin_token_value"] |
| 640 | + request.requestHeaders.getRawHeaders = mock_getRawHeaders() |
| 641 | + requester = self.get_success(self.auth.get_user_by_req(request)) |
| 642 | + self.assertEqual( |
| 643 | + requester.user.to_string(), |
| 644 | + OIDC_ADMIN_USERID, |
| 645 | + ) |
| 646 | + self.assertEqual(requester.is_guest, False) |
| 647 | + self.assertEqual(requester.device_id, None) |
| 648 | + self.assertEqual( |
| 649 | + get_awaitable_result(self.auth.is_server_admin(requester)), True |
| 650 | + ) |
| 651 | + |
| 652 | + # There should be no call to the introspection endpoint |
| 653 | + self._rust_client.post.assert_not_called() |
| 654 | + |
| 655 | + @override_config({"mau_stats_only": True}) |
| 656 | + def test_request_tracking(self) -> None: |
| 657 | + """Using an access token should update the client_ips and MAU tables.""" |
| 658 | + # To start, there are no MAU users. |
| 659 | + store = self.hs.get_datastores().main |
| 660 | + mau = self.get_success(store.get_monthly_active_count()) |
| 661 | + self.assertEqual(mau, 0) |
| 662 | + |
| 663 | + known_token = "token-token-GOOD-:)" |
| 664 | + |
| 665 | + async def mock_http_client_request( |
| 666 | + url: str, request_body: str, **kwargs: Any |
| 667 | + ) -> bytes: |
| 668 | + """Mocked auth provider response.""" |
| 669 | + token = parse_qs(request_body)["token"][0] |
| 670 | + if token == known_token: |
| 671 | + return json.dumps( |
| 672 | + { |
| 673 | + "active": True, |
| 674 | + "scope": MATRIX_USER_SCOPE, |
| 675 | + "sub": SUBJECT, |
| 676 | + "username": USERNAME, |
| 677 | + }, |
| 678 | + ).encode("utf-8") |
| 679 | + |
| 680 | + return json.dumps({"active": False}).encode("utf-8") |
| 681 | + |
| 682 | + self._rust_client.post = mock_http_client_request |
| 683 | + |
| 684 | + EXAMPLE_IPV4_ADDR = "123.123.123.123" |
| 685 | + EXAMPLE_USER_AGENT = "httprettygood" |
| 686 | + |
| 687 | + # First test a known access token |
| 688 | + channel = FakeChannel(self.site, self.reactor) |
| 689 | + # type-ignore: FakeChannel is a mock of an HTTPChannel, not a proper HTTPChannel |
| 690 | + req = SynapseRequest(channel, self.site, self.hs.hostname) # type: ignore[arg-type] |
| 691 | + req.client.host = EXAMPLE_IPV4_ADDR |
| 692 | + req.requestHeaders.addRawHeader("Authorization", f"Bearer {known_token}") |
| 693 | + req.requestHeaders.addRawHeader("User-Agent", EXAMPLE_USER_AGENT) |
| 694 | + req.content = BytesIO(b"") |
| 695 | + req.requestReceived( |
| 696 | + b"GET", |
| 697 | + b"/_matrix/client/v3/account/whoami", |
| 698 | + b"1.1", |
| 699 | + ) |
| 700 | + channel.await_result() |
| 701 | + self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body) |
| 702 | + self.assertEqual(channel.json_body["user_id"], USER_ID, channel.json_body) |
| 703 | + |
| 704 | + # Expect to see one MAU entry, from the first request |
| 705 | + mau = self.get_success(store.get_monthly_active_count()) |
| 706 | + self.assertEqual(mau, 1) |
| 707 | + |
| 708 | + conn_infos = self.get_success( |
| 709 | + store.get_user_ip_and_agents(UserID.from_string(USER_ID)) |
| 710 | + ) |
| 711 | + self.assertEqual(len(conn_infos), 1, conn_infos) |
| 712 | + conn_info = conn_infos[0] |
| 713 | + self.assertEqual(conn_info["access_token"], known_token) |
| 714 | + self.assertEqual(conn_info["ip"], EXAMPLE_IPV4_ADDR) |
| 715 | + self.assertEqual(conn_info["user_agent"], EXAMPLE_USER_AGENT) |
| 716 | + |
| 717 | + # Now test MAS making a request using the special __oidc_admin token |
| 718 | + MAS_IPV4_ADDR = "127.0.0.1" |
| 719 | + MAS_USER_AGENT = "masmasmas" |
| 720 | + |
| 721 | + channel = FakeChannel(self.site, self.reactor) |
| 722 | + req = SynapseRequest(channel, self.site, self.hs.hostname) # type: ignore[arg-type] |
| 723 | + req.client.host = MAS_IPV4_ADDR |
| 724 | + req.requestHeaders.addRawHeader( |
| 725 | + "Authorization", f"Bearer {self.auth._admin_token()}" |
| 726 | + ) |
| 727 | + req.requestHeaders.addRawHeader("User-Agent", MAS_USER_AGENT) |
| 728 | + req.content = BytesIO(b"") |
| 729 | + req.requestReceived( |
| 730 | + b"GET", |
| 731 | + b"/_matrix/client/v3/account/whoami", |
| 732 | + b"1.1", |
| 733 | + ) |
| 734 | + channel.await_result() |
| 735 | + self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body) |
| 736 | + self.assertEqual( |
| 737 | + channel.json_body["user_id"], OIDC_ADMIN_USERID, channel.json_body |
| 738 | + ) |
| 739 | + |
| 740 | + # Still expect to see one MAU entry, from the first request |
| 741 | + mau = self.get_success(store.get_monthly_active_count()) |
| 742 | + self.assertEqual(mau, 1) |
| 743 | + |
| 744 | + conn_infos = self.get_success( |
| 745 | + store.get_user_ip_and_agents(UserID.from_string(OIDC_ADMIN_USERID)) |
| 746 | + ) |
| 747 | + self.assertEqual(conn_infos, []) |
| 748 | + |
| 749 | + |
| 750 | +@parameterized_class( |
| 751 | + ("config",), |
| 752 | + [ |
| 753 | + ( |
| 754 | + { |
| 755 | + "experimental_features": { |
| 756 | + "msc3861": { |
| 757 | + "enabled": True, |
| 758 | + "issuer": ISSUER, |
| 759 | + "client_id": CLIENT_ID, |
| 760 | + "client_auth_method": "client_secret_post", |
| 761 | + "client_secret": CLIENT_SECRET, |
| 762 | + "admin_token": "admin_token_value", |
| 763 | + } |
| 764 | + } |
| 765 | + }, |
| 766 | + ), |
| 767 | + ( |
| 768 | + { |
| 769 | + "matrix_authentication_service": { |
| 770 | + "enabled": True, |
| 771 | + "endpoint": "http://localhost:1234/", |
| 772 | + "secret": "secret", |
| 773 | + }, |
| 774 | + }, |
| 775 | + ), |
| 776 | + ], |
| 777 | +) |
| 778 | +class DisabledEndpointsTestCase(HomeserverTestCase): |
| 779 | + servlets = [ |
| 780 | + account.register_servlets, |
| 781 | + devices.register_servlets, |
| 782 | + keys.register_servlets, |
| 783 | + register.register_servlets, |
| 784 | + login.register_servlets, |
| 785 | + logout.register_servlets, |
| 786 | + admin.register_servlets, |
| 787 | + ] |
| 788 | + |
| 789 | + config: Dict[str, Any] |
| 790 | + |
| 791 | + def default_config(self) -> Dict[str, Any]: |
| 792 | + config = super().default_config() |
| 793 | + config["public_baseurl"] = BASE_URL |
| 794 | + config["disable_registration"] = True |
| 795 | + config.update(self.config) |
| 796 | + return config |
| 797 | + |
638 | 798 | def expect_unauthorized( |
639 | 799 | self, method: str, path: str, content: Union[bytes, str, JsonDict] = "" |
640 | 800 | ) -> None: |
@@ -774,13 +934,11 @@ def test_device_management_endpoints_removed(self) -> None: |
774 | 934 |
|
775 | 935 | # Because we still support those endpoints with ASes, it checks the |
776 | 936 | # access token before returning 404 |
777 | | - self._set_introspection_returnvalue( |
778 | | - { |
779 | | - "active": True, |
780 | | - "sub": SUBJECT, |
781 | | - "scope": " ".join([MATRIX_USER_SCOPE, MATRIX_DEVICE_SCOPE]), |
782 | | - "username": USERNAME, |
783 | | - }, |
| 937 | + self.hs.get_auth().get_user_by_req = AsyncMock( # type: ignore[method-assign] |
| 938 | + return_value=create_requester( |
| 939 | + user_id=USER_ID, |
| 940 | + device_id=DEVICE, |
| 941 | + ) |
784 | 942 | ) |
785 | 943 |
|
786 | 944 | self.expect_unrecognized("POST", "/_matrix/client/v3/delete_devices", auth=True) |
@@ -810,118 +968,3 @@ def test_admin_api_endpoints_removed(self) -> None: |
810 | 968 | self.expect_unrecognized("GET", "/_synapse/admin/v1/users/foo/admin") |
811 | 969 | self.expect_unrecognized("PUT", "/_synapse/admin/v1/users/foo/admin") |
812 | 970 | self.expect_unrecognized("POST", "/_synapse/admin/v1/account_validity/validity") |
813 | | - |
814 | | - def test_admin_token(self) -> None: |
815 | | - """The handler should return a requester with admin rights when admin_token is used.""" |
816 | | - self._set_introspection_returnvalue({"active": False}) |
817 | | - |
818 | | - request = Mock(args={}) |
819 | | - request.args[b"access_token"] = [b"admin_token_value"] |
820 | | - request.requestHeaders.getRawHeaders = mock_getRawHeaders() |
821 | | - requester = self.get_success(self.auth.get_user_by_req(request)) |
822 | | - self.assertEqual( |
823 | | - requester.user.to_string(), |
824 | | - OIDC_ADMIN_USERID, |
825 | | - ) |
826 | | - self.assertEqual(requester.is_guest, False) |
827 | | - self.assertEqual(requester.device_id, None) |
828 | | - self.assertEqual( |
829 | | - get_awaitable_result(self.auth.is_server_admin(requester)), True |
830 | | - ) |
831 | | - |
832 | | - # There should be no call to the introspection endpoint |
833 | | - self._rust_client.post.assert_not_called() |
834 | | - |
835 | | - @override_config({"mau_stats_only": True}) |
836 | | - def test_request_tracking(self) -> None: |
837 | | - """Using an access token should update the client_ips and MAU tables.""" |
838 | | - # To start, there are no MAU users. |
839 | | - store = self.hs.get_datastores().main |
840 | | - mau = self.get_success(store.get_monthly_active_count()) |
841 | | - self.assertEqual(mau, 0) |
842 | | - |
843 | | - known_token = "token-token-GOOD-:)" |
844 | | - |
845 | | - async def mock_http_client_request( |
846 | | - url: str, request_body: str, **kwargs: Any |
847 | | - ) -> bytes: |
848 | | - """Mocked auth provider response.""" |
849 | | - token = parse_qs(request_body)["token"][0] |
850 | | - if token == known_token: |
851 | | - return json.dumps( |
852 | | - { |
853 | | - "active": True, |
854 | | - "scope": MATRIX_USER_SCOPE, |
855 | | - "sub": SUBJECT, |
856 | | - "username": USERNAME, |
857 | | - }, |
858 | | - ).encode("utf-8") |
859 | | - |
860 | | - return json.dumps({"active": False}).encode("utf-8") |
861 | | - |
862 | | - self._rust_client.post = mock_http_client_request |
863 | | - |
864 | | - EXAMPLE_IPV4_ADDR = "123.123.123.123" |
865 | | - EXAMPLE_USER_AGENT = "httprettygood" |
866 | | - |
867 | | - # First test a known access token |
868 | | - channel = FakeChannel(self.site, self.reactor) |
869 | | - # type-ignore: FakeChannel is a mock of an HTTPChannel, not a proper HTTPChannel |
870 | | - req = SynapseRequest(channel, self.site, self.hs.hostname) # type: ignore[arg-type] |
871 | | - req.client.host = EXAMPLE_IPV4_ADDR |
872 | | - req.requestHeaders.addRawHeader("Authorization", f"Bearer {known_token}") |
873 | | - req.requestHeaders.addRawHeader("User-Agent", EXAMPLE_USER_AGENT) |
874 | | - req.content = BytesIO(b"") |
875 | | - req.requestReceived( |
876 | | - b"GET", |
877 | | - b"/_matrix/client/v3/account/whoami", |
878 | | - b"1.1", |
879 | | - ) |
880 | | - channel.await_result() |
881 | | - self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body) |
882 | | - self.assertEqual(channel.json_body["user_id"], USER_ID, channel.json_body) |
883 | | - |
884 | | - # Expect to see one MAU entry, from the first request |
885 | | - mau = self.get_success(store.get_monthly_active_count()) |
886 | | - self.assertEqual(mau, 1) |
887 | | - |
888 | | - conn_infos = self.get_success( |
889 | | - store.get_user_ip_and_agents(UserID.from_string(USER_ID)) |
890 | | - ) |
891 | | - self.assertEqual(len(conn_infos), 1, conn_infos) |
892 | | - conn_info = conn_infos[0] |
893 | | - self.assertEqual(conn_info["access_token"], known_token) |
894 | | - self.assertEqual(conn_info["ip"], EXAMPLE_IPV4_ADDR) |
895 | | - self.assertEqual(conn_info["user_agent"], EXAMPLE_USER_AGENT) |
896 | | - |
897 | | - # Now test MAS making a request using the special __oidc_admin token |
898 | | - MAS_IPV4_ADDR = "127.0.0.1" |
899 | | - MAS_USER_AGENT = "masmasmas" |
900 | | - |
901 | | - channel = FakeChannel(self.site, self.reactor) |
902 | | - req = SynapseRequest(channel, self.site, self.hs.hostname) # type: ignore[arg-type] |
903 | | - req.client.host = MAS_IPV4_ADDR |
904 | | - req.requestHeaders.addRawHeader( |
905 | | - "Authorization", f"Bearer {self.auth._admin_token()}" |
906 | | - ) |
907 | | - req.requestHeaders.addRawHeader("User-Agent", MAS_USER_AGENT) |
908 | | - req.content = BytesIO(b"") |
909 | | - req.requestReceived( |
910 | | - b"GET", |
911 | | - b"/_matrix/client/v3/account/whoami", |
912 | | - b"1.1", |
913 | | - ) |
914 | | - channel.await_result() |
915 | | - self.assertEqual(channel.code, HTTPStatus.OK, channel.json_body) |
916 | | - self.assertEqual( |
917 | | - channel.json_body["user_id"], OIDC_ADMIN_USERID, channel.json_body |
918 | | - ) |
919 | | - |
920 | | - # Still expect to see one MAU entry, from the first request |
921 | | - mau = self.get_success(store.get_monthly_active_count()) |
922 | | - self.assertEqual(mau, 1) |
923 | | - |
924 | | - conn_infos = self.get_success( |
925 | | - store.get_user_ip_and_agents(UserID.from_string(OIDC_ADMIN_USERID)) |
926 | | - ) |
927 | | - self.assertEqual(conn_infos, []) |
|
0 commit comments