summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.rst8
-rw-r--r--synapse/__init__.py2
-rw-r--r--synapse/app/_base.py2
-rw-r--r--synapse/appservice/scheduler.py2
-rw-r--r--synapse/config/server.py2
-rw-r--r--synapse/crypto/context_factory.py2
-rw-r--r--synapse/crypto/event_signing.py2
-rw-r--r--synapse/crypto/keyring.py2
-rw-r--r--synapse/event_auth.py4
-rw-r--r--synapse/events/builder.py2
-rw-r--r--synapse/events/spamcheck.py2
-rw-r--r--synapse/federation/transport/client.py20
-rw-r--r--synapse/federation/transport/server.py28
-rw-r--r--synapse/groups/groups_server.py32
-rw-r--r--synapse/handlers/auth.py2
-rw-r--r--synapse/handlers/federation.py16
-rw-r--r--synapse/handlers/initial_sync.py2
-rw-r--r--synapse/handlers/message.py2
-rw-r--r--synapse/handlers/presence.py2
-rw-r--r--synapse/handlers/profile.py6
-rw-r--r--synapse/handlers/register.py17
-rw-r--r--synapse/handlers/room.py6
-rw-r--r--synapse/handlers/search.py2
-rw-r--r--synapse/http/matrixfederationclient.py2
-rw-r--r--synapse/http/server.py2
-rw-r--r--synapse/http/servlet.py6
-rw-r--r--synapse/http/site.py2
-rw-r--r--synapse/notifier.py2
-rw-r--r--synapse/push/emailpusher.py2
-rw-r--r--synapse/push/httppusher.py6
-rw-r--r--synapse/push/pusher.py2
-rw-r--r--synapse/push/pusherpool.py6
-rw-r--r--synapse/replication/tcp/resource.py2
-rw-r--r--synapse/rest/client/v1/directory.py2
-rw-r--r--synapse/rest/client/v1/login.py6
-rw-r--r--synapse/rest/client/v1/presence.py2
-rw-r--r--synapse/rest/client/v1/profile.py4
-rw-r--r--synapse/rest/client/v1/room.py6
-rw-r--r--synapse/rest/client/v2_alpha/filter.py2
-rw-r--r--synapse/rest/client/v2_alpha/groups.py2
-rw-r--r--synapse/rest/client/v2_alpha/sync.py2
-rw-r--r--synapse/rest/client/v2_alpha/user_directory.py2
-rw-r--r--synapse/rest/key/v2/remote_key_resource.py2
-rw-r--r--synapse/rest/media/v1/_base.py7
-rw-r--r--synapse/rest/media/v1/media_repository.py2
-rw-r--r--synapse/rest/media/v1/preview_url_resource.py6
-rw-r--r--synapse/state.py2
-rw-r--r--synapse/storage/_base.py2
-rw-r--r--synapse/storage/background_updates.py2
-rw-r--r--synapse/storage/events.py2
-rw-r--r--synapse/storage/prepare_database.py2
-rw-r--r--synapse/storage/roommember.py2
-rw-r--r--synapse/storage/schema/delta/30/as_users.py2
-rw-r--r--synapse/storage/search.py4
-rw-r--r--synapse/streams/config.py6
-rw-r--r--synapse/types.py45
-rw-r--r--synapse/util/__init__.py18
-rw-r--r--synapse/util/async.py6
-rw-r--r--synapse/util/logcontext.py2
-rw-r--r--synapse/util/retryutils.py2
-rw-r--r--synapse/util/wheel_timer.py5
-rw-r--r--tests/storage/test_appservice.py2
-rw-r--r--tests/test_types.py24
-rw-r--r--tests/utils.py8
64 files changed, 219 insertions, 159 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 80518b7bae..4911cfa284 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,11 @@
+Changes in synapse v0.24.1 (2017-10-24)
+=======================================
+
+Bug fixes:
+
+* Fix updating group profiles over federation (PR #2567)
+
+
 Changes in synapse v0.24.0 (2017-10-23)
 =======================================
 
diff --git a/synapse/__init__.py b/synapse/__init__.py
index c867d1cfd8..e74abe0130 100644
--- a/synapse/__init__.py
+++ b/synapse/__init__.py
@@ -16,4 +16,4 @@
 """ This is a reference implementation of a Matrix home server.
 """
 
-__version__ = "0.24.0"
+__version__ = "0.24.1"
diff --git a/synapse/app/_base.py b/synapse/app/_base.py
index cf4730730d..9477737759 100644
--- a/synapse/app/_base.py
+++ b/synapse/app/_base.py
@@ -19,7 +19,7 @@ import sys
 
 try:
     import affinity
-except:
+except Exception:
     affinity = None
 
 from daemonize import Daemonize
diff --git a/synapse/appservice/scheduler.py b/synapse/appservice/scheduler.py
index 68a9de17b8..6da315473d 100644
--- a/synapse/appservice/scheduler.py
+++ b/synapse/appservice/scheduler.py
@@ -123,7 +123,7 @@ class _ServiceQueuer(object):
                 with Measure(self.clock, "servicequeuer.send"):
                     try:
                         yield self.txn_ctrl.send(service, events)
-                    except:
+                    except Exception:
                         logger.exception("AS request failed")
         finally:
             self.requests_in_flight.discard(service.id)
diff --git a/synapse/config/server.py b/synapse/config/server.py
index c9a1715f1f..b66993dab9 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -303,7 +303,7 @@ def read_gc_thresholds(thresholds):
         return (
             int(thresholds[0]), int(thresholds[1]), int(thresholds[2]),
         )
-    except:
+    except Exception:
         raise ConfigError(
             "Value of `gc_threshold` must be a list of three integers if set"
         )
diff --git a/synapse/crypto/context_factory.py b/synapse/crypto/context_factory.py
index aad4752fe7..cff3ca809a 100644
--- a/synapse/crypto/context_factory.py
+++ b/synapse/crypto/context_factory.py
@@ -34,7 +34,7 @@ class ServerContextFactory(ssl.ContextFactory):
         try:
             _ecCurve = _OpenSSLECCurve(_defaultCurveName)
             _ecCurve.addECKeyToContext(context)
-        except:
+        except Exception:
             logger.exception("Failed to enable elliptic curve for TLS")
         context.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
         context.use_certificate_chain_file(config.tls_certificate_file)
diff --git a/synapse/crypto/event_signing.py b/synapse/crypto/event_signing.py
index ec7711ba7d..0d0e7b5286 100644
--- a/synapse/crypto/event_signing.py
+++ b/synapse/crypto/event_signing.py
@@ -43,7 +43,7 @@ def check_event_content_hash(event, hash_algorithm=hashlib.sha256):
     message_hash_base64 = event.hashes[name]
     try:
         message_hash_bytes = decode_base64(message_hash_base64)
-    except:
+    except Exception:
         raise SynapseError(
             400,
             "Invalid base64: %s" % (message_hash_base64,),
diff --git a/synapse/crypto/keyring.py b/synapse/crypto/keyring.py
index 054bac456d..35f810b07b 100644
--- a/synapse/crypto/keyring.py
+++ b/synapse/crypto/keyring.py
@@ -759,7 +759,7 @@ def _handle_key_deferred(verify_request):
     ))
     try:
         verify_signed_json(json_object, server_name, verify_key)
-    except:
+    except Exception:
         raise SynapseError(
             401,
             "Invalid signature for server %s with key %s:%s" % (
diff --git a/synapse/event_auth.py b/synapse/event_auth.py
index 9e746a28bf..061ee86b16 100644
--- a/synapse/event_auth.py
+++ b/synapse/event_auth.py
@@ -443,12 +443,12 @@ def _check_power_levels(event, auth_events):
     for k, v in user_list.items():
         try:
             UserID.from_string(k)
-        except:
+        except Exception:
             raise SynapseError(400, "Not a valid user_id: %s" % (k,))
 
         try:
             int(v)
-        except:
+        except Exception:
             raise SynapseError(400, "Not a valid power level: %s" % (v,))
 
     key = (event.type, event.state_key, )
diff --git a/synapse/events/builder.py b/synapse/events/builder.py
index 365fd96bd2..13fbba68c0 100644
--- a/synapse/events/builder.py
+++ b/synapse/events/builder.py
@@ -55,7 +55,7 @@ class EventBuilderFactory(object):
 
         local_part = str(int(self.clock.time())) + i + random_string(5)
 
-        e_id = EventID.create(local_part, self.hostname)
+        e_id = EventID(local_part, self.hostname)
 
         return e_id.to_string()
 
diff --git a/synapse/events/spamcheck.py b/synapse/events/spamcheck.py
index dccc579eac..633e068eb8 100644
--- a/synapse/events/spamcheck.py
+++ b/synapse/events/spamcheck.py
@@ -22,7 +22,7 @@ class SpamChecker(object):
         config = None
         try:
             module, config = hs.config.spam_checker
-        except:
+        except Exception:
             pass
 
         if module is not None:
diff --git a/synapse/federation/transport/client.py b/synapse/federation/transport/client.py
index 125d8f3598..d25ae1b282 100644
--- a/synapse/federation/transport/client.py
+++ b/synapse/federation/transport/client.py
@@ -486,6 +486,26 @@ class TransportLayerClient(object):
         )
 
     @log_function
+    def update_group_profile(self, destination, group_id, requester_user_id, content):
+        """Update a remote group profile
+
+        Args:
+            destination (str)
+            group_id (str)
+            requester_user_id (str)
+            content (dict): The new profile of the group
+        """
+        path = PREFIX + "/groups/%s/profile" % (group_id,)
+
+        return self.client.post_json(
+            destination=destination,
+            path=path,
+            args={"requester_user_id": requester_user_id},
+            data=content,
+            ignore_backoff=True,
+        )
+
+    @log_function
     def get_group_summary(self, destination, group_id, requester_user_id):
         """Get a group summary
         """
diff --git a/synapse/federation/transport/server.py b/synapse/federation/transport/server.py
index f0778c65c5..8f3c14c303 100644
--- a/synapse/federation/transport/server.py
+++ b/synapse/federation/transport/server.py
@@ -112,7 +112,7 @@ class Authenticator(object):
                 key = strip_quotes(param_dict["key"])
                 sig = strip_quotes(param_dict["sig"])
                 return (origin, key, sig)
-            except:
+            except Exception:
                 raise AuthenticationError(
                     400, "Malformed Authorization header", Codes.UNAUTHORIZED
                 )
@@ -177,7 +177,7 @@ class BaseFederationServlet(object):
                 if self.REQUIRE_AUTH:
                     logger.exception("authenticate_request failed")
                     raise
-            except:
+            except Exception:
                 logger.exception("authenticate_request failed")
                 raise
 
@@ -270,7 +270,7 @@ class FederationSendServlet(BaseFederationServlet):
             code, response = yield self.handler.on_incoming_transaction(
                 transaction_data
             )
-        except:
+        except Exception:
             logger.exception("on_incoming_transaction failed")
             raise
 
@@ -610,7 +610,7 @@ class FederationVersionServlet(BaseFederationServlet):
 
 
 class FederationGroupsProfileServlet(BaseFederationServlet):
-    """Get the basic profile of a group on behalf of a user
+    """Get/set the basic profile of a group on behalf of a user
     """
     PATH = "/groups/(?P<group_id>[^/]*)/profile$"
 
@@ -626,30 +626,30 @@ class FederationGroupsProfileServlet(BaseFederationServlet):
 
         defer.returnValue((200, new_content))
 
-
-class FederationGroupsSummaryServlet(BaseFederationServlet):
-    PATH = "/groups/(?P<group_id>[^/]*)/summary$"
-
     @defer.inlineCallbacks
-    def on_GET(self, origin, content, query, group_id):
+    def on_POST(self, origin, content, query, group_id):
         requester_user_id = parse_string_from_args(query, "requester_user_id")
         if get_domain_from_id(requester_user_id) != origin:
             raise SynapseError(403, "requester_user_id doesn't match origin")
 
-        new_content = yield self.handler.get_group_summary(
-            group_id, requester_user_id
+        new_content = yield self.handler.update_group_profile(
+            group_id, requester_user_id, content
         )
 
         defer.returnValue((200, new_content))
 
+
+class FederationGroupsSummaryServlet(BaseFederationServlet):
+    PATH = "/groups/(?P<group_id>[^/]*)/summary$"
+
     @defer.inlineCallbacks
-    def on_POST(self, origin, content, query, group_id):
+    def on_GET(self, origin, content, query, group_id):
         requester_user_id = parse_string_from_args(query, "requester_user_id")
         if get_domain_from_id(requester_user_id) != origin:
             raise SynapseError(403, "requester_user_id doesn't match origin")
 
-        new_content = yield self.handler.update_group_profile(
-            group_id, requester_user_id, content
+        new_content = yield self.handler.get_group_summary(
+            group_id, requester_user_id
         )
 
         defer.returnValue((200, new_content))
diff --git a/synapse/groups/groups_server.py b/synapse/groups/groups_server.py
index fc4edb7f04..23beb3187e 100644
--- a/synapse/groups/groups_server.py
+++ b/synapse/groups/groups_server.py
@@ -13,14 +13,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from twisted.internet import defer
+import logging
 
 from synapse.api.errors import SynapseError
-from synapse.types import UserID, get_domain_from_id, RoomID, GroupID
-
-
-import logging
-import urllib
+from synapse.types import GroupID, RoomID, UserID, get_domain_from_id
+from twisted.internet import defer
 
 logger = logging.getLogger(__name__)
 
@@ -698,9 +695,11 @@ class GroupsServerHandler(object):
     def create_group(self, group_id, user_id, content):
         group = yield self.check_group_is_ours(group_id)
 
-        _validate_group_id(group_id)
-
         logger.info("Attempting to create group with ID: %r", group_id)
+
+        # parsing the id into a GroupID validates it.
+        group_id_obj = GroupID.from_string(group_id)
+
         if group:
             raise SynapseError(400, "Group already exists")
 
@@ -710,7 +709,7 @@ class GroupsServerHandler(object):
                 raise SynapseError(
                     403, "Only server admin can create group on this server",
                 )
-            localpart = GroupID.from_string(group_id).localpart
+            localpart = group_id_obj.localpart
             if not localpart.startswith(self.hs.config.group_creation_prefix):
                 raise SynapseError(
                     400,
@@ -786,18 +785,3 @@ def _parse_visibility_from_contents(content):
         is_public = True
 
     return is_public
-
-
-def _validate_group_id(group_id):
-    """Validates the group ID is valid for creation on this home server
-    """
-    localpart = GroupID.from_string(group_id).localpart
-
-    if localpart.lower() != localpart:
-        raise SynapseError(400, "Group ID must be lower case")
-
-    if urllib.quote(localpart.encode('utf-8')) != localpart:
-        raise SynapseError(
-            400,
-            "Group ID can only contain characters a-z, 0-9, or '_-./'",
-        )
diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py
index b00446bec0..9cef9d184b 100644
--- a/synapse/handlers/auth.py
+++ b/synapse/handlers/auth.py
@@ -267,7 +267,7 @@ class AuthHandler(BaseHandler):
         user_id = authdict["user"]
         password = authdict["password"]
         if not user_id.startswith('@'):
-            user_id = UserID.create(user_id, self.hs.hostname).to_string()
+            user_id = UserID(user_id, self.hs.hostname).to_string()
 
         return self._check_password(user_id, password)
 
diff --git a/synapse/handlers/federation.py b/synapse/handlers/federation.py
index 7711cded01..8b1e606754 100644
--- a/synapse/handlers/federation.py
+++ b/synapse/handlers/federation.py
@@ -227,7 +227,7 @@ class FederationHandler(BaseHandler):
                 state, auth_chain = yield self.replication_layer.get_state_for_room(
                     origin, pdu.room_id, pdu.event_id,
                 )
-            except:
+            except Exception:
                 logger.exception("Failed to get state for event: %s", pdu.event_id)
 
         yield self._process_received_pdu(
@@ -461,7 +461,7 @@ class FederationHandler(BaseHandler):
         def check_match(id):
             try:
                 return server_name == get_domain_from_id(id)
-            except:
+            except Exception:
                 return False
 
         # Parses mapping `event_id -> (type, state_key) -> state event_id`
@@ -499,7 +499,7 @@ class FederationHandler(BaseHandler):
                             continue
                         try:
                             domain = get_domain_from_id(ev.state_key)
-                        except:
+                        except Exception:
                             continue
 
                         if domain != server_name:
@@ -738,7 +738,7 @@ class FederationHandler(BaseHandler):
                         joined_domains[dom] = min(d, old_d)
                     else:
                         joined_domains[dom] = d
-                except:
+                except Exception:
                     pass
 
             return sorted(joined_domains.items(), key=lambda d: d[1])
@@ -940,7 +940,7 @@ class FederationHandler(BaseHandler):
                     room_creator_user_id="",
                     is_public=False
                 )
-            except:
+            except Exception:
                 # FIXME
                 pass
 
@@ -1775,7 +1775,7 @@ class FederationHandler(BaseHandler):
                     [e_id for e_id, _ in event.auth_events]
                 )
                 seen_events = set(have_events.keys())
-            except:
+            except Exception:
                 # FIXME:
                 logger.exception("Failed to get auth chain")
 
@@ -1899,7 +1899,7 @@ class FederationHandler(BaseHandler):
                         except AuthError:
                             pass
 
-                except:
+                except Exception:
                     # FIXME:
                     logger.exception("Failed to query auth chain")
 
@@ -1966,7 +1966,7 @@ class FederationHandler(BaseHandler):
         def get_next(it, opt=None):
             try:
                 return it.next()
-            except:
+            except Exception:
                 return opt
 
         current_local = get_next(local_iter)
diff --git a/synapse/handlers/initial_sync.py b/synapse/handlers/initial_sync.py
index 10f5f35a69..9718d4abc5 100644
--- a/synapse/handlers/initial_sync.py
+++ b/synapse/handlers/initial_sync.py
@@ -214,7 +214,7 @@ class InitialSyncHandler(BaseHandler):
                     })
 
                 d["account_data"] = account_data_events
-            except:
+            except Exception:
                 logger.exception("Failed to get snapshot")
 
         yield concurrently_execute(handle_room, room_list, 10)
diff --git a/synapse/handlers/message.py b/synapse/handlers/message.py
index 28792788d9..21f1717dd2 100644
--- a/synapse/handlers/message.py
+++ b/synapse/handlers/message.py
@@ -563,7 +563,7 @@ class MessageHandler(BaseHandler):
         try:
             dump = ujson.dumps(unfreeze(event.content))
             ujson.loads(dump)
-        except:
+        except Exception:
             logger.exception("Failed to encode content: %r", event.content)
             raise
 
diff --git a/synapse/handlers/presence.py b/synapse/handlers/presence.py
index c7c0b0a1e2..fa96ea69cd 100644
--- a/synapse/handlers/presence.py
+++ b/synapse/handlers/presence.py
@@ -364,7 +364,7 @@ class PresenceHandler(object):
                 )
 
             preserve_fn(self._update_states)(changes)
-        except:
+        except Exception:
             logger.exception("Exception in _handle_timeouts loop")
 
     @defer.inlineCallbacks
diff --git a/synapse/handlers/profile.py b/synapse/handlers/profile.py
index e56e0a52bf..62b9bd503e 100644
--- a/synapse/handlers/profile.py
+++ b/synapse/handlers/profile.py
@@ -118,7 +118,7 @@ class ProfileHandler(BaseHandler):
                     logger.exception("Failed to get displayname")
 
                 raise
-            except:
+            except Exception:
                 logger.exception("Failed to get displayname")
             else:
                 defer.returnValue(result["displayname"])
@@ -165,7 +165,7 @@ class ProfileHandler(BaseHandler):
                 if e.code != 404:
                     logger.exception("Failed to get avatar_url")
                 raise
-            except:
+            except Exception:
                 logger.exception("Failed to get avatar_url")
 
             defer.returnValue(result["avatar_url"])
@@ -266,7 +266,7 @@ class ProfileHandler(BaseHandler):
                     },
                     ignore_backoff=True,
                 )
-            except:
+            except Exception:
                 logger.exception("Failed to get avatar_url")
 
                 yield self.store.update_remote_profile_cache(
diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py
index 560fb36254..49dc33c147 100644
--- a/synapse/handlers/register.py
+++ b/synapse/handlers/register.py
@@ -15,7 +15,6 @@
 
 """Contains functions for registering clients."""
 import logging
-import urllib
 
 from twisted.internet import defer
 
@@ -23,6 +22,7 @@ from synapse.api.errors import (
     AuthError, Codes, SynapseError, RegistrationError, InvalidCaptchaError
 )
 from synapse.http.client import CaptchaServerHttpClient
+from synapse import types
 from synapse.types import UserID
 from synapse.util.async import run_on_reactor
 from ._base import BaseHandler
@@ -46,12 +46,10 @@ class RegistrationHandler(BaseHandler):
     @defer.inlineCallbacks
     def check_username(self, localpart, guest_access_token=None,
                        assigned_user_id=None):
-        yield run_on_reactor()
-
-        if urllib.quote(localpart.encode('utf-8')) != localpart:
+        if types.contains_invalid_mxid_characters(localpart):
             raise SynapseError(
                 400,
-                "User ID can only contain characters a-z, 0-9, or '_-./'",
+                "User ID can only contain characters a-z, 0-9, or '=_-./'",
                 Codes.INVALID_USERNAME
             )
 
@@ -81,7 +79,7 @@ class RegistrationHandler(BaseHandler):
                     "A different user ID has already been registered for this session",
                 )
 
-        yield self.check_user_id_not_appservice_exclusive(user_id)
+        self.check_user_id_not_appservice_exclusive(user_id)
 
         users = yield self.store.get_users_by_id_case_insensitive(user_id)
         if users:
@@ -254,11 +252,10 @@ class RegistrationHandler(BaseHandler):
         """
         Registers email_id as SAML2 Based Auth.
         """
-        if urllib.quote(localpart) != localpart:
+        if types.contains_invalid_mxid_characters(localpart):
             raise SynapseError(
                 400,
-                "User ID must only contain characters which do not"
-                " require URL encoding."
+                "User ID can only contain characters a-z, 0-9, or '=_-./'",
             )
         user = UserID(localpart, self.hs.hostname)
         user_id = user.to_string()
@@ -292,7 +289,7 @@ class RegistrationHandler(BaseHandler):
             try:
                 identity_handler = self.hs.get_handlers().identity_handler
                 threepid = yield identity_handler.threepid_from_creds(c)
-            except:
+            except Exception:
                 logger.exception("Couldn't validate 3pid")
                 raise RegistrationError(400, "Couldn't validate 3pid")
 
diff --git a/synapse/handlers/room.py b/synapse/handlers/room.py
index 535ba9517c..496f1fc39b 100644
--- a/synapse/handlers/room.py
+++ b/synapse/handlers/room.py
@@ -91,7 +91,7 @@ class RoomCreationHandler(BaseHandler):
                 if wchar in config["room_alias_name"]:
                     raise SynapseError(400, "Invalid characters in room alias")
 
-            room_alias = RoomAlias.create(
+            room_alias = RoomAlias(
                 config["room_alias_name"],
                 self.hs.hostname,
             )
@@ -108,7 +108,7 @@ class RoomCreationHandler(BaseHandler):
         for i in invite_list:
             try:
                 UserID.from_string(i)
-            except:
+            except Exception:
                 raise SynapseError(400, "Invalid user_id: %s" % (i,))
 
         invite_3pid_list = config.get("invite_3pid", [])
@@ -123,7 +123,7 @@ class RoomCreationHandler(BaseHandler):
         while attempts < 5:
             try:
                 random_string = stringutils.random_string(18)
-                gen_room_id = RoomID.create(
+                gen_room_id = RoomID(
                     random_string,
                     self.hs.hostname,
                 )
diff --git a/synapse/handlers/search.py b/synapse/handlers/search.py
index df75d70fac..9772ed1a0e 100644
--- a/synapse/handlers/search.py
+++ b/synapse/handlers/search.py
@@ -61,7 +61,7 @@ class SearchHandler(BaseHandler):
                 assert batch_group is not None
                 assert batch_group_key is not None
                 assert batch_token is not None
-            except:
+            except Exception:
                 raise SynapseError(400, "Invalid batch")
 
         try:
diff --git a/synapse/http/matrixfederationclient.py b/synapse/http/matrixfederationclient.py
index 8c8b7fa656..833496b72d 100644
--- a/synapse/http/matrixfederationclient.py
+++ b/synapse/http/matrixfederationclient.py
@@ -550,7 +550,7 @@ class MatrixFederationHttpClient(object):
                 length = yield _readBodyToFile(
                     response, output_stream, max_size
                 )
-        except:
+        except Exception:
             logger.exception("Failed to download body")
             raise
 
diff --git a/synapse/http/server.py b/synapse/http/server.py
index 8a27e3b422..3ca1c9947c 100644
--- a/synapse/http/server.py
+++ b/synapse/http/server.py
@@ -130,7 +130,7 @@ def wrap_request_handler(request_handler, include_metrics=False):
                             pretty_print=_request_user_agent_is_curl(request),
                             version_string=self.version_string,
                         )
-                    except:
+                    except Exception:
                         logger.exception(
                             "Failed handle request %s.%s on %r: %r",
                             request_handler.__module__,
diff --git a/synapse/http/servlet.py b/synapse/http/servlet.py
index 9a4c36ad5d..8118ee7cc2 100644
--- a/synapse/http/servlet.py
+++ b/synapse/http/servlet.py
@@ -48,7 +48,7 @@ def parse_integer_from_args(args, name, default=None, required=False):
     if name in args:
         try:
             return int(args[name][0])
-        except:
+        except Exception:
             message = "Query parameter %r must be an integer" % (name,)
             raise SynapseError(400, message)
     else:
@@ -88,7 +88,7 @@ def parse_boolean_from_args(args, name, default=None, required=False):
                 "true": True,
                 "false": False,
             }[args[name][0]]
-        except:
+        except Exception:
             message = (
                 "Boolean query parameter %r must be one of"
                 " ['true', 'false']"
@@ -162,7 +162,7 @@ def parse_json_value_from_request(request):
     """
     try:
         content_bytes = request.content.read()
-    except:
+    except Exception:
         raise SynapseError(400, "Error reading JSON content.")
 
     try:
diff --git a/synapse/http/site.py b/synapse/http/site.py
index 4b09d7ee66..cd1492b1c3 100644
--- a/synapse/http/site.py
+++ b/synapse/http/site.py
@@ -67,7 +67,7 @@ class SynapseRequest(Request):
             ru_utime, ru_stime = context.get_resource_usage()
             db_txn_count = context.db_txn_count
             db_txn_duration = context.db_txn_duration
-        except:
+        except Exception:
             ru_utime, ru_stime = (0, 0)
             db_txn_count, db_txn_duration = (0, 0)
 
diff --git a/synapse/notifier.py b/synapse/notifier.py
index 385208b574..626da778cd 100644
--- a/synapse/notifier.py
+++ b/synapse/notifier.py
@@ -289,7 +289,7 @@ class Notifier(object):
                 for user_stream in user_streams:
                     try:
                         user_stream.notify(stream_key, new_token, time_now_ms)
-                    except:
+                    except Exception:
                         logger.exception("Failed to notify listener")
 
                 self.notify_replication()
diff --git a/synapse/push/emailpusher.py b/synapse/push/emailpusher.py
index a69dda7b09..58df98a793 100644
--- a/synapse/push/emailpusher.py
+++ b/synapse/push/emailpusher.py
@@ -121,7 +121,7 @@ class EmailPusher(object):
                         starting_max_ordering = self.max_stream_ordering
                         try:
                             yield self._unsafe_process()
-                        except:
+                        except Exception:
                             logger.exception("Exception processing notifs")
                         if self.max_stream_ordering == starting_max_ordering:
                             break
diff --git a/synapse/push/httppusher.py b/synapse/push/httppusher.py
index 62c41cd9db..74c0bc462c 100644
--- a/synapse/push/httppusher.py
+++ b/synapse/push/httppusher.py
@@ -131,7 +131,7 @@ class HttpPusher(object):
                         starting_max_ordering = self.max_stream_ordering
                         try:
                             yield self._unsafe_process()
-                        except:
+                        except Exception:
                             logger.exception("Exception processing notifs")
                         if self.max_stream_ordering == starting_max_ordering:
                             break
@@ -314,7 +314,7 @@ class HttpPusher(object):
             defer.returnValue([])
         try:
             resp = yield self.http_client.post_json_get_json(self.url, notification_dict)
-        except:
+        except Exception:
             logger.warn("Failed to push %s ", self.url)
             defer.returnValue(False)
         rejected = []
@@ -345,7 +345,7 @@ class HttpPusher(object):
         }
         try:
             resp = yield self.http_client.post_json_get_json(self.url, d)
-        except:
+        except Exception:
             logger.exception("Failed to push %s ", self.url)
             defer.returnValue(False)
         rejected = []
diff --git a/synapse/push/pusher.py b/synapse/push/pusher.py
index 491f27bded..71576330a9 100644
--- a/synapse/push/pusher.py
+++ b/synapse/push/pusher.py
@@ -27,7 +27,7 @@ logger = logging.getLogger(__name__)
 try:
     from synapse.push.emailpusher import EmailPusher
     from synapse.push.mailer import Mailer, load_jinja2_templates
-except:
+except Exception:
     pass
 
 
diff --git a/synapse/push/pusherpool.py b/synapse/push/pusherpool.py
index 43cb6e9c01..7c069b662e 100644
--- a/synapse/push/pusherpool.py
+++ b/synapse/push/pusherpool.py
@@ -137,7 +137,7 @@ class PusherPool:
                         )
 
             yield preserve_context_over_deferred(defer.gatherResults(deferreds))
-        except:
+        except Exception:
             logger.exception("Exception in pusher on_new_notifications")
 
     @defer.inlineCallbacks
@@ -162,7 +162,7 @@ class PusherPool:
                         )
 
             yield preserve_context_over_deferred(defer.gatherResults(deferreds))
-        except:
+        except Exception:
             logger.exception("Exception in pusher on_new_receipts")
 
     @defer.inlineCallbacks
@@ -188,7 +188,7 @@ class PusherPool:
         for pusherdict in pushers:
             try:
                 p = self.pusher_factory.create_pusher(pusherdict)
-            except:
+            except Exception:
                 logger.exception("Couldn't start a pusher: caught Exception")
                 continue
             if p:
diff --git a/synapse/replication/tcp/resource.py b/synapse/replication/tcp/resource.py
index 6c1beca4e3..1d03e79b85 100644
--- a/synapse/replication/tcp/resource.py
+++ b/synapse/replication/tcp/resource.py
@@ -162,7 +162,7 @@ class ReplicationStreamer(object):
                         )
                         try:
                             updates, current_token = yield stream.get_updates()
-                        except:
+                        except Exception:
                             logger.info("Failed to handle stream %s", stream.NAME)
                             raise
 
diff --git a/synapse/rest/client/v1/directory.py b/synapse/rest/client/v1/directory.py
index f15aa5c13f..1c3933380f 100644
--- a/synapse/rest/client/v1/directory.py
+++ b/synapse/rest/client/v1/directory.py
@@ -93,7 +93,7 @@ class ClientDirectoryServer(ClientV1RestServlet):
                 )
             except SynapseError as e:
                 raise e
-            except:
+            except Exception:
                 logger.exception("Failed to create association")
                 raise
         except AuthError:
diff --git a/synapse/rest/client/v1/login.py b/synapse/rest/client/v1/login.py
index a43410fb37..9536e8ade6 100644
--- a/synapse/rest/client/v1/login.py
+++ b/synapse/rest/client/v1/login.py
@@ -211,7 +211,7 @@ class LoginRestServlet(ClientV1RestServlet):
         user_id = identifier["user"]
 
         if not user_id.startswith('@'):
-            user_id = UserID.create(
+            user_id = UserID(
                 user_id, self.hs.hostname
             ).to_string()
 
@@ -278,7 +278,7 @@ class LoginRestServlet(ClientV1RestServlet):
         if user is None:
             raise LoginError(401, "Invalid JWT", errcode=Codes.UNAUTHORIZED)
 
-        user_id = UserID.create(user, self.hs.hostname).to_string()
+        user_id = UserID(user, self.hs.hostname).to_string()
         auth_handler = self.auth_handler
         registered_user_id = yield auth_handler.check_user_exists(user_id)
         if registered_user_id:
@@ -444,7 +444,7 @@ class CasTicketServlet(ClientV1RestServlet):
                 if required_value != actual_value:
                     raise LoginError(401, "Unauthorized", errcode=Codes.UNAUTHORIZED)
 
-        user_id = UserID.create(user, self.hs.hostname).to_string()
+        user_id = UserID(user, self.hs.hostname).to_string()
         auth_handler = self.auth_handler
         registered_user_id = yield auth_handler.check_user_exists(user_id)
         if not registered_user_id:
diff --git a/synapse/rest/client/v1/presence.py b/synapse/rest/client/v1/presence.py
index 47b2dc45e7..4a73813c58 100644
--- a/synapse/rest/client/v1/presence.py
+++ b/synapse/rest/client/v1/presence.py
@@ -78,7 +78,7 @@ class PresenceStatusRestServlet(ClientV1RestServlet):
                 raise KeyError()
         except SynapseError as e:
             raise e
-        except:
+        except Exception:
             raise SynapseError(400, "Unable to parse state")
 
         yield self.presence_handler.set_state(user, state)
diff --git a/synapse/rest/client/v1/profile.py b/synapse/rest/client/v1/profile.py
index d7edc34245..e4e3611a14 100644
--- a/synapse/rest/client/v1/profile.py
+++ b/synapse/rest/client/v1/profile.py
@@ -52,7 +52,7 @@ class ProfileDisplaynameRestServlet(ClientV1RestServlet):
 
         try:
             new_name = content["displayname"]
-        except:
+        except Exception:
             defer.returnValue((400, "Unable to parse name"))
 
         yield self.profile_handler.set_displayname(
@@ -94,7 +94,7 @@ class ProfileAvatarURLRestServlet(ClientV1RestServlet):
         content = parse_json_object_from_request(request)
         try:
             new_name = content["avatar_url"]
-        except:
+        except Exception:
             defer.returnValue((400, "Unable to parse name"))
 
         yield self.profile_handler.set_avatar_url(
diff --git a/synapse/rest/client/v1/room.py b/synapse/rest/client/v1/room.py
index 6c379d53ac..75b735b47d 100644
--- a/synapse/rest/client/v1/room.py
+++ b/synapse/rest/client/v1/room.py
@@ -238,7 +238,7 @@ class JoinRoomAliasServlet(ClientV1RestServlet):
 
         try:
             content = parse_json_object_from_request(request)
-        except:
+        except Exception:
             # Turns out we used to ignore the body entirely, and some clients
             # cheekily send invalid bodies.
             content = {}
@@ -247,7 +247,7 @@ class JoinRoomAliasServlet(ClientV1RestServlet):
             room_id = room_identifier
             try:
                 remote_room_hosts = request.args["server_name"]
-            except:
+            except Exception:
                 remote_room_hosts = None
         elif RoomAlias.is_valid(room_identifier):
             handler = self.handlers.room_member_handler
@@ -587,7 +587,7 @@ class RoomMembershipRestServlet(ClientV1RestServlet):
 
         try:
             content = parse_json_object_from_request(request)
-        except:
+        except Exception:
             # Turns out we used to ignore the body entirely, and some clients
             # cheekily send invalid bodies.
             content = {}
diff --git a/synapse/rest/client/v2_alpha/filter.py b/synapse/rest/client/v2_alpha/filter.py
index d2b2fd66e6..1b9dc4528d 100644
--- a/synapse/rest/client/v2_alpha/filter.py
+++ b/synapse/rest/client/v2_alpha/filter.py
@@ -50,7 +50,7 @@ class GetFilterRestServlet(RestServlet):
 
         try:
             filter_id = int(filter_id)
-        except:
+        except Exception:
             raise SynapseError(400, "Invalid filter_id")
 
         try:
diff --git a/synapse/rest/client/v2_alpha/groups.py b/synapse/rest/client/v2_alpha/groups.py
index d11bccc1da..100f47ca9e 100644
--- a/synapse/rest/client/v2_alpha/groups.py
+++ b/synapse/rest/client/v2_alpha/groups.py
@@ -412,7 +412,7 @@ class GroupCreateServlet(RestServlet):
         # TODO: Create group on remote server
         content = parse_json_object_from_request(request)
         localpart = content.pop("localpart")
-        group_id = GroupID.create(localpart, self.server_name).to_string()
+        group_id = GroupID(localpart, self.server_name).to_string()
 
         result = yield self.groups_handler.create_group(group_id, user_id, content)
 
diff --git a/synapse/rest/client/v2_alpha/sync.py b/synapse/rest/client/v2_alpha/sync.py
index a1e0e53b33..a0a8e4b8e4 100644
--- a/synapse/rest/client/v2_alpha/sync.py
+++ b/synapse/rest/client/v2_alpha/sync.py
@@ -125,7 +125,7 @@ class SyncRestServlet(RestServlet):
                     filter_object = json.loads(filter_id)
                     set_timeline_upper_limit(filter_object,
                                              self.hs.config.filter_timeline_limit)
-                except:
+                except Exception:
                     raise SynapseError(400, "Invalid filter JSON")
                 self.filtering.check_valid_filter(filter_object)
                 filter = FilterCollection(filter_object)
diff --git a/synapse/rest/client/v2_alpha/user_directory.py b/synapse/rest/client/v2_alpha/user_directory.py
index 6e012da4aa..2d4a43c353 100644
--- a/synapse/rest/client/v2_alpha/user_directory.py
+++ b/synapse/rest/client/v2_alpha/user_directory.py
@@ -65,7 +65,7 @@ class UserDirectorySearchRestServlet(RestServlet):
 
         try:
             search_term = body["search_term"]
-        except:
+        except Exception:
             raise SynapseError(400, "`search_term` is required field")
 
         results = yield self.user_directory_handler.search_users(
diff --git a/synapse/rest/key/v2/remote_key_resource.py b/synapse/rest/key/v2/remote_key_resource.py
index 9fe2013657..cc2842aa72 100644
--- a/synapse/rest/key/v2/remote_key_resource.py
+++ b/synapse/rest/key/v2/remote_key_resource.py
@@ -213,7 +213,7 @@ class RemoteKey(Resource):
                     )
                 except KeyLookupError as e:
                     logger.info("Failed to fetch key: %s", e)
-                except:
+                except Exception:
                     logger.exception("Failed to get key for %r", server_name)
             yield self.query_keys(
                 request, query, query_remote_on_cache_miss=False
diff --git a/synapse/rest/media/v1/_base.py b/synapse/rest/media/v1/_base.py
index b9600f2167..95fa95fce3 100644
--- a/synapse/rest/media/v1/_base.py
+++ b/synapse/rest/media/v1/_base.py
@@ -17,6 +17,7 @@ from synapse.http.server import respond_with_json, finish_request
 from synapse.api.errors import (
     cs_error, Codes, SynapseError
 )
+from synapse.util import logcontext
 
 from twisted.internet import defer
 from twisted.protocols.basic import FileSender
@@ -44,7 +45,7 @@ def parse_media_id(request):
             except UnicodeDecodeError:
                 pass
         return server_name, media_id, file_name
-    except:
+    except Exception:
         raise SynapseError(
             404,
             "Invalid media id token %r" % (request.postpath,),
@@ -103,7 +104,9 @@ def respond_with_file(request, media_type, file_path,
         )
 
         with open(file_path, "rb") as f:
-            yield FileSender().beginFileTransfer(f, request)
+            yield logcontext.make_deferred_yieldable(
+                FileSender().beginFileTransfer(f, request)
+            )
 
         finish_request(request)
     else:
diff --git a/synapse/rest/media/v1/media_repository.py b/synapse/rest/media/v1/media_repository.py
index 6b50b45b1f..eed9056a2f 100644
--- a/synapse/rest/media/v1/media_repository.py
+++ b/synapse/rest/media/v1/media_repository.py
@@ -310,7 +310,7 @@ class MediaRepository(object):
                 media_length=length,
                 filesystem_id=file_id,
             )
-        except:
+        except Exception:
             os.remove(fname)
             raise
 
diff --git a/synapse/rest/media/v1/preview_url_resource.py b/synapse/rest/media/v1/preview_url_resource.py
index 2a3e37fdf4..80114fca0d 100644
--- a/synapse/rest/media/v1/preview_url_resource.py
+++ b/synapse/rest/media/v1/preview_url_resource.py
@@ -367,7 +367,7 @@ class PreviewUrlResource(Resource):
                 dirs = self.filepaths.url_cache_filepath_dirs_to_delete(media_id)
                 for dir in dirs:
                     os.rmdir(dir)
-            except:
+            except Exception:
                 pass
 
         yield self.store.delete_url_cache(removed_media)
@@ -397,7 +397,7 @@ class PreviewUrlResource(Resource):
                 dirs = self.filepaths.url_cache_filepath_dirs_to_delete(media_id)
                 for dir in dirs:
                     os.rmdir(dir)
-            except:
+            except Exception:
                 pass
 
             thumbnail_dir = self.filepaths.url_cache_thumbnail_directory(media_id)
@@ -415,7 +415,7 @@ class PreviewUrlResource(Resource):
                 dirs = self.filepaths.url_cache_thumbnail_dirs_to_delete(media_id)
                 for dir in dirs:
                     os.rmdir(dir)
-            except:
+            except Exception:
                 pass
 
         yield self.store.delete_url_cache_media(removed_media)
diff --git a/synapse/state.py b/synapse/state.py
index dcdcdef65e..9e624b4937 100644
--- a/synapse/state.py
+++ b/synapse/state.py
@@ -560,7 +560,7 @@ def _resolve_with_state(unconflicted_state_ids, conflicted_state_ds, auth_event_
         resolved_state = _resolve_state_events(
             conflicted_state, auth_events
         )
-    except:
+    except Exception:
         logger.exception("Failed to resolve state")
         raise
 
diff --git a/synapse/storage/_base.py b/synapse/storage/_base.py
index 5124a833a5..6caf7b3356 100644
--- a/synapse/storage/_base.py
+++ b/synapse/storage/_base.py
@@ -103,7 +103,7 @@ class LoggingTransaction(object):
                     "[SQL values] {%s} %r",
                     self.name, args[0]
                 )
-            except:
+            except Exception:
                 # Don't let logging failures stop SQL from working
                 pass
 
diff --git a/synapse/storage/background_updates.py b/synapse/storage/background_updates.py
index 7157fb1dfb..a6e6f52a6a 100644
--- a/synapse/storage/background_updates.py
+++ b/synapse/storage/background_updates.py
@@ -98,7 +98,7 @@ class BackgroundUpdateStore(SQLBaseStore):
                 result = yield self.do_next_background_update(
                     self.BACKGROUND_UPDATE_DURATION_MS
                 )
-            except:
+            except Exception:
                 logger.exception("Error doing update")
             else:
                 if result is None:
diff --git a/synapse/storage/events.py b/synapse/storage/events.py
index 637640ec2a..4298d8baf1 100644
--- a/synapse/storage/events.py
+++ b/synapse/storage/events.py
@@ -1481,7 +1481,7 @@ class EventsStore(SQLBaseStore):
                                         for i in ids
                                         if i in res
                                     ])
-                            except:
+                            except Exception:
                                 logger.exception("Failed to callback")
                 with PreserveLoggingContext():
                     reactor.callFromThread(fire, event_list, row_dict)
diff --git a/synapse/storage/prepare_database.py b/synapse/storage/prepare_database.py
index ccaaabcfa0..817c2185c8 100644
--- a/synapse/storage/prepare_database.py
+++ b/synapse/storage/prepare_database.py
@@ -66,7 +66,7 @@ def prepare_database(db_conn, database_engine, config):
 
         cur.close()
         db_conn.commit()
-    except:
+    except Exception:
         db_conn.rollback()
         raise
 
diff --git a/synapse/storage/roommember.py b/synapse/storage/roommember.py
index a0fc9a6867..3fa8019eb7 100644
--- a/synapse/storage/roommember.py
+++ b/synapse/storage/roommember.py
@@ -636,7 +636,7 @@ class RoomMemberStore(SQLBaseStore):
                 room_id = row["room_id"]
                 try:
                     content = json.loads(row["content"])
-                except:
+                except Exception:
                     continue
 
                 display_name = content.get("displayname", None)
diff --git a/synapse/storage/schema/delta/30/as_users.py b/synapse/storage/schema/delta/30/as_users.py
index 5b7d8d1ab5..c53e53c94f 100644
--- a/synapse/storage/schema/delta/30/as_users.py
+++ b/synapse/storage/schema/delta/30/as_users.py
@@ -22,7 +22,7 @@ def run_create(cur, database_engine, *args, **kwargs):
     # NULL indicates user was not registered by an appservice.
     try:
         cur.execute("ALTER TABLE users ADD COLUMN appservice_id TEXT")
-    except:
+    except Exception:
         # Maybe we already added the column? Hope so...
         pass
 
diff --git a/synapse/storage/search.py b/synapse/storage/search.py
index 8f2b3c4435..05d4ef586e 100644
--- a/synapse/storage/search.py
+++ b/synapse/storage/search.py
@@ -81,7 +81,7 @@ class SearchStore(BackgroundUpdateStore):
                     etype = row["type"]
                     try:
                         content = json.loads(row["content"])
-                    except:
+                    except Exception:
                         continue
 
                     if etype == "m.room.message":
@@ -407,7 +407,7 @@ class SearchStore(BackgroundUpdateStore):
                 origin_server_ts, stream = pagination_token.split(",")
                 origin_server_ts = int(origin_server_ts)
                 stream = int(stream)
-            except:
+            except Exception:
                 raise SynapseError(400, "Invalid pagination token")
 
             clauses.append(
diff --git a/synapse/streams/config.py b/synapse/streams/config.py
index 4f089bfb94..ca78e551cb 100644
--- a/synapse/streams/config.py
+++ b/synapse/streams/config.py
@@ -80,13 +80,13 @@ class PaginationConfig(object):
                 from_tok = None  # For backwards compat.
             elif from_tok:
                 from_tok = StreamToken.from_string(from_tok)
-        except:
+        except Exception:
             raise SynapseError(400, "'from' paramater is invalid")
 
         try:
             if to_tok:
                 to_tok = StreamToken.from_string(to_tok)
-        except:
+        except Exception:
             raise SynapseError(400, "'to' paramater is invalid")
 
         limit = get_param("limit", None)
@@ -98,7 +98,7 @@ class PaginationConfig(object):
 
         try:
             return PaginationConfig(from_tok, to_tok, direction, limit)
-        except:
+        except Exception:
             logger.exception("Failed to create pagination config")
             raise SynapseError(400, "Invalid request.")
 
diff --git a/synapse/types.py b/synapse/types.py
index 37d5fa7f9f..6e76c016d9 100644
--- a/synapse/types.py
+++ b/synapse/types.py
@@ -12,6 +12,7 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+import string
 
 from synapse.api.errors import SynapseError
 
@@ -126,15 +127,11 @@ class DomainSpecificString(
         try:
             cls.from_string(s)
             return True
-        except:
+        except Exception:
             return False
 
     __str__ = to_string
 
-    @classmethod
-    def create(cls, localpart, domain,):
-        return cls(localpart=localpart, domain=domain)
-
 
 class UserID(DomainSpecificString):
     """Structure representing a user ID."""
@@ -160,6 +157,38 @@ class GroupID(DomainSpecificString):
     """Structure representing a group ID."""
     SIGIL = "+"
 
+    @classmethod
+    def from_string(cls, s):
+        group_id = super(GroupID, cls).from_string(s)
+        if not group_id.localpart:
+            raise SynapseError(
+                400,
+                "Group ID cannot be empty",
+            )
+
+        if contains_invalid_mxid_characters(group_id.localpart):
+            raise SynapseError(
+                400,
+                "Group ID can only contain characters a-z, 0-9, or '=_-./'",
+            )
+
+        return group_id
+
+
+mxid_localpart_allowed_characters = set("_-./=" + string.ascii_lowercase + string.digits)
+
+
+def contains_invalid_mxid_characters(localpart):
+    """Check for characters not allowed in an mxid or groupid localpart
+
+    Args:
+        localpart (basestring): the localpart to be checked
+
+    Returns:
+        bool: True if there are any naughty characters
+    """
+    return any(c not in mxid_localpart_allowed_characters for c in localpart)
+
 
 class StreamToken(
     namedtuple("Token", (
@@ -184,7 +213,7 @@ class StreamToken(
                 # i.e. old token from before receipt_key
                 keys.append("0")
             return cls(*keys)
-        except:
+        except Exception:
             raise SynapseError(400, "Invalid Token")
 
     def to_string(self):
@@ -270,7 +299,7 @@ class RoomStreamToken(namedtuple("_StreamToken", "topological stream")):
             if string[0] == 't':
                 parts = string[1:].split('-', 1)
                 return cls(topological=int(parts[0]), stream=int(parts[1]))
-        except:
+        except Exception:
             pass
         raise SynapseError(400, "Invalid token %r" % (string,))
 
@@ -279,7 +308,7 @@ class RoomStreamToken(namedtuple("_StreamToken", "topological stream")):
         try:
             if string[0] == 's':
                 return cls(topological=None, stream=int(string[1:]))
-        except:
+        except Exception:
             pass
         raise SynapseError(400, "Invalid token %r" % (string,))
 
diff --git a/synapse/util/__init__.py b/synapse/util/__init__.py
index 2a2360ab5d..756d8ffa32 100644
--- a/synapse/util/__init__.py
+++ b/synapse/util/__init__.py
@@ -59,9 +59,9 @@ class Clock(object):
             f(function): The function to call repeatedly.
             msec(float): How long to wait between calls in milliseconds.
         """
-        l = task.LoopingCall(f)
-        l.start(msec / 1000.0, now=False)
-        return l
+        call = task.LoopingCall(f)
+        call.start(msec / 1000.0, now=False)
+        return call
 
     def call_later(self, delay, callback, *args, **kwargs):
         """Call something later
@@ -82,7 +82,7 @@ class Clock(object):
     def cancel_call_later(self, timer, ignore_errs=False):
         try:
             timer.cancel()
-        except:
+        except Exception:
             if not ignore_errs:
                 raise
 
@@ -97,12 +97,12 @@ class Clock(object):
 
             try:
                 ret_deferred.errback(e)
-            except:
+            except Exception:
                 pass
 
             try:
                 given_deferred.cancel()
-            except:
+            except Exception:
                 pass
 
         timer = None
@@ -110,7 +110,7 @@ class Clock(object):
         def cancel(res):
             try:
                 self.cancel_call_later(timer)
-            except:
+            except Exception:
                 pass
             return res
 
@@ -119,7 +119,7 @@ class Clock(object):
         def success(res):
             try:
                 ret_deferred.callback(res)
-            except:
+            except Exception:
                 pass
 
             return res
@@ -127,7 +127,7 @@ class Clock(object):
         def err(res):
             try:
                 ret_deferred.errback(res)
-            except:
+            except Exception:
                 pass
 
         given_deferred.addCallbacks(callback=success, errback=err)
diff --git a/synapse/util/async.py b/synapse/util/async.py
index a0a9039475..1a884e96ee 100644
--- a/synapse/util/async.py
+++ b/synapse/util/async.py
@@ -73,7 +73,7 @@ class ObservableDeferred(object):
                 try:
                     # TODO: Handle errors here.
                     self._observers.pop().callback(r)
-                except:
+                except Exception:
                     pass
             return r
 
@@ -83,7 +83,7 @@ class ObservableDeferred(object):
                 try:
                     # TODO: Handle errors here.
                     self._observers.pop().errback(f)
-                except:
+                except Exception:
                     pass
 
             if consumeErrors:
@@ -205,7 +205,7 @@ class Linearizer(object):
             try:
                 with PreserveLoggingContext():
                     yield current_defer
-            except:
+            except Exception:
                 logger.exception("Unexpected exception in Linearizer")
 
             logger.info("Acquired linearizer lock %r for key %r", self.name,
diff --git a/synapse/util/logcontext.py b/synapse/util/logcontext.py
index 990216145e..9683cc7265 100644
--- a/synapse/util/logcontext.py
+++ b/synapse/util/logcontext.py
@@ -42,7 +42,7 @@ try:
 
     def get_thread_resource_usage():
         return resource.getrusage(RUSAGE_THREAD)
-except:
+except Exception:
     # If the system doesn't support resource.getrusage(RUSAGE_THREAD) then we
     # won't track resource usage by returning None.
     def get_thread_resource_usage():
diff --git a/synapse/util/retryutils.py b/synapse/util/retryutils.py
index 4fa9d1a03c..1adedbb361 100644
--- a/synapse/util/retryutils.py
+++ b/synapse/util/retryutils.py
@@ -189,7 +189,7 @@ class RetryDestinationLimiter(object):
                 yield self.store.set_destination_retry_timings(
                     self.destination, retry_last_ts, self.retry_interval
                 )
-            except:
+            except Exception:
                 logger.exception(
                     "Failed to store set_destination_retry_timings",
                 )
diff --git a/synapse/util/wheel_timer.py b/synapse/util/wheel_timer.py
index 7412fc57a4..b70f9a6b0a 100644
--- a/synapse/util/wheel_timer.py
+++ b/synapse/util/wheel_timer.py
@@ -91,7 +91,4 @@ class WheelTimer(object):
         return ret
 
     def __len__(self):
-        l = 0
-        for entry in self.entries:
-            l += len(entry.queue)
-        return l
+        return sum(len(entry.queue) for entry in self.entries)
diff --git a/tests/storage/test_appservice.py b/tests/storage/test_appservice.py
index 9e98d0e330..79f569e787 100644
--- a/tests/storage/test_appservice.py
+++ b/tests/storage/test_appservice.py
@@ -65,7 +65,7 @@ class ApplicationServiceStoreTestCase(unittest.TestCase):
         for f in self.as_yaml_files:
             try:
                 os.remove(f)
-            except:
+            except Exception:
                 pass
 
     def _add_appservice(self, as_token, id, url, hs_token, sender):
diff --git a/tests/test_types.py b/tests/test_types.py
index 24d61dbe54..115def2287 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -17,7 +17,7 @@ from tests import unittest
 
 from synapse.api.errors import SynapseError
 from synapse.server import HomeServer
-from synapse.types import UserID, RoomAlias
+from synapse.types import UserID, RoomAlias, GroupID
 
 mock_homeserver = HomeServer(hostname="my.domain")
 
@@ -60,3 +60,25 @@ class RoomAliasTestCase(unittest.TestCase):
         room = RoomAlias("channel", "my.domain")
 
         self.assertEquals(room.to_string(), "#channel:my.domain")
+
+
+class GroupIDTestCase(unittest.TestCase):
+    def test_parse(self):
+        group_id = GroupID.from_string("+group/=_-.123:my.domain")
+        self.assertEqual("group/=_-.123", group_id.localpart)
+        self.assertEqual("my.domain", group_id.domain)
+
+    def test_validate(self):
+        bad_ids = [
+            "$badsigil:domain",
+            "+:empty",
+        ] + [
+            "+group" + c + ":domain" for c in "A%?æ£"
+        ]
+        for id_string in bad_ids:
+            try:
+                GroupID.from_string(id_string)
+                self.fail("Parsing '%s' should raise exception" % id_string)
+            except SynapseError as exc:
+                self.assertEqual(400, exc.code)
+                self.assertEqual("M_UNKNOWN", exc.errcode)
diff --git a/tests/utils.py b/tests/utils.py
index 3c81a3e16d..d2ebce4b2e 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -184,7 +184,7 @@ class MockHttpResource(HttpServer):
             mock_request.args = urlparse.parse_qs(path.split('?')[1])
             mock_request.path = path.split('?')[0]
             path = mock_request.path
-        except:
+        except Exception:
             pass
 
         for (method, pattern, func) in self.callbacks:
@@ -364,13 +364,13 @@ class MemoryDataStore(object):
             return {
                 "name": self.tokens_to_users[token],
             }
-        except:
+        except Exception:
             raise StoreError(400, "User does not exist.")
 
     def get_room(self, room_id):
         try:
             return self.rooms[room_id]
-        except:
+        except Exception:
             return None
 
     def store_room(self, room_id, room_creator_user_id, is_public):
@@ -499,7 +499,7 @@ class DeferredMockCallable(object):
         for _, _, d in self.expectations:
             try:
                 d.errback(failure)
-            except:
+            except Exception:
                 pass
 
         raise failure