diff options
author | Steven Hammerton <steven.hammerton@openmarket.com> | 2015-11-05 14:01:12 +0000 |
---|---|---|
committer | Steven Hammerton <steven.hammerton@openmarket.com> | 2015-11-05 14:06:48 +0000 |
commit | 414a4a71b4421a376feb4e3e4ec5ae997fa289b2 (patch) | |
tree | a5b22aa2c878b465d8c30a506701f7be0eec3045 /synapse/handlers/auth.py | |
parent | Add service URL to CAS config (diff) | |
download | synapse-414a4a71b4421a376feb4e3e4ec5ae997fa289b2.tar.xz |
Allow hs to do CAS login completely and issue the client with a login token that can be redeemed for the usual successful login response
Diffstat (limited to 'synapse/handlers/auth.py')
-rw-r--r-- | synapse/handlers/auth.py | 76 |
1 files changed, 73 insertions, 3 deletions
diff --git a/synapse/handlers/auth.py b/synapse/handlers/auth.py index 1b11dbdffd..7a85883aa6 100644 --- a/synapse/handlers/auth.py +++ b/synapse/handlers/auth.py @@ -18,7 +18,7 @@ from twisted.internet import defer from ._base import BaseHandler from synapse.api.constants import LoginType from synapse.types import UserID -from synapse.api.errors import LoginError, Codes +from synapse.api.errors import AuthError, LoginError, Codes from synapse.util.async import run_on_reactor from twisted.web.client import PartialDownloadError @@ -46,6 +46,13 @@ class AuthHandler(BaseHandler): } self.bcrypt_rounds = hs.config.bcrypt_rounds self.sessions = {} + self.INVALID_TOKEN_HTTP_STATUS = 401 + self._KNOWN_CAVEAT_PREFIXES = set([ + "gen = ", + "type = ", + "time < ", + "user_id = ", + ]) @defer.inlineCallbacks def check_auth(self, flows, clientdict, clientip): @@ -297,10 +304,11 @@ class AuthHandler(BaseHandler): defer.returnValue((user_id, access_token, refresh_token)) @defer.inlineCallbacks - def login_with_cas_user_id(self, user_id): + def login_with_user_id(self, user_id): """ Authenticates the user with the given user ID, - intended to have been captured from a CAS response + it is intended that the authentication of the user has + already been verified by other mechanism (e.g. CAS) Args: user_id (str): User ID @@ -393,6 +401,17 @@ class AuthHandler(BaseHandler): )) return m.serialize() + def generate_short_term_login_token(self, user_id): + macaroon = self._generate_base_macaroon(user_id) + macaroon.add_first_party_caveat("type = login") + now = self.hs.get_clock().time_msec() + expiry = now + (2 * 60 * 1000) + macaroon.add_first_party_caveat("time < %d" % (expiry,)) + return macaroon.serialize() + + def validate_short_term_login_token_and_get_user_id(self, login_token): + return self._validate_macaroon_and_get_user_id(login_token, "login", True) + def _generate_base_macaroon(self, user_id): macaroon = pymacaroons.Macaroon( location=self.hs.config.server_name, @@ -402,6 +421,57 @@ class AuthHandler(BaseHandler): macaroon.add_first_party_caveat("user_id = %s" % (user_id,)) return macaroon + def _validate_macaroon_and_get_user_id(self, macaroon_str, + macaroon_type, validate_expiry): + try: + macaroon = pymacaroons.Macaroon.deserialize(macaroon_str) + user_id = self._get_user_from_macaroon(macaroon) + v = pymacaroons.Verifier() + v.satisfy_exact("gen = 1") + v.satisfy_exact("type = " + macaroon_type) + v.satisfy_exact("user_id = " + user_id) + if validate_expiry: + v.satisfy_general(self._verify_expiry) + + v.verify(macaroon, self.hs.config.macaroon_secret_key) + + v = pymacaroons.Verifier() + v.satisfy_general(self._verify_recognizes_caveats) + v.verify(macaroon, self.hs.config.macaroon_secret_key) + return user_id + except (pymacaroons.exceptions.MacaroonException, TypeError, ValueError): + raise AuthError( + self.INVALID_TOKEN_HTTP_STATUS, "Invalid token", + errcode=Codes.UNKNOWN_TOKEN + ) + + def _get_user_from_macaroon(self, macaroon): + user_prefix = "user_id = " + for caveat in macaroon.caveats: + if caveat.caveat_id.startswith(user_prefix): + return caveat.caveat_id[len(user_prefix):] + raise AuthError( + self.INVALID_TOKEN_HTTP_STATUS, "No user_id found in token", + errcode=Codes.UNKNOWN_TOKEN + ) + + def _verify_expiry(self, caveat): + prefix = "time < " + if not caveat.startswith(prefix): + return False + expiry = int(caveat[len(prefix):]) + now = self.hs.get_clock().time_msec() + return now < expiry + + def _verify_recognizes_caveats(self, caveat): + first_space = caveat.find(" ") + if first_space < 0: + return False + second_space = caveat.find(" ", first_space + 1) + if second_space < 0: + return False + return caveat[:second_space + 1] in self._KNOWN_CAVEAT_PREFIXES + @defer.inlineCallbacks def set_password(self, user_id, newpassword): password_hash = self.hash(newpassword) |