def make_homeserver(self, reactor, clock): self.hs = self.setup_test_homeserver() self.service = ApplicationService( id="unique_identifier", token="some_token", hostname="example.com", sender="@asbot:example.com", namespaces={ ApplicationService.NS_USERS: [{ "regex": r"@as_user.*", "exclusive": False }], ApplicationService.NS_ROOMS: [], ApplicationService.NS_ALIASES: [], }, ) self.another_service = ApplicationService( id="another__identifier", token="another_token", hostname="example.com", sender="@as2bot:example.com", namespaces={ ApplicationService.NS_USERS: [{ "regex": r"@as2_user.*", "exclusive": False }], ApplicationService.NS_ROOMS: [], ApplicationService.NS_ALIASES: [], }, ) self.hs.get_datastore().services_cache.append(self.service) self.hs.get_datastore().services_cache.append(self.another_service) return self.hs
def test_GET_whoami_appservices(self): user_id = "@as:test" as_token = "i_am_an_app_service" appservice = ApplicationService( as_token, self.hs.config.server.server_name, id="1234", namespaces={"users": [{ "regex": user_id, "exclusive": True }]}, sender=user_id, ) self.hs.get_datastore().services_cache.append(appservice) whoami = self._whoami(as_token) self.assertEqual( whoami, { "user_id": user_id, # Unstable until MSC3069 enters spec "org.matrix.msc3069.is_guest": False, }, ) self.assertFalse(hasattr(whoami, "device_id"))
def test_allowed_appservice_ratelimited_via_can_requester_do_action(self): appservice = ApplicationService( None, "example.com", id="foo", rate_limited=True, sender="@as:example.com", ) as_requester = create_requester("@user:example.com", app_service=appservice) limiter = Ratelimiter(clock=None, rate_hz=0.1, burst_count=1) allowed, time_allowed = limiter.can_requester_do_action(as_requester, _time_now_s=0) self.assertTrue(allowed) self.assertEquals(10.0, time_allowed) allowed, time_allowed = limiter.can_requester_do_action(as_requester, _time_now_s=5) self.assertFalse(allowed) self.assertEquals(10.0, time_allowed) allowed, time_allowed = limiter.can_requester_do_action(as_requester, _time_now_s=10) self.assertTrue(allowed) self.assertEquals(20.0, time_allowed)
def _register_application_service( self, namespaces: Optional[Dict[str, Iterable[Dict]]] = None, ) -> ApplicationService: """ Register a new application service, with the given namespaces of interest. Args: namespaces: A dictionary containing any user, room or alias namespaces that the application service is interested in. Returns: The registered application service. """ # Create an application service appservice = ApplicationService( token=random_string(10), id=random_string(10), sender="@as:example.com", rate_limited=False, namespaces=namespaces, supports_ephemeral=True, ) # Register the application service self._services.append(appservice) return appservice
def test_POST_appservice_registration_valid(self): user_id = "@as_user_kermit:test" as_token = "i_am_an_app_service" appservice = ApplicationService( as_token, self.hs.config.server.server_name, id="1234", namespaces={ "users": [{ "regex": r"@as_user.*", "exclusive": True }] }, sender="@as:test", ) self.hs.get_datastore().services_cache.append(appservice) request_data = json.dumps({ "username": "******", "type": APP_SERVICE_REGISTRATION_TYPE }) channel = self.make_request( b"POST", self.url + b"?access_token=i_am_an_app_service", request_data) self.assertEquals(channel.result["code"], b"200", channel.result) det_data = {"user_id": user_id, "home_server": self.hs.hostname} self.assertDictContainsSubset(det_data, channel.json_body)
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer: config = self.default_config() config["update_user_directory"] = True self.appservice = ApplicationService( token="i_am_an_app_service", id="1234", namespaces={ "users": [{ "regex": r"@as_user.*", "exclusive": True }] }, # Note: this user does not match the regex above, so that tests # can distinguish the sender from the AS user. sender="@as_main:test", ) mock_load_appservices = Mock(return_value=[self.appservice]) with patch( "synapse.storage.databases.main.appservice.load_appservices", mock_load_appservices, ): hs = self.setup_test_homeserver(config=config) return hs
def test_blocking_mau__appservice_requester_disallowed_when_tracking_ips( self): self.auth_blocking._max_mau_value = 50 self.auth_blocking._limit_usage_by_mau = True self.auth_blocking._track_appservice_user_ips = True self.store.get_monthly_active_count = simple_async_mock(100) self.store.user_last_seen_monthly_active = simple_async_mock() self.store.is_trial_user = simple_async_mock() appservice = ApplicationService( "abcd", self.hs.config.server.server_name, id="1234", namespaces={ "users": [{ "regex": "@_appservice.*:sender", "exclusive": True }] }, sender="@appservice:sender", ) requester = Requester( user="******", access_token_id=None, device_id="FOOBAR", is_guest=False, shadow_banned=False, app_service=appservice, authenticated_entity="@appservice:server", ) self.get_failure(self.auth.check_auth_blocking(requester=requester), ResourceLimitError)
def test_allowed_appservice_via_can_requester_do_action(self): appservice = ApplicationService( None, "example.com", id="foo", rate_limited=False, sender="@as:example.com", ) as_requester = create_requester("@user:example.com", app_service=appservice) limiter = Ratelimiter( store=self.hs.get_datastore(), clock=None, rate_hz=0.1, burst_count=1 ) allowed, time_allowed = self.get_success_or_raise( limiter.can_do_action(as_requester, _time_now_s=0) ) self.assertTrue(allowed) self.assertEquals(-1, time_allowed) allowed, time_allowed = self.get_success_or_raise( limiter.can_do_action(as_requester, _time_now_s=5) ) self.assertTrue(allowed) self.assertEquals(-1, time_allowed) allowed, time_allowed = self.get_success_or_raise( limiter.can_do_action(as_requester, _time_now_s=10) ) self.assertTrue(allowed) self.assertEquals(-1, time_allowed)
def test_update_and_retrieval_of_service(self): url = "https://matrix.org/appservices/foobar" hs_token = "hstok" user_regex = ["@foobar_.*:matrix.org"] alias_regex = ["#foobar_.*:matrix.org"] room_regex = [] service = ApplicationService(url=url, hs_token=hs_token, token=self.as_token, namespaces={ ApplicationService.NS_USERS: user_regex, ApplicationService.NS_ALIASES: alias_regex, ApplicationService.NS_ROOMS: room_regex }) yield self.store.update_app_service(service) stored_service = yield self.store.get_app_service_by_token( self.as_token) self.assertEquals(stored_service.token, self.as_token) self.assertEquals(stored_service.url, url) self.assertEquals( stored_service.namespaces[ApplicationService.NS_ALIASES], alias_regex) self.assertEquals( stored_service.namespaces[ApplicationService.NS_ROOMS], room_regex) self.assertEquals( stored_service.namespaces[ApplicationService.NS_USERS], user_regex)
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer): self.api = hs.get_application_service_api() self.service = ApplicationService( id="unique_identifier", sender="@as:test", url=URL, token="unused", hs_token=TOKEN, )
def _parse_services_dict(self, results): # SQL results in the form: # [ # { # 'regex': "something", # 'url': "something", # 'namespace': enum, # 'as_id': 0, # 'token': "something", # 'hs_token': "otherthing", # 'id': 0 # } # ] services = {} for res in results: as_token = res["token"] if as_token is None: continue if as_token not in services: # add the service services[as_token] = { "id": res["id"], "url": res["url"], "token": as_token, "hs_token": res["hs_token"], "sender": res["sender"], "namespaces": { ApplicationService.NS_USERS: [], ApplicationService.NS_ALIASES: [], ApplicationService.NS_ROOMS: [] } } # add the namespace regex if one exists ns_int = res["namespace"] if ns_int is None: continue try: services[as_token]["namespaces"][ ApplicationService.NS_LIST[ns_int]].append( json.loads(res["regex"]) ) except IndexError: logger.error("Bad namespace enum '%s'. %s", ns_int, res) except JSONDecodeError: logger.error("Bad regex object '%s'", res["regex"]) service_list = [] for service in services.values(): service_list.append(ApplicationService( token=service["token"], url=service["url"], namespaces=service["namespaces"], hs_token=service["hs_token"], sender=service["sender"], id=service["id"] )) return service_list
def setUp(self): self.service = ApplicationService(url="some_url", token="some_token", namespaces={ ApplicationService.NS_USERS: [], ApplicationService.NS_ROOMS: [], ApplicationService.NS_ALIASES: [] }) self.event = Mock(type="m.something", room_id="!foo:bar", sender="@someone:somewhere")
def _populate_cache(self): """Populates the ApplicationServiceCache from the database.""" sql = ("SELECT * FROM application_services LEFT JOIN " "application_services_regex ON application_services.id = " "application_services_regex.as_id") # SQL results in the form: # [ # { # 'regex': "something", # 'url': "something", # 'namespace': enum, # 'as_id': 0, # 'token': "something", # 'hs_token': "otherthing", # 'id': 0 # } # ] services = {} results = yield self._execute_and_decode(sql) for res in results: as_token = res["token"] if as_token not in services: # add the service services[as_token] = { "url": res["url"], "token": as_token, "hs_token": res["hs_token"], "sender": res["sender"], "namespaces": { ApplicationService.NS_USERS: [], ApplicationService.NS_ALIASES: [], ApplicationService.NS_ROOMS: [] } } # add the namespace regex if one exists ns_int = res["namespace"] if ns_int is None: continue try: services[as_token]["namespaces"][ ApplicationService.NS_LIST[ns_int]].append(res["regex"]) except IndexError: logger.error("Bad namespace enum '%s'. %s", ns_int, res) # TODO get last successful txn id f.e. service for service in services.values(): logger.info("Found application service: %s", service) self.cache.services.append( ApplicationService(token=service["token"], url=service["url"], namespaces=service["namespaces"], hs_token=service["hs_token"], sender=service["sender"]))
def setUp(self): self.service = ApplicationService( id="unique_identifier", sender="@as:test", url="some_url", token="some_token", hostname="matrix.org", # only used by get_groups_for_user ) self.event = Mock(type="m.something", room_id="!foo:bar", sender="@someone:somewhere") self.store = Mock()
def make_homeserver(self, reactor: MemoryReactor, clock: Clock) -> HomeServer: self.appservice = ApplicationService( token="i_am_an_app_service", id="1234", namespaces={"users": [{"regex": r"@as_user.*", "exclusive": True}]}, sender="@as:test", ) mock_load_appservices = Mock(return_value=[self.appservice]) with patch( "synapse.storage.databases.main.appservice.load_appservices", mock_load_appservices, ): hs = super().make_homeserver(reactor, clock) return hs
def setUp(self): self.service = ApplicationService( id="unique_identifier", url="some_url", token="some_token", hostname="matrix.org", # only used by get_groups_for_user namespaces={ ApplicationService.NS_USERS: [], ApplicationService.NS_ROOMS: [], ApplicationService.NS_ALIASES: [] }) self.event = Mock(type="m.something", room_id="!foo:bar", sender="@someone:somewhere") self.store = Mock()
def setUp(self): self.service = ApplicationService( id="unique_identifier", sender="@as:test", url="some_url", token="some_token", ) self.event = Mock( event_id="$abc:xyz", type="m.something", room_id="!foo:bar", sender="@someone:somewhere", ) self.store = Mock() self.store.get_aliases_for_room = simple_async_mock([]) self.store.get_users_in_room = simple_async_mock([])
def _load_appservice(self, as_info): required_string_fields = [ "url", "as_token", "hs_token", "sender_localpart" ] for field in required_string_fields: if not isinstance(as_info.get(field), basestring): raise KeyError("Required string field: '%s'", field) localpart = as_info["sender_localpart"] if urllib.quote(localpart) != localpart: raise ValueError( "sender_localpart needs characters which are not URL encoded." ) user = UserID(localpart, self.hostname) user_id = user.to_string() # namespace checks if not isinstance(as_info.get("namespaces"), dict): raise KeyError("Requires 'namespaces' object.") for ns in ApplicationService.NS_LIST: # specific namespaces are optional if ns in as_info["namespaces"]: # expect a list of dicts with exclusive and regex keys for regex_obj in as_info["namespaces"][ns]: if not isinstance(regex_obj, dict): raise ValueError( "Expected namespace entry in %s to be an object," " but got %s", ns, regex_obj ) if not isinstance(regex_obj.get("regex"), basestring): raise ValueError( "Missing/bad type 'regex' key in %s", regex_obj ) if not isinstance(regex_obj.get("exclusive"), bool): raise ValueError( "Missing/bad type 'exclusive' key in %s", regex_obj ) return ApplicationService( token=as_info["as_token"], url=as_info["url"], namespaces=as_info["namespaces"], hs_token=as_info["hs_token"], sender=user_id, id=as_info["as_token"] # the token is the only unique thing here )
def test_POST_appservice_registration_no_type(self) -> None: as_token = "i_am_an_app_service" appservice = ApplicationService( as_token, id="1234", namespaces={"users": [{"regex": r"@as_user.*", "exclusive": True}]}, sender="@as:test", ) self.hs.get_datastores().main.services_cache.append(appservice) request_data = json.dumps({"username": "******"}) channel = self.make_request( b"POST", self.url + b"?access_token=i_am_an_app_service", request_data ) self.assertEqual(channel.result["code"], b"400", channel.result)
def test_GET_whoami_appservices(self): user_id = "@as:test" as_token = "i_am_an_app_service" appservice = ApplicationService( as_token, self.hs.config.server_name, id="1234", namespaces={"users": [{ "regex": user_id, "exclusive": True }]}, sender=user_id, ) self.hs.get_datastore().services_cache.append(appservice) whoami = self.whoami(as_token) self.assertEqual(whoami, {"user_id": user_id}) self.assertFalse(hasattr(whoami, "device_id"))
def test_as_ignores_mau(self): """Test that application services can still create users when the MAU limit has been reached. This only works when application service user ip tracking is disabled. """ # Create and sync so that the MAU counts get updated token1 = self.create_user("kermit1") self.do_sync_for_user(token1) token2 = self.create_user("kermit2") self.do_sync_for_user(token2) # check we're testing what we think we are: there should be two active users self.assertEqual( self.get_success(self.store.get_monthly_active_count()), 2) # We've created and activated two users, we shouldn't be able to # register new users with self.assertRaises(SynapseError) as cm: self.create_user("kermit3") e = cm.exception self.assertEqual(e.code, 403) self.assertEqual(e.errcode, Codes.RESOURCE_LIMIT_EXCEEDED) # Cheekily add an application service that we use to register a new user # with. as_token = "foobartoken" self.store.services_cache.append( ApplicationService( token=as_token, hostname=self.hs.hostname, id="SomeASID", sender="@as_sender:test", namespaces={"users": [{ "regex": "@as_*", "exclusive": True }]}, )) self.create_user("as_kermit4", token=as_token, appservice=True)
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: # Mock the ApplicationServiceScheduler's _TransactionController's send method so that # we can track what's going out self.send_mock = simple_async_mock() hs.get_application_service_handler( ).scheduler.txn_ctrl.send = self.send_mock # type: ignore[assignment] # We assign to a method. # Define an application service for the tests self._service_token = "VERYSECRET" self._service = ApplicationService( self._service_token, "as1", "@as.sender:test", namespaces={ "users": [ { "regex": "@_as_.*:test", "exclusive": True }, { "regex": "@as.sender:test", "exclusive": True }, ] }, msc3202_transaction_extensions=True, ) self.hs.get_datastores().main.services_cache = [self._service] # Register some appservice users self._sender_user, self._sender_device = self.register_appservice_user( "as.sender", self._service_token) self._namespaced_user, self._namespaced_device = self.register_appservice_user( "_as_user1", self._service_token) # Register a real user as well. self._real_user = self.register_user("real.user", "meow") self._real_user_token = self.login("real.user", "meow")
def test_deleting_alias_via_directory_appservice(self) -> None: user_id = "@as:test" as_token = "i_am_an_app_service" appservice = ApplicationService( as_token, id="1234", namespaces={"aliases": [{ "regex": "#asns-*", "exclusive": True }]}, sender=user_id, ) self.hs.get_datastores().main.services_cache.append(appservice) # Add an alias for the room, as the appservice alias = RoomAlias(f"asns-{random_string(5)}", self.hs.hostname).to_string() data = {"room_id": self.room_id} request_data = json.dumps(data) channel = self.make_request( "PUT", f"/_matrix/client/r0/directory/room/{alias}", request_data, access_token=as_token, ) self.assertEqual(channel.code, HTTPStatus.OK, channel.result) # Then try to remove the alias, as the appservice channel = self.make_request( "DELETE", f"/_matrix/client/r0/directory/room/{alias}", access_token=as_token, ) self.assertEqual(channel.code, HTTPStatus.OK, channel.result)
def _load_appservice(hostname: str, as_info: JsonDict, config_filename: str) -> ApplicationService: required_string_fields = ["id", "as_token", "hs_token", "sender_localpart"] for field in required_string_fields: if not isinstance(as_info.get(field), str): raise KeyError("Required string field: '%s' (%s)" % (field, config_filename)) # 'url' must either be a string or explicitly null, not missing # to avoid accidentally turning off push for ASes. if not isinstance(as_info.get("url"), str) and as_info.get("url", "") is not None: raise KeyError("Required string field or explicit null: 'url' (%s)" % (config_filename, )) localpart = as_info["sender_localpart"] if urlparse.quote(localpart) != localpart: raise ValueError( "sender_localpart needs characters which are not URL encoded.") user = UserID(localpart, hostname) user_id = user.to_string() # Rate limiting for users of this AS is on by default (excludes sender) rate_limited = as_info.get("rate_limited") if not isinstance(rate_limited, bool): rate_limited = True # namespace checks if not isinstance(as_info.get("namespaces"), dict): raise KeyError("Requires 'namespaces' object.") for ns in ApplicationService.NS_LIST: # specific namespaces are optional if ns in as_info["namespaces"]: # expect a list of dicts with exclusive and regex keys for regex_obj in as_info["namespaces"][ns]: if not isinstance(regex_obj, dict): raise ValueError( "Expected namespace entry in %s to be an object, but got %s", ns, regex_obj, ) if not isinstance(regex_obj.get("regex"), str): raise ValueError("Missing/bad type 'regex' key in %s", regex_obj) if not isinstance(regex_obj.get("exclusive"), bool): raise ValueError("Missing/bad type 'exclusive' key in %s", regex_obj) # protocols check protocols = as_info.get("protocols") if protocols: if not isinstance(protocols, list): raise KeyError("Optional 'protocols' must be a list if present.") for p in protocols: if not isinstance(p, str): raise KeyError("Bad value for 'protocols' item") if as_info["url"] is None: logger.info( "(%s) Explicitly empty 'url' provided. This application service" " will not receive events or queries.", config_filename, ) ip_range_whitelist = None if as_info.get("ip_range_whitelist"): ip_range_whitelist = IPSet(as_info.get("ip_range_whitelist")) supports_ephemeral = as_info.get("de.sorunome.msc2409.push_ephemeral", False) # Opt-in flag for the MSC3202-specific transactional behaviour. # When enabled, appservice transactions contain the following information: # - device One-Time Key counts # - device unused fallback key usage states # - device list changes msc3202_transaction_extensions = as_info.get("org.matrix.msc3202", False) if not isinstance(msc3202_transaction_extensions, bool): raise ValueError( "The `org.matrix.msc3202` option should be true or false if specified." ) return ApplicationService( token=as_info["as_token"], url=as_info["url"], namespaces=as_info["namespaces"], hs_token=as_info["hs_token"], sender=user_id, id=as_info["id"], protocols=protocols, rate_limited=rate_limited, ip_range_whitelist=ip_range_whitelist, supports_ephemeral=supports_ephemeral, msc3202_transaction_extensions=msc3202_transaction_extensions, )
def test_application_service_receives_device_list_updates( self, experimental_feature_enabled: bool, as_supports_txn_extensions: bool, as_should_receive_device_list_updates: bool, ): """ Tests that an application service receives notice of changed device lists for a user, when a user changes their device lists. Arguments above are populated by parameterized. Args: as_should_receive_device_list_updates: Whether we expect the AS to receive the device list changes. experimental_feature_enabled: Whether the "msc3202_transaction_extensions" experimental feature is enabled. This feature must be enabled for device lists to ASs to work. as_supports_txn_extensions: Whether the application service has explicitly registered to receive information defined by MSC3202 - which includes device list changes. """ # Change whether the experimental feature is enabled or disabled before making # device list changes self.as_handler._msc3202_transaction_extensions_enabled = ( experimental_feature_enabled) # Create an appservice that is interested in "local_user" appservice = ApplicationService( token=random_string(10), id=random_string(10), sender="@as:example.com", rate_limited=False, namespaces={ ApplicationService.NS_USERS: [{ "regex": "@local_user:.+", "exclusive": False, }], }, supports_ephemeral=True, msc3202_transaction_extensions=as_supports_txn_extensions, # Must be set for Synapse to try pushing data to the AS hs_token="abcde", url="some_url", ) # Register the application service self._services.append(appservice) # Register a user on the homeserver self.local_user = self.register_user("local_user", "password") self.local_user_token = self.login("local_user", "password") if as_should_receive_device_list_updates: # Ensure that the resulting JSON uses the unstable prefix and contains the # expected users self.put_json.assert_called_once() json_body = self.put_json.call_args[1]["json_body"] # Our application service should have received a device list update with # "local_user" in the "changed" list device_list_dict = json_body.get("org.matrix.msc3202.device_lists", {}) self.assertEqual([], device_list_dict["left"]) self.assertEqual([self.local_user], device_list_dict["changed"]) else: # No device list changes should have been sent out self.put_json.assert_not_called()
def test_as_trial_days(self): user_tokens: List[str] = [] def advance_time_and_sync(): self.reactor.advance(24 * 60 * 61) for token in user_tokens: self.do_sync_for_user(token) # Cheekily add an application service that we use to register a new user # with. as_token_1 = "foobartoken1" self.store.services_cache.append( ApplicationService( token=as_token_1, id="SomeASID", sender="@as_sender_1:test", namespaces={ "users": [{ "regex": "@as_1.*", "exclusive": True }] }, )) as_token_2 = "foobartoken2" self.store.services_cache.append( ApplicationService( token=as_token_2, id="AnotherASID", sender="@as_sender_2:test", namespaces={ "users": [{ "regex": "@as_2.*", "exclusive": True }] }, )) user_tokens.append(self.create_user("kermit1")) user_tokens.append(self.create_user("kermit2")) user_tokens.append( self.create_user("as_1kermit3", token=as_token_1, appservice=True)) user_tokens.append( self.create_user("as_2kermit4", token=as_token_2, appservice=True)) # Advance time by 1 day to include the first appservice advance_time_and_sync() self.assertEqual( self.get_success(self.store.get_monthly_active_count_by_service()), {"SomeASID": 1}, ) # Advance time by 1 day to include the next appservice advance_time_and_sync() self.assertEqual( self.get_success(self.store.get_monthly_active_count_by_service()), { "SomeASID": 1, "AnotherASID": 1 }, ) # Advance time by 1 day to include the native users advance_time_and_sync() self.assertEqual( self.get_success(self.store.get_monthly_active_count_by_service()), { "SomeASID": 1, "AnotherASID": 1, "native": 2, }, )
def _load_appservice(hostname, as_info, config_filename): required_string_fields = ["id", "as_token", "hs_token", "sender_localpart"] for field in required_string_fields: if not isinstance(as_info.get(field), string_types): raise KeyError( "Required string field: '%s' (%s)" % (field, config_filename) ) # 'url' must either be a string or explicitly null, not missing # to avoid accidentally turning off push for ASes. if ( not isinstance(as_info.get("url"), string_types) and as_info.get("url", "") is not None ): raise KeyError( "Required string field or explicit null: 'url' (%s)" % (config_filename,) ) localpart = as_info["sender_localpart"] if urlparse.quote(localpart) != localpart: raise ValueError("sender_localpart needs characters which are not URL encoded.") user = UserID(localpart, hostname) user_id = user.to_string() # Rate limiting for users of this AS is on by default (excludes sender) rate_limited = True if isinstance(as_info.get("rate_limited"), bool): rate_limited = as_info.get("rate_limited") # namespace checks if not isinstance(as_info.get("namespaces"), dict): raise KeyError("Requires 'namespaces' object.") for ns in ApplicationService.NS_LIST: # specific namespaces are optional if ns in as_info["namespaces"]: # expect a list of dicts with exclusive and regex keys for regex_obj in as_info["namespaces"][ns]: if not isinstance(regex_obj, dict): raise ValueError( "Expected namespace entry in %s to be an object, but got %s", ns, regex_obj, ) if not isinstance(regex_obj.get("regex"), string_types): raise ValueError("Missing/bad type 'regex' key in %s", regex_obj) if not isinstance(regex_obj.get("exclusive"), bool): raise ValueError( "Missing/bad type 'exclusive' key in %s", regex_obj ) # protocols check protocols = as_info.get("protocols") if protocols: # Because strings are lists in python if isinstance(protocols, str) or not isinstance(protocols, list): raise KeyError("Optional 'protocols' must be a list if present.") for p in protocols: if not isinstance(p, str): raise KeyError("Bad value for 'protocols' item") if as_info["url"] is None: logger.info( "(%s) Explicitly empty 'url' provided. This application service" " will not receive events or queries.", config_filename, ) ip_range_whitelist = None if as_info.get("ip_range_whitelist"): ip_range_whitelist = IPSet(as_info.get("ip_range_whitelist")) return ApplicationService( token=as_info["as_token"], hostname=hostname, url=as_info["url"], namespaces=as_info["namespaces"], hs_token=as_info["hs_token"], sender=user_id, id=as_info["id"], protocols=protocols, rate_limited=rate_limited, ip_range_whitelist=ip_range_whitelist, )