diff --git a/synapse/api/auth.py b/synapse/api/auth.py
index 1ab27da941..b17025c7ce 100644
--- a/synapse/api/auth.py
+++ b/synapse/api/auth.py
@@ -39,6 +39,9 @@ AuthEventTypes = (
EventTypes.ThirdPartyInvite,
)
+# guests always get this device id.
+GUEST_DEVICE_ID = "guest_device"
+
class Auth(object):
"""
@@ -717,7 +720,8 @@ class Auth(object):
"user": user,
"is_guest": True,
"token_id": None,
- "device_id": None,
+ # all guests get the same device id
+ "device_id": GUEST_DEVICE_ID,
}
elif rights == "delete_pusher":
# We don't store these tokens in the database
@@ -790,9 +794,6 @@ class Auth(object):
type_string(str): The kind of token required (e.g. "access", "refresh",
"delete_pusher")
verify_expiry(bool): Whether to verify whether the macaroon has expired.
- This should really always be True, but there exist access tokens
- in the wild which expire when they should not, so we can't
- enforce expiry yet.
user_id (str): The user_id required
"""
v = pymacaroons.Verifier()
@@ -805,11 +806,24 @@ class Auth(object):
v.satisfy_exact("type = " + type_string)
v.satisfy_exact("user_id = %s" % user_id)
v.satisfy_exact("guest = true")
+
+ # verify_expiry should really always be True, but there exist access
+ # tokens in the wild which expire when they should not, so we can't
+ # enforce expiry yet (so we have to allow any caveat starting with
+ # 'time < ' in access tokens).
+ #
+ # On the other hand, short-term login tokens (as used by CAS login, for
+ # example) have an expiry time which we do want to enforce.
+
if verify_expiry:
v.satisfy_general(self._verify_expiry)
else:
v.satisfy_general(lambda c: c.startswith("time < "))
+ # access_tokens and refresh_tokens include a nonce for uniqueness: any
+ # value is acceptable
+ v.satisfy_general(lambda c: c.startswith("nonce = "))
+
v.verify(macaroon, self.hs.config.macaroon_secret_key)
def _verify_expiry(self, caveat):
diff --git a/synapse/config/registration.py b/synapse/config/registration.py
index cc3f879857..87e500c97a 100644
--- a/synapse/config/registration.py
+++ b/synapse/config/registration.py
@@ -32,7 +32,6 @@ class RegistrationConfig(Config):
)
self.registration_shared_secret = config.get("registration_shared_secret")
- self.user_creation_max_duration = int(config["user_creation_max_duration"])
self.bcrypt_rounds = config.get("bcrypt_rounds", 12)
self.trusted_third_party_id_servers = config["trusted_third_party_id_servers"]
@@ -55,11 +54,6 @@ class RegistrationConfig(Config):
# secret, even if registration is otherwise disabled.
registration_shared_secret: "%(registration_shared_secret)s"
- # Sets the expiry for the short term user creation in
- # milliseconds. For instance the bellow duration is two weeks
- # in milliseconds.
- user_creation_max_duration: 1209600000
-
# Set the number of bcrypt rounds used to generate password hash.
# Larger numbers increase the work factor needed to generate the hash.
# The default number of rounds is 12.
diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py
index 8984f87f96..91e7e725b9 100644
--- a/synapse/handlers/auth.py
+++ b/synapse/handlers/auth.py
@@ -526,14 +526,15 @@ class AuthHandler(BaseHandler):
device_id)
defer.returnValue(access_token)
- def generate_access_token(self, user_id, extra_caveats=None,
- duration_in_ms=(60 * 60 * 1000)):
+ def generate_access_token(self, user_id, extra_caveats=None):
extra_caveats = extra_caveats or []
macaroon = self._generate_base_macaroon(user_id)
macaroon.add_first_party_caveat("type = access")
- now = self.hs.get_clock().time_msec()
- expiry = now + duration_in_ms
- macaroon.add_first_party_caveat("time < %d" % (expiry,))
+ # Include a nonce, to make sure that each login gets a different
+ # access token.
+ macaroon.add_first_party_caveat("nonce = %s" % (
+ stringutils.random_string_with_symbols(16),
+ ))
for caveat in extra_caveats:
macaroon.add_first_party_caveat(caveat)
return macaroon.serialize()
diff --git a/synapse/handlers/register.py b/synapse/handlers/register.py
index 7e119f13b1..886fec8701 100644
--- a/synapse/handlers/register.py
+++ b/synapse/handlers/register.py
@@ -369,7 +369,7 @@ class RegistrationHandler(BaseHandler):
defer.returnValue(data)
@defer.inlineCallbacks
- def get_or_create_user(self, requester, localpart, displayname, duration_in_ms,
+ def get_or_create_user(self, requester, localpart, displayname,
password_hash=None):
"""Creates a new user if the user does not exist,
else revokes all previous access tokens and generates a new one.
@@ -399,8 +399,7 @@ class RegistrationHandler(BaseHandler):
user = UserID(localpart, self.hs.hostname)
user_id = user.to_string()
- token = self.auth_handler().generate_access_token(
- user_id, None, duration_in_ms)
+ token = self.auth_handler().generate_access_token(user_id)
if need_register:
yield self.store.register(
diff --git a/synapse/http/matrixfederationclient.py b/synapse/http/matrixfederationclient.py
index d0556ae347..d5970c05a8 100644
--- a/synapse/http/matrixfederationclient.py
+++ b/synapse/http/matrixfederationclient.py
@@ -33,6 +33,7 @@ from synapse.api.errors import (
from signedjson.sign import sign_json
+import cgi
import simplejson as json
import logging
import random
@@ -292,12 +293,7 @@ class MatrixFederationHttpClient(object):
if 200 <= response.code < 300:
# We need to update the transactions table to say it was sent?
- c_type = response.headers.getRawHeaders("Content-Type")
-
- if "application/json" not in c_type:
- raise RuntimeError(
- "Content-Type not application/json"
- )
+ check_content_type_is_json(response.headers)
body = yield preserve_context_over_fn(readBody, response)
defer.returnValue(json.loads(body))
@@ -342,12 +338,7 @@ class MatrixFederationHttpClient(object):
if 200 <= response.code < 300:
# We need to update the transactions table to say it was sent?
- c_type = response.headers.getRawHeaders("Content-Type")
-
- if "application/json" not in c_type:
- raise RuntimeError(
- "Content-Type not application/json"
- )
+ check_content_type_is_json(response.headers)
body = yield preserve_context_over_fn(readBody, response)
@@ -400,12 +391,7 @@ class MatrixFederationHttpClient(object):
if 200 <= response.code < 300:
# We need to update the transactions table to say it was sent?
- c_type = response.headers.getRawHeaders("Content-Type")
-
- if "application/json" not in c_type:
- raise RuntimeError(
- "Content-Type not application/json"
- )
+ check_content_type_is_json(response.headers)
body = yield preserve_context_over_fn(readBody, response)
@@ -525,3 +511,29 @@ def _flatten_response_never_received(e):
)
else:
return "%s: %s" % (type(e).__name__, e.message,)
+
+
+def check_content_type_is_json(headers):
+ """
+ Check that a set of HTTP headers have a Content-Type header, and that it
+ is application/json.
+
+ Args:
+ headers (twisted.web.http_headers.Headers): headers to check
+
+ Raises:
+ RuntimeError if the
+
+ """
+ c_type = headers.getRawHeaders("Content-Type")
+ if c_type is None:
+ raise RuntimeError(
+ "No Content-Type header"
+ )
+
+ c_type = c_type[0] # only the first header
+ val, options = cgi.parse_header(c_type)
+ if val != "application/json":
+ raise RuntimeError(
+ "Content-Type not application/json: was '%s'" % c_type
+ )
diff --git a/synapse/rest/client/v1/register.py b/synapse/rest/client/v1/register.py
index b5a76fefac..ecf7e311a9 100644
--- a/synapse/rest/client/v1/register.py
+++ b/synapse/rest/client/v1/register.py
@@ -384,7 +384,6 @@ class CreateUserRestServlet(ClientV1RestServlet):
def __init__(self, hs):
super(CreateUserRestServlet, self).__init__(hs)
self.store = hs.get_datastore()
- self.direct_user_creation_max_duration = hs.config.user_creation_max_duration
self.handlers = hs.get_handlers()
@defer.inlineCallbacks
@@ -418,18 +417,8 @@ class CreateUserRestServlet(ClientV1RestServlet):
if "displayname" not in user_json:
raise SynapseError(400, "Expected 'displayname' key.")
- if "duration_seconds" not in user_json:
- raise SynapseError(400, "Expected 'duration_seconds' key.")
-
localpart = user_json["localpart"].encode("utf-8")
displayname = user_json["displayname"].encode("utf-8")
- duration_seconds = 0
- try:
- duration_seconds = int(user_json["duration_seconds"])
- except ValueError:
- raise SynapseError(400, "Failed to parse 'duration_seconds'")
- if duration_seconds > self.direct_user_creation_max_duration:
- duration_seconds = self.direct_user_creation_max_duration
password_hash = user_json["password_hash"].encode("utf-8") \
if user_json.get("password_hash") else None
@@ -438,7 +427,6 @@ class CreateUserRestServlet(ClientV1RestServlet):
requester=requester,
localpart=localpart,
displayname=displayname,
- duration_in_ms=(duration_seconds * 1000),
password_hash=password_hash
)
diff --git a/synapse/rest/client/v2_alpha/devices.py b/synapse/rest/client/v2_alpha/devices.py
index 3ba0b0fc07..a1feaf3d54 100644
--- a/synapse/rest/client/v2_alpha/devices.py
+++ b/synapse/rest/client/v2_alpha/devices.py
@@ -39,7 +39,7 @@ class DevicesRestServlet(servlet.RestServlet):
@defer.inlineCallbacks
def on_GET(self, request):
- requester = yield self.auth.get_user_by_req(request)
+ requester = yield self.auth.get_user_by_req(request, allow_guest=True)
devices = yield self.device_handler.get_devices_by_user(
requester.user.to_string()
)
@@ -63,7 +63,7 @@ class DeviceRestServlet(servlet.RestServlet):
@defer.inlineCallbacks
def on_GET(self, request, device_id):
- requester = yield self.auth.get_user_by_req(request)
+ requester = yield self.auth.get_user_by_req(request, allow_guest=True)
device = yield self.device_handler.get_device(
requester.user.to_string(),
device_id,
@@ -99,7 +99,7 @@ class DeviceRestServlet(servlet.RestServlet):
@defer.inlineCallbacks
def on_PUT(self, request, device_id):
- requester = yield self.auth.get_user_by_req(request)
+ requester = yield self.auth.get_user_by_req(request, allow_guest=True)
body = servlet.parse_json_object_from_request(request)
yield self.device_handler.update_device(
diff --git a/synapse/rest/client/v2_alpha/keys.py b/synapse/rest/client/v2_alpha/keys.py
index f185f9a774..08b7c99d57 100644
--- a/synapse/rest/client/v2_alpha/keys.py
+++ b/synapse/rest/client/v2_alpha/keys.py
@@ -65,7 +65,7 @@ class KeyUploadServlet(RestServlet):
@defer.inlineCallbacks
def on_POST(self, request, device_id):
- requester = yield self.auth.get_user_by_req(request)
+ requester = yield self.auth.get_user_by_req(request, allow_guest=True)
user_id = requester.user.to_string()
body = parse_json_object_from_request(request)
@@ -150,7 +150,7 @@ class KeyQueryServlet(RestServlet):
@defer.inlineCallbacks
def on_POST(self, request, user_id, device_id):
- yield self.auth.get_user_by_req(request)
+ yield self.auth.get_user_by_req(request, allow_guest=True)
timeout = parse_integer(request, "timeout", 10 * 1000)
body = parse_json_object_from_request(request)
result = yield self.e2e_keys_handler.query_devices(body, timeout)
@@ -158,7 +158,7 @@ class KeyQueryServlet(RestServlet):
@defer.inlineCallbacks
def on_GET(self, request, user_id, device_id):
- requester = yield self.auth.get_user_by_req(request)
+ requester = yield self.auth.get_user_by_req(request, allow_guest=True)
timeout = parse_integer(request, "timeout", 10 * 1000)
auth_user_id = requester.user.to_string()
user_id = user_id if user_id else auth_user_id
@@ -204,7 +204,7 @@ class OneTimeKeyServlet(RestServlet):
@defer.inlineCallbacks
def on_GET(self, request, user_id, device_id, algorithm):
- yield self.auth.get_user_by_req(request)
+ yield self.auth.get_user_by_req(request, allow_guest=True)
timeout = parse_integer(request, "timeout", 10 * 1000)
result = yield self.e2e_keys_handler.claim_one_time_keys(
{"one_time_keys": {user_id: {device_id: algorithm}}},
@@ -214,7 +214,7 @@ class OneTimeKeyServlet(RestServlet):
@defer.inlineCallbacks
def on_POST(self, request, user_id, device_id, algorithm):
- yield self.auth.get_user_by_req(request)
+ yield self.auth.get_user_by_req(request, allow_guest=True)
timeout = parse_integer(request, "timeout", 10 * 1000)
body = parse_json_object_from_request(request)
result = yield self.e2e_keys_handler.claim_one_time_keys(
diff --git a/synapse/rest/client/v2_alpha/register.py b/synapse/rest/client/v2_alpha/register.py
index 16a45610a5..bc2ec95ddd 100644
--- a/synapse/rest/client/v2_alpha/register.py
+++ b/synapse/rest/client/v2_alpha/register.py
@@ -15,6 +15,7 @@
from twisted.internet import defer
+import synapse
from synapse.api.auth import get_access_token_from_request, has_access_token
from synapse.api.constants import LoginType
from synapse.api.errors import SynapseError, Codes, UnrecognizedRequestError
@@ -100,12 +101,14 @@ class RegisterRestServlet(RestServlet):
def on_POST(self, request):
yield run_on_reactor()
+ body = parse_json_object_from_request(request)
+
kind = "user"
if "kind" in request.args:
kind = request.args["kind"][0]
if kind == "guest":
- ret = yield self._do_guest_registration()
+ ret = yield self._do_guest_registration(body)
defer.returnValue(ret)
return
elif kind != "user":
@@ -113,8 +116,6 @@ class RegisterRestServlet(RestServlet):
"Do not understand membership kind: %s" % (kind,)
)
- body = parse_json_object_from_request(request)
-
# we do basic sanity checks here because the auth layer will store these
# in sessions. Pull out the username/password provided to us.
desired_password = None
@@ -420,13 +421,22 @@ class RegisterRestServlet(RestServlet):
)
@defer.inlineCallbacks
- def _do_guest_registration(self):
+ def _do_guest_registration(self, params):
if not self.hs.config.allow_guest_access:
defer.returnValue((403, "Guest access is disabled"))
user_id, _ = yield self.registration_handler.register(
generate_token=False,
make_guest=True
)
+
+ # we don't allow guests to specify their own device_id, because
+ # we have nowhere to store it.
+ device_id = synapse.api.auth.GUEST_DEVICE_ID
+ initial_display_name = params.get("initial_device_display_name")
+ self.device_handler.check_device_registered(
+ user_id, device_id, initial_display_name
+ )
+
access_token = self.auth_handler.generate_access_token(
user_id, ["guest = true"]
)
@@ -434,6 +444,7 @@ class RegisterRestServlet(RestServlet):
# so long as we don't return a refresh_token here.
defer.returnValue((200, {
"user_id": user_id,
+ "device_id": device_id,
"access_token": access_token,
"home_server": self.hs.hostname,
}))
diff --git a/synapse/rest/client/v2_alpha/sendtodevice.py b/synapse/rest/client/v2_alpha/sendtodevice.py
index ac660669f3..d607bd2970 100644
--- a/synapse/rest/client/v2_alpha/sendtodevice.py
+++ b/synapse/rest/client/v2_alpha/sendtodevice.py
@@ -50,7 +50,7 @@ class SendToDeviceRestServlet(servlet.RestServlet):
@defer.inlineCallbacks
def _put(self, request, message_type, txn_id):
- requester = yield self.auth.get_user_by_req(request)
+ requester = yield self.auth.get_user_by_req(request, allow_guest=True)
content = parse_json_object_from_request(request)
|