async def async_render_GET(self, request: Request): # make sure that there is a valid mapping session, to stop people dictionary- # scanning for accounts session_id = request.getCookie(SESSION_COOKIE_NAME) if not session_id: _return_json({"error": "missing session_id"}, request) return session_id = session_id.decode("ascii", errors="replace") session = get_mapping_session(session_id) if not session: logger.info("Couldn't find session id %s", session_id) _return_json({"error": "unknown session"}, request) return if b"username" not in request.args: _return_json({"error": "missing username"}, request) return localpart = request.args[b"username"][0].decode("utf-8", errors="replace") logger.info("Checking for availability of username %s", localpart) try: user_id = self._module_api.get_qualified_user_id(localpart) registered_id = await self._module_api.check_user_exists(user_id) available = registered_id is None except Exception as e: logger.warning("Error checking for availability of %s: %s %s" % (localpart, type(e), e)) available = False response = {"available": available} _return_json(response, request)
def render_GET(self, request: Request): # $ requestHandler # see https://twistedmatrix.com/documents/21.2.0/api/twisted.web.server.Request.html ensure_tainted( request, # $ tainted request.uri, # $ tainted request.path, # $ tainted request.prepath, # $ tainted request.postpath, # $ tainted # file-like request.content, # $ tainted request.content.read(), # $ MISSING: tainted # Dict[bytes, List[bytes]] (for query args) request.args, # $ tainted request.args[b"key"], # $ tainted request.args[b"key"][0], # $ tainted request.args.get(b"key"), # $ tainted request.args.get(b"key")[0], # $ tainted request.received_cookies, # $ tainted request.received_cookies["key"], # $ tainted request.received_cookies.get("key"), # $ tainted request.getCookie(b"key"), # $ tainted # twisted.web.http_headers.Headers # see https://twistedmatrix.com/documents/21.2.0/api/twisted.web.http_headers.Headers.html request.requestHeaders, # $ tainted request.requestHeaders.getRawHeaders("key"), # $ MISSING: tainted request.requestHeaders.getRawHeaders("key") [0], # $ MISSING: tainted request.requestHeaders.getAllRawHeaders(), # $ MISSING: tainted list(request.requestHeaders.getAllRawHeaders() ), # $ MISSING: tainted request.getHeader("key"), # $ tainted request.getAllHeaders(), # $ tainted request.getAllHeaders()["key"], # $ tainted request.user, # $ tainted request.getUser(), # $ tainted request.password, # $ tainted request.getPassword(), # $ tainted request.host, # $ tainted request.getHost(), # $ tainted request.getRequestHostname(), # $ tainted ) # technically user-controlled, but unlikely to lead to vulnerabilities. ensure_not_tainted(request.method, ) # not tainted at all ensure_not_tainted( # outgoing things request.cookies, request.responseHeaders, )
async def async_render_POST(self, request: Request): # make sure that there is a valid mapping session, to stop people dictionary- # scanning for accounts session_id = request.getCookie(SESSION_COOKIE_NAME) if not session_id: _return_json({"error": "missing session_id"}, request) return session_id = session_id.decode("ascii", errors="replace") session = get_mapping_session(session_id) if not session: logger.info("Couldn't find session id %s", session_id) _return_json({"error": "unknown session"}, request) return if b"username" not in request.args: _return_json({"error": "missing username"}, request) return localpart = request.args[b"username"][0].decode("utf-8", errors="replace") if b"password" not in request.args: _return_json({"error": "missing password"}, request) return password = request.args[b"password"][0].decode("utf-8", errors="replace") if localpart.startswith("@"): uid = localpart else: uid = UserID(localpart, self._module_api._hs.hostname).to_string() success = False try: passwd_response = await self._module_api._auth_handler._check_local_password( uid, password) if passwd_response == uid: success = True except Exception as e: logger.warning("Error checking credentials of %s: %s %s" % (localpart, type(e), e)) response = {"success": success} _return_json(response, request)
async def async_render_POST(self, request: Request): session_id = request.getCookie(SESSION_COOKIE_NAME) if not session_id: _return_html_error(400, "missing session_id", request) return session_id = session_id.decode("ascii", errors="replace") session = get_mapping_session(session_id) if not session: logger.info("Session ID %s not found", session_id) _return_html_error(403, "Unknown session", request) return # we don't clear the session from the dict until the ID is successfully # registered, so the user can go round and have another go if need be. # # this means there's theoretically a race where a single user can register # two accounts. I'm going to assume that's not a dealbreaker. if b"username" not in request.args: _return_html_error(400, "missing username", request) return localpart = request.args[b"username"][0].decode("utf-8", errors="replace") if b"password" not in request.args: logger.info("Registering username %s", localpart) try: registered_user_id = await self._module_api.register_user( localpart=localpart, displayname=localpart) except SynapseError as e: logger.warning("Error during registration: %s", e) _return_html_error(e.code, e.msg, request) return else: password = request.args[b"password"][0].decode("utf-8", errors="replace") if localpart.startswith("@"): registered_user_id = localpart else: registered_user_id = UserID( localpart, self._module_api._hs.hostname).to_string() success = False try: passwd_response = await self._module_api._auth_handler._check_local_password( registered_user_id, password) if passwd_response != registered_user_id: raise LoginError(403, "Invalid password", errcode=Codes.FORBIDDEN) except Exception as e: logger.warning("Error checking credentials of %s: %s %s" % (localpart, type(e), e)) _return_html_error(e.code, e.msg, request) return await self._module_api.record_user_external_id("saml", session.remote_user_id, registered_user_id) del username_mapping_sessions[session_id] # delete the cookie request.addCookie( SESSION_COOKIE_NAME, b"", expires=b"Thu, 01 Jan 1970 00:00:00 GMT", path=b"/", ) await self._module_api.complete_sso_login_async( registered_user_id, request, session.client_redirect_url, )
def render(self, request: server.Request) -> bytes: # Deny by default. request.setResponseCode(401) # Get session cookie value if any. sessionid = request.getCookie(self.cookie) if sessionid is not None: if sessionid in self.sessions: request.setResponseCode(200) self.log.info("Session: Validation succeeded") return b"" else: self.log.info("Session: Invalid session id") # Token is passed as a query parameter in the original URL. origurl = http.urlparse(request.getHeader(self.header)) query = http.parse_qs(origurl.query) args = query.get(self.param, []) if len(args) != 1: self.log.error("Request: Token {param} missing", param=self.param) return b"" try: token = jwt.JWT(key=self.key, jwt=args[0].decode()) except (jwt.JWTExpired, jwt.JWTNotYetValid, jwt.JWTMissingClaim, jwt.JWTInvalidClaimValue, jwt.JWTInvalidClaimFormat, jwt.JWTMissingKeyID, jwt.JWTMissingKey) as error: self.log.error("JWT token: {error}", error=error) return b"" except Exception: self.log.failure("JWT token: Unknown exception") return b"" try: claims = json.loads(token.claims) except json.JSONDecodeError as error: self.log.failure("JWT token: Claims {error}", error=error) return b"" # Collect session parameters from claims. sessparams = claims.get("session", {}) kwargs = { "expires": sessparams.get("expires", None), "domain": sessparams.get("domain", None), "path": sessparams.get("path", None), "secure": sessparams.get("secure", None), "httpOnly": sessparams.get("httpOnly", None), "sameSite": sessparams.get("sameSite", None), } # Use maxAge for session ttl if it is present, convert it into a str # type as required by the addCookie call. if "maxAge" in sessparams: kwargs["max_age"] = str(sessparams["maxAge"]) sessttl = int(sessparams["maxAge"]) else: sessttl = self.sessttl # Generate a new session id and remember it. Also clean it up after # ttl seconds. sessionid = secrets.token_urlsafe(nbytes=16).encode() self.sessions.add(sessionid) reactor.callLater(sessttl, self._session_remove, sessionid) self.log.info("Session: Created, num sessions: {sessions}", sessions=len(self.sessions)) # Set cookie in the browser. request.addCookie(self.cookie, sessionid, **kwargs) request.setResponseCode(200) self.log.info("JWT token: Validation succeeded") return b""