def getUser(db, user_name): if user_name == configuration.base.SYSTEM_USER_NAME: return dbutils.User.makeSystem() try: return dbutils.User.fromName(db, user_name) except dbutils.NoSuchUser: if configuration.base.AUTHENTICATION_MODE == "host": email = getUserEmailAddress(user_name) return dbutils.User.create( db, user_name, user_name, email, email_verified=None) raise
def getUser(db, user_name): if user_name == configuration.base.SYSTEM_USER_NAME: return user_name try: return dbutils.User.fromName(db, user_name) except dbutils.NoSuchUser: if configuration.base.AUTHENTICATION_MODE == "host": user = dbutils.User.create(db, user_name, user_name, getUserEmailAddress(user_name)) db.commit() return user raise
def process_request(environ, start_response): request_start = time.time() critic = api.critic.startSession() db = critic.database user = None try: try: req = request.Request(db, environ, start_response) req.setUser(db) if req.user is None: if configuration.base.AUTHENTICATION_MODE == "host": user = dbutils.User.makeAnonymous() elif configuration.base.SESSION_TYPE == "httpauth": req.requestHTTPAuthentication() return [] elif req.path.startswith("externalauth/"): provider_name = req.path[len("externalauth/"):] raise request.DoExternalAuthentication(provider_name) elif req.path.startswith("oauth/"): provider_name = req.path[len("oauth/"):] if provider_name in auth.PROVIDERS: provider = auth.PROVIDERS[provider_name] if isinstance(provider, auth.OAuthProvider): if finishOAuth(db, req, provider): return [] elif configuration.base.SESSION_TYPE == "cookie": if req.cookies.get("has_sid") == "1": req.ensureSecure() if configuration.base.ALLOW_ANONYMOUS_USER \ or req.path in request.INSECURE_PATHS \ or req.path.startswith("static-resource/"): user = dbutils.User.makeAnonymous() # Don't try to redirect POST requests to the login page. elif req.method == "GET": if configuration.base.AUTHENTICATION_MODE == "critic": raise request.NeedLogin(req) else: raise request.DoExternalAuthentication( configuration.base.AUTHENTICATION_MODE, req.getTargetURL()) if not user: req.setStatus(403) req.start() return [] else: try: user = dbutils.User.fromName(db, req.user) except dbutils.NoSuchUser: if configuration.base.AUTHENTICATION_MODE == "host": email = getUserEmailAddress(req.user) user = dbutils.User.create(db, req.user, req.user, email, email_verified=None) db.commit() else: # This can't really happen. raise if not user.isAnonymous(): critic.setActualUser(api.user.fetch(critic, user_id=user.id)) user.loadPreferences(db) if user.status == 'retired': cursor = db.cursor() cursor.execute("UPDATE users SET status='current' WHERE id=%s", (user.id, )) user = dbutils.User.fromId(db, user.id) db.commit() if not user.getPreference(db, "debug.profiling.databaseQueries"): db.disableProfiling() if not req.path: if user.isAnonymous(): location = "tutorial" else: location = user.getPreference(db, "defaultPage") if req.query: location += "?" + req.query req.setStatus(307) req.addResponseHeader("Location", location) req.start() return [] if req.path == "redirect": target = req.getParameter("target", "/") if req.method == "POST": # Don't use HTTP redirect for POST requests. req.setContentType("text/html") req.start() return [ "<meta http-equiv='refresh' content='0; %s'>" % htmlify(target) ] else: raise request.MovedTemporarily(target) # Require a .git suffix on HTTP(S) repository URLs unless the user- # agent starts with "git/" (as Git's normally does.) # # Our objectives are: # # 1) Not to require Git's user-agent to be its default value, since # the user might have to override it to get through firewalls. # 2) Never to send regular user requests to 'git http-backend' by # mistake. # # This is a compromise. if req.getRequestHeader("User-Agent", "").startswith("git/"): suffix = None else: suffix = ".git" if handleRepositoryPath(db, req, user, suffix): db = None return [] if req.path.startswith("!/"): req.path = req.path[2:] elif configuration.extensions.ENABLED: handled = extensions.role.page.execute(db, req, user) if isinstance(handled, basestring): req.start() return [handled] if req.path.startswith("static-resource/"): return handleStaticResource(req) if req.path.startswith("r/"): req.updateQuery({"id": [req.path[2:]]}) req.path = "showreview" if configuration.extensions.ENABLED: match = RE_EXTENSION_RESOURCE.match(req.path) if match: content_type, resource = extensions.resource.get( req, db, user, match.group(1)) if resource: req.setContentType(content_type) if content_type.startswith("image/"): req.addResponseHeader("Cache-Control", "max-age=3600") req.start() return [resource] else: req.setStatus(404) req.start() return [] if req.path.startswith("download/"): operationfn = download elif req.path == "api" or req.path.startswith("api/"): try: result = jsonapi.handle(critic, req) except jsonapi.Error as error: req.setStatus(error.http_status) result = { "error": { "title": error.title, "message": error.message } } else: req.setStatus(200) accept_header = req.getRequestHeader("Accept") if accept_header == "application/vnd.api+json": default_indent = None else: default_indent = 2 indent = req.getParameter("indent", default_indent, filter=int) if indent == 0: # json.encode(..., indent=0) still gives line-breaks, just # no indentation. This is not so useful, so set indent to # None instead, which disables formatting entirely. indent = None req.setContentType("application/vnd.api+json") req.start() return [json_encode(result, indent=indent)] else: operationfn = OPERATIONS.get(req.path) if operationfn: result = operationfn(req, db, user) if isinstance(result, (OperationResult, OperationError)): req.setContentType("text/json") if isinstance(result, OperationResult): if db.profiling: result.set("__profiling__", formatDBProfiling(db)) result.set("__time__", time.time() - request_start) elif not req.hasContentType(): req.setContentType("text/plain") req.start() if isinstance(result, unicode): return [result.encode("utf8")] else: return [str(result)] override_user = req.getParameter("user", None) while True: pagefn = PAGES.get(req.path) if pagefn: try: if not user.isAnonymous() and override_user: user = dbutils.User.fromName(db, override_user) req.setContentType("text/html") result = pagefn(req, db, user) if db.profiling and not (isinstance(result, str) or isinstance(result, Document)): source = "" for fragment in result: source += fragment result = source if isinstance(result, str) or isinstance( result, Document): req.start() result = str(result) result += ("<!-- total request time: %.2f ms -->" % ((time.time() - request_start) * 1000)) if db.profiling: result += ("<!--\n\n%s\n\n -->" % formatDBProfiling(db)) return [result] else: result = WrappedResult(db, req, user, result) req.start() # Prevent the finally clause below from closing the # connection. WrappedResult does it instead. db = None return result except gitutils.NoSuchRepository as error: raise page.utils.DisplayMessage( title="Invalid URI Parameter!", body=error.message) except gitutils.GitReferenceError as error: if error.ref: raise page.utils.DisplayMessage( title="Specified ref not found", body=("There is no ref named \"%s\" in %s." % (error.ref, error.repository))) elif error.sha1: raise page.utils.DisplayMessage( title="SHA-1 not found", body=error.message) else: raise except dbutils.NoSuchUser as error: raise page.utils.DisplayMessage( title="Invalid URI Parameter!", body=error.message) except dbutils.NoSuchReview as error: raise page.utils.DisplayMessage( title="Invalid URI Parameter!", body=error.message) path = req.path if "/" in path: repository = gitutils.Repository.fromName( db, path.split("/", 1)[0]) if repository: path = path.split("/", 1)[1] else: repository = None def revparsePlain(item): try: return gitutils.getTaggedCommit( repository, repository.revparse(item)) except: raise revparse = revparsePlain if repository is None: review_id = req.getParameter("review", None, filter=int) if review_id: cursor = db.cursor() cursor.execute( """SELECT repository FROM branches JOIN reviews ON (reviews.branch=branches.id) WHERE reviews.id=%s""", (review_id, )) row = cursor.fetchone() if row: repository = gitutils.Repository.fromId(db, row[0]) def revparseWithReview(item): if re.match("^[0-9a-f]+$", item): cursor.execute( """SELECT sha1 FROM commits JOIN changesets ON (changesets.child=commits.id) JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id) WHERE reviewchangesets.review=%s AND commits.sha1 LIKE %s""", (review_id, item + "%")) row = cursor.fetchone() if row: return row[0] else: return revparsePlain(item) revparse = revparseWithReview if repository is None: repository = gitutils.Repository.fromName( db, user.getPreference(db, "defaultRepository")) if gitutils.re_sha1.match(path): if repository and not repository.iscommit(path): repository = None if not repository: try: repository = gitutils.Repository.fromSHA1( db, path) except gitutils.GitReferenceError: repository = None if repository: try: items = filter(None, map(revparse, path.split(".."))) updated_query = {} if len(items) == 1: updated_query["repository"] = [repository.name] updated_query["sha1"] = [items[0]] elif len(items) == 2: updated_query["repository"] = [repository.name] updated_query["from"] = [items[0]] updated_query["to"] = [items[1]] if updated_query: req.updateQuery(updated_query) req.path = "showcommit" continue except gitutils.GitReferenceError: pass break raise page.utils.DisplayMessage(title="Not found!", body="Page not handled: /%s" % path, status=404) except GeneratorExit: raise except page.utils.NotModified: req.setStatus(304) req.start() return [] except request.MovedTemporarily as redirect: req.setStatus(307) req.addResponseHeader("Location", redirect.location) if redirect.no_cache: req.addResponseHeader("Cache-Control", "no-cache") req.start() return [] except request.DoExternalAuthentication as command: command.execute(db, req) return [] except request.MissingWSGIRemoteUser as error: # req object is not initialized yet. start_response("200 OK", [("Content-Type", "text/html")]) return [ """\ <pre>error: Critic was configured with '--auth-mode host' but there was no REMOTE_USER variable in the WSGI environ dict provided by the web server. To fix this you can either reinstall Critic using '--auth-mode critic' (to let Critic handle user authentication automatically), or you can configure user authentication properly in the web server. For apache2, the latter can be done by adding the something like the following to the apache site configuration for Critic: <Location /> AuthType Basic AuthName "Authentication Required" AuthUserFile "/path/to/critic-main.htpasswd.users" Require valid-user </Location> If you need more dynamic http authentication you can instead setup mod_wsgi with a custom WSGIAuthUserScript directive. This will cause the provided credentials to be passed to a Python function called check_password() that you can implement yourself. This way you can validate the user/pass via any existing database or for example an LDAP server. For more information on setting up such authentication in apache2, see: <a href="%(url)s">%(url)s</a></pre>""" % { "url": "http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider" } ] except page.utils.DisplayMessage as message: if user is None: user = dbutils.User.makeAnonymous() document = page.utils.displayMessage(db, req, user, title=message.title, message=message.body, review=message.review, is_html=message.html) req.setContentType("text/html") req.setStatus(message.status) req.start() return [str(document)] except page.utils.DisplayFormattedText as formatted_text: if user is None: user = dbutils.User.makeAnonymous() document = page.utils.displayFormattedText(db, req, user, formatted_text.source) req.setContentType("text/html") req.start() return [str(document)] except Exception: # crash might be psycopg2.ProgrammingError so rollback to avoid # "InternalError: current transaction is aborted" inside handleException() if db and db.closed(): db = None elif db: db.rollback() error_title, error_body = handleException(db, req, user) error_body = reflow("\n\n".join(error_body)) error_message = "\n".join( [error_title, "=" * len(error_title), "", error_body]) assert not req.isStarted() req.setStatus(500) req.setContentType("text/plain") req.start() return [error_message] finally: if db: db.close()
def process_request(environ, start_response): request_start = time.time() db = dbutils.Database() user = None try: try: req = request.Request(db, environ, start_response) req.setUser(db) if req.user is None: if configuration.base.AUTHENTICATION_MODE == "host": user = dbutils.User.makeAnonymous() elif configuration.base.SESSION_TYPE == "httpauth": req.requestHTTPAuthentication() return [] elif req.path.startswith("externalauth/"): provider_name = req.path[len("externalauth/"):] raise request.DoExternalAuthentication(provider_name) elif req.path.startswith("oauth/"): provider_name = req.path[len("oauth/"):] if provider_name in auth.PROVIDERS: provider = auth.PROVIDERS[provider_name] if isinstance(provider, auth.OAuthProvider): if finishOAuth(db, req, provider): return [] elif configuration.base.SESSION_TYPE == "cookie": if req.cookies.get("has_sid") == "1": req.ensureSecure() if configuration.base.ALLOW_ANONYMOUS_USER \ or req.path in request.INSECURE_PATHS \ or req.path.startswith("static-resource/"): user = dbutils.User.makeAnonymous() # Don't try to redirect POST requests to the login page. elif req.method == "GET": if configuration.base.AUTHENTICATION_MODE == "critic": raise request.NeedLogin(req) else: raise request.DoExternalAuthentication( configuration.base.AUTHENTICATION_MODE, req.getTargetURL()) if not user: req.setStatus(403) req.start() return [] else: try: user = dbutils.User.fromName(db, req.user) except dbutils.NoSuchUser: if configuration.base.AUTHENTICATION_MODE == "host": email = getUserEmailAddress(req.user) user = dbutils.User.create( db, req.user, req.user, email, email_verified=None) db.commit() else: # This can't really happen. raise user.loadPreferences(db) if user.status == 'retired': cursor = db.cursor() cursor.execute("UPDATE users SET status='current' WHERE id=%s", (user.id,)) user = dbutils.User.fromId(db, user.id) db.commit() if not user.getPreference(db, "debug.profiling.databaseQueries"): db.disableProfiling() if not req.path: if user.isAnonymous(): location = "tutorial" else: location = user.getPreference(db, "defaultPage") if req.query: location += "?" + req.query req.setStatus(307) req.addResponseHeader("Location", location) req.start() return [] if req.path == "redirect": target = req.getParameter("target", "/") if req.method == "POST": # Don't use HTTP redirect for POST requests. req.setContentType("text/html") req.start() return ["<meta http-equiv='refresh' content='0; %s'>" % htmlify(target)] else: raise request.MovedTemporarily(target) # Require a .git suffix on HTTP(S) repository URLs unless the user- # agent starts with "git/" (as Git's normally does.) # # Our objectives are: # # 1) Not to require Git's user-agent to be its default value, since # the user might have to override it to get through firewalls. # 2) Never to send regular user requests to 'git http-backend' by # mistake. # # This is a compromise. if req.getRequestHeader("User-Agent", "").startswith("git/"): suffix = None else: suffix = ".git" if handleRepositoryPath(db, req, user, suffix): db = None return [] if req.path.startswith("!/"): req.path = req.path[2:] elif configuration.extensions.ENABLED: handled = extensions.role.page.execute(db, req, user) if isinstance(handled, basestring): req.start() return [handled] if req.path.startswith("static-resource/"): return handleStaticResource(req) if req.path.startswith("r/"): req.query = "id=" + req.path[2:] + ("&" + req.query if req.query else "") req.path = "showreview" if configuration.extensions.ENABLED: match = RE_EXTENSION_RESOURCE.match(req.path) if match: content_type, resource = extensions.resource.get(req, db, user, match.group(1)) if resource: req.setContentType(content_type) if content_type.startswith("image/"): req.addResponseHeader("Cache-Control", "max-age=3600") req.start() return [resource] else: req.setStatus(404) req.start() return [] if req.path.startswith("download/"): operationfn = download else: operationfn = OPERATIONS.get(req.path) if operationfn: result = operationfn(req, db, user) if isinstance(result, (OperationResult, OperationError)): req.setContentType("text/json") if isinstance(result, OperationResult): if db.profiling: result.set("__profiling__", formatDBProfiling(db)) result.set("__time__", time.time() - request_start) elif not req.hasContentType(): req.setContentType("text/plain") req.start() if isinstance(result, unicode): return [result.encode("utf8")] else: return [str(result)] override_user = req.getParameter("user", None) while True: pagefn = PAGES.get(req.path) if pagefn: try: if not user.isAnonymous() and override_user: user = dbutils.User.fromName(db, override_user) req.setContentType("text/html") result = pagefn(req, db, user) if db.profiling and not (isinstance(result, str) or isinstance(result, Document)): source = "" for fragment in result: source += fragment result = source if isinstance(result, str) or isinstance(result, Document): req.start() result = str(result) result += ("<!-- total request time: %.2f ms -->" % ((time.time() - request_start) * 1000)) if db.profiling: result += ("<!--\n\n%s\n\n -->" % formatDBProfiling(db)) return [result] else: result = WrappedResult(db, req, user, result) req.start() # Prevent the finally clause below from closing the # connection. WrappedResult does it instead. db = None return result except gitutils.NoSuchRepository as error: raise page.utils.DisplayMessage( title="Invalid URI Parameter!", body=error.message) except gitutils.GitReferenceError as error: if error.ref: raise page.utils.DisplayMessage( title="Specified ref not found", body=("There is no ref named \"%s\" in %s." % (error.ref, error.repository))) elif error.sha1: raise page.utils.DisplayMessage( title="SHA-1 not found", body=error.message) else: raise except dbutils.NoSuchUser as error: raise page.utils.DisplayMessage( title="Invalid URI Parameter!", body=error.message) except dbutils.NoSuchReview as error: raise page.utils.DisplayMessage( title="Invalid URI Parameter!", body=error.message) path = req.path if "/" in path: repository = gitutils.Repository.fromName(db, path.split("/", 1)[0]) if repository: path = path.split("/", 1)[1] else: repository = None def revparsePlain(item): try: return gitutils.getTaggedCommit(repository, repository.revparse(item)) except: raise revparse = revparsePlain if repository is None: review_id = req.getParameter("review", None, filter=int) if review_id: cursor = db.cursor() cursor.execute("""SELECT repository FROM branches JOIN reviews ON (reviews.branch=branches.id) WHERE reviews.id=%s""", (review_id,)) row = cursor.fetchone() if row: repository = gitutils.Repository.fromId(db, row[0]) def revparseWithReview(item): if re.match("^[0-9a-f]+$", item): cursor.execute("""SELECT sha1 FROM commits JOIN changesets ON (changesets.child=commits.id) JOIN reviewchangesets ON (reviewchangesets.changeset=changesets.id) WHERE reviewchangesets.review=%s AND commits.sha1 LIKE %s""", (review_id, item + "%")) row = cursor.fetchone() if row: return row[0] else: return revparsePlain(item) revparse = revparseWithReview if repository is None: repository = gitutils.Repository.fromName( db, user.getPreference(db, "defaultRepository")) if gitutils.re_sha1.match(path): if repository and not repository.iscommit(path): repository = None if not repository: try: repository = gitutils.Repository.fromSHA1(db, path) except gitutils.GitReferenceError: repository = None if repository: try: items = filter(None, map(revparse, path.split(".."))) query = None if len(items) == 1: query = ("repository=%d&sha1=%s" % (repository.id, items[0])) elif len(items) == 2: query = ("repository=%d&from=%s&to=%s" % (repository.id, items[0], items[1])) if query: if req.query: query += "&" + req.query req.query = query req.path = "showcommit" continue except gitutils.GitReferenceError: pass break req.setStatus(404) raise page.utils.DisplayMessage( title="Not found!", body="Page not handled: /%s" % path) except GeneratorExit: raise except page.utils.NotModified: req.setStatus(304) req.start() return [] except request.MovedTemporarily as redirect: req.setStatus(307) req.addResponseHeader("Location", redirect.location) if redirect.no_cache: req.addResponseHeader("Cache-Control", "no-cache") req.start() return [] except request.DoExternalAuthentication as command: command.execute(db, req) return [] except request.MissingWSGIRemoteUser as error: # req object is not initialized yet. start_response("200 OK", [("Content-Type", "text/html")]) return ["""\ <pre>error: Critic was configured with '--auth-mode host' but there was no REMOTE_USER variable in the WSGI environ dict provided by the web server. To fix this you can either reinstall Critic using '--auth-mode critic' (to let Critic handle user authentication automatically), or you can configure user authentication properly in the web server. For apache2, the latter can be done by adding the something like the following to the apache site configuration for Critic: <Location /> AuthType Basic AuthName "Authentication Required" AuthUserFile "/path/to/critic-main.htpasswd.users" Require valid-user </Location> If you need more dynamic http authentication you can instead setup mod_wsgi with a custom WSGIAuthUserScript directive. This will cause the provided credentials to be passed to a Python function called check_password() that you can implement yourself. This way you can validate the user/pass via any existing database or for example an LDAP server. For more information on setting up such authentication in apache2, see: <a href="%(url)s">%(url)s</a></pre>""" % { "url": "http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider" }] except page.utils.DisplayMessage as message: if user is None: user = dbutils.User.makeAnonymous() document = page.utils.displayMessage( db, req, user, title=message.title, message=message.body, review=message.review, is_html=message.html) req.setContentType("text/html") req.start() return [str(document)] except Exception: # crash might be psycopg2.ProgrammingError so rollback to avoid # "InternalError: current transaction is aborted" inside handleException() if db and db.closed(): db = None elif db: db.rollback() error_title, error_body = handleException(db, req, user) error_body = reflow("\n\n".join(error_body)) error_message = "\n".join([error_title, "=" * len(error_title), "", error_body]) assert not req.isStarted() req.setStatus(500) req.setContentType("text/plain") req.start() return [error_message] finally: if db: db.close()
def checkSession(db, req): """Check if the request is part of a session and if so set req.user Raises an request.HTTPResponse exception if immediate action is required, otherwise sets req.user to non-None (but possibly to the anonymous user) and returns.""" # Step 1: If the host web server is supposed to authenticate users, use the # $REMOTE_USER environment variable. if configuration.base.AUTHENTICATION_MODE == "host": # Strip white-space, since Apache is known to do this internally when # authenticating, but then passing on the original unstripped string to # us on success. username = req.getEnvironment().get("REMOTE_USER", "").strip() if not username: # No REMOTE_USER variable. If we support anonymous users, this is # fine, otherwise it indicates a configuration error. if configuration.base.ALLOW_ANONYMOUS_USER: db.setUser(dbutils.User.makeAnonymous()) return raise request.MissingWSGIRemoteUser() # We have a username. Fetch the (or create a) matching user record. try: db.setUser(dbutils.User.fromName(db, username)) except dbutils.NoSuchUser: email = getUserEmailAddress(username) db.setUser(dbutils.User.create( db, username, username, email, email_verified=None)) return # Step 2: If cookie based sessions are used, check if there is a valid # session cookie. if configuration.base.SESSION_TYPE == "cookie": sid = req.cookies.get("sid") if sid: cursor = db.cursor() cursor.execute( """SELECT uid, labels, EXTRACT('epoch' FROM NOW() - atime) AS age FROM usersessions WHERE key=%s""", (sid,)) row = cursor.fetchone() if row: user_id, labels, session_age = row if configuration.base.SESSION_MAX_AGE == 0 \ or session_age < configuration.base.SESSION_MAX_AGE: # This is a valid session cookie. user = dbutils.User.fromId(db, user_id) if labels is None: labels = auth.DATABASE.getAuthenticationLabels(user) else: labels = labels.split("|") if labels else () db.setUser(user, labels) return # The session cookie is too old. Delete it from the database. with db.updating_cursor("usersessions") as cursor: cursor.execute("""DELETE FROM usersessions WHERE key=%s""", (sid,)) # The session cookie is not valid. Delete it from the browser. req.deleteCookie("sid") # Also delete the has_sid cookie, if there is one. req.deleteCookie("has_sid") # Since the session seems to have expired, offer the user to sign in # again by redirecting to the login page. Signing in is optional # though, meaning the login page will have a "Continue anonymously" # link (if anonymous access is allowed.) # # Exception: Don't do this if /login is being requested. if req.allowRedirect(307) and req.path != "login": raise request.NeedLogin(req, optional=True) elif req.cookies.get("has_sid") == "1": # The request had no session cookie, but had the has_sid cookie that # indicates the browser ought to have a sesssion cookie. Typically, # this means a signed in user accesses a mixed HTTP/HTTPS system # over HTTP. If so, redirect the user to HTTPS. req.ensureSecure() # The above call would have raised if a redirect was meaningful. If # it didn't, the has_sid cookie is bogus, so delete it. req.deleteCookie("has_sid") elif req.cookies.get("has_sid") == "0": # This indicates that the user just signed out. If anonymous access # is not allowed, we'll redirect the user to the login page again, # which is sort of a bit unhelpful. # # Worse yet; if use of an external authentication provider is # enforced, the login page will redirect there, which might sign the # user back in, non-interactively. In that case, signing out would # be impossible. # # So, instead, detect the sign-out and return a simple "you have # signed out" page in this case. # Delete the cookie. This means that on reload, the user is # redirected to the login page again. (This is to prevent the user # from getting stuck on this "you have signed out" page.) req.deleteCookie("has_sid") # Do the redirect if anonymous access isn't allowed. Also don't do # it on the actual login page. if not configuration.base.ALLOW_ANONYMOUS_USER \ and req.path != "login": raise request.DisplayMessage( title="You have signed out", body="To use this system, you will need to sign in again.") # Step 3(a): Check if there's a valid HTTP Authorization header (even if # cookie based sessions are typically used.) If there is such a # header, we assume HTTP authentication was meant to be used, and # respond with a 401 Unauthorized response if authentication # using the header fails. authorization_header = req.getRequestHeader("Authorization") if authorization_header: import base64 try: authtype, base64_credentials = authorization_header.split(None, 1) except ValueError: authtype = "invalid" if authtype.lower() != "basic": raise request.RequestHTTPAuthentication() try: credentials = base64.b64decode(base64_credentials) except (ValueError, TypeError) as error: raise request.RequestHTTPAuthentication() username, _, password = credentials.partition(":") username = username.strip() if username and password: try: auth.DATABASE.performHTTPAuthentication(db, username, password) req.session_type = "httpauth" return except auth.AuthenticationFailed: pass raise request.RequestHTTPAuthentication() # Step 3(b): If the request has a "use_httpauth" cookie, request/require # HTTP authentication. This is a just a convenience feature for # clients using HTTP stacks that only send credentials in # response to server challenges. (If cookie sessions are used, # no such challenge would normally be returned, we'd rather # redirect to the login page.) if req.cookies.get("use_httpauth"): raise request.RequestHTTPAuthentication() # Also do this for requests with a "httpauth=yes" query parameter. if req.getParameter("httpauth", "no") == "yes": raise request.RequestHTTPAuthentication() # Step 4: If anonymous access is supported or if it should be allowed as an # exception for the accessed path, leave the session anonymous. if configuration.base.ALLOW_ANONYMOUS_USER or isInsecurePath(req): db.setUser(dbutils.User.makeAnonymous()) req.session_type = None return # Step 5: If HTTP authentication is required (i.e. no session cookies) then # request that now. if configuration.base.SESSION_TYPE == "httpauth": raise request.RequestHTTPAuthentication() # Step 6: Cookie based sessions are enabled, and not anonymous access. If # this is a POST or PUT request, respond with 403 Forbidden, and # otherwise redirect to the login page. if not req.allowRedirect(307): raise request.Forbidden("Valid user session required") raise request.NeedLogin(req, optional=req.cookies.has_key("has_sid"))
def main(environ, start_response): request_start = time.time() db = dbutils.Database() user = None try: try: req = request.Request(db, environ, start_response) if req.user is None: if configuration.base.AUTHENTICATION_MODE == "critic": if configuration.base.SESSION_TYPE == "httpauth": req.setStatus(401) req.addResponseHeader("WWW-Authenticate", "Basic realm=\"Critic\"") req.start() return elif configuration.base.ALLOW_ANONYMOUS_USER or req.path in ( "login", "validatelogin"): user = dbutils.User.makeAnonymous() elif req.method == "GET": raise page.utils.NeedLogin, req else: # Don't try to redirect POST requests to the login page. req.setStatus(403) req.start() return else: try: user = dbutils.User.fromName(db, req.user) except dbutils.NoSuchUser: cursor = db.cursor() cursor.execute( """INSERT INTO users (name, email, fullname) VALUES (%s, %s, %s) RETURNING id""", (req.user, getUserEmailAddress(req.user), req.user)) user = dbutils.User.fromId(db, cursor.fetchone()[0]) db.commit() user.loadPreferences(db) if user.status == 'retired': cursor = db.cursor() cursor.execute("UPDATE users SET status='current' WHERE id=%s", (user.id, )) user = dbutils.User.fromId(db, user.id) db.commit() if not user.getPreference(db, "debug.profiling.databaseQueries"): db.disableProfiling() if not req.path: if user.isAnonymous(): location = "tutorial" else: location = user.getPreference(db, "defaultPage") if req.query: location += "?" + req.query req.setStatus(307) req.addResponseHeader("Location", location) req.start() return if req.path == "redirect": target = req.getParameter("target", "/") if req.method == "POST": # Don't use HTTP redirect for POST requests. req.setContentType("text/html") req.start() yield "<meta http-equiv='refresh' content='0; %s'>" % htmlify( target) return else: raise page.utils.MovedTemporarily, target if req.path.startswith("!/"): req.path = req.path[2:] elif configuration.extensions.ENABLED: handled = extensions.executePage(db, req, user) if handled: req.start() yield handled return if req.path.startswith("r/"): req.query = "id=" + req.path[2:] + ("&" + req.query if req.query else "") req.path = "showreview" if configuration.extensions.ENABLED: match = RE_EXTENSION_RESOURCE.match(req.path) if match: content_type, resource = extensions.getExtensionResource( req, db, user, match.group(1)) if resource: req.setContentType(content_type) req.start() yield resource return else: req.setStatus(404) req.start() return if req.path.startswith("download/"): operation = download else: operation = operations.get(req.path) if operation: req.setContentType("text/plain") try: result = operation(req, db, user) except OperationError, error: result = error except page.utils.DisplayMessage, message: result = "error:" + message.title if message.body: result += " " + message.body except Exception, exception: result = "error:\n" + "".join( traceback.format_exception(*sys.exc_info()))
def checkSession(db, req): """Check if the request is part of a session and if so set req.user Raises an request.HTTPResponse exception if immediate action is required, otherwise sets req.user to non-None (but possibly to the anonymous user) and returns.""" # Step 1: If the host web server is supposed to authenticate users, use the # $REMOTE_USER environment variable. if configuration.base.AUTHENTICATION_MODE == "host": # Strip white-space, since Apache is known to do this internally when # authenticating, but then passing on the original unstripped string to # us on success. username = req.getEnvironment().get("REMOTE_USER", "").strip() if not username: # No REMOTE_USER variable. If we support anonymous users, this is # fine, otherwise it indicates a configuration error. if configuration.base.ALLOW_ANONYMOUS_USER: db.setUser(dbutils.User.makeAnonymous()) return raise request.MissingWSGIRemoteUser() # We have a username. Fetch the (or create a) matching user record. try: db.setUser(dbutils.User.fromName(db, username)) except dbutils.NoSuchUser: email = getUserEmailAddress(username) db.setUser( dbutils.User.create(db, username, username, email, email_verified=None)) return # Step 2: If cookie based sessions are used, check if there is a valid # session cookie. if configuration.base.SESSION_TYPE == "cookie": sid = req.cookies.get("sid") if sid: cursor = db.cursor() cursor.execute( """SELECT uid, labels, EXTRACT('epoch' FROM NOW() - atime) AS age FROM usersessions WHERE key=%s""", (sid, )) row = cursor.fetchone() if row: user_id, labels, session_age = row if configuration.base.SESSION_MAX_AGE == 0 \ or session_age < configuration.base.SESSION_MAX_AGE: # This is a valid session cookie. user = dbutils.User.fromId(db, user_id) if labels is None: labels = auth.DATABASE.getAuthenticationLabels(user) else: labels = labels.split("|") if labels else () db.setUser(user, labels) return # The session cookie is too old. Delete it from the database. with db.updating_cursor("usersessions") as cursor: cursor.execute( """DELETE FROM usersessions WHERE key=%s""", (sid, )) # The session cookie is not valid. Delete it from the browser. req.deleteCookie("sid") # Also delete the has_sid cookie, if there is one. req.deleteCookie("has_sid") # Since the session seems to have expired, offer the user to sign in # again by redirecting to the login page. Signing in is optional # though, meaning the login page will have a "Continue anonymously" # link (if anonymous access is allowed.) # # Exception: Don't do this if /login is being requested. if req.allowRedirect(307) and req.path != "login": raise request.NeedLogin(req, optional=True) elif req.cookies.get("has_sid") == "1": # The request had no session cookie, but had the has_sid cookie that # indicates the browser ought to have a sesssion cookie. Typically, # this means a signed in user accesses a mixed HTTP/HTTPS system # over HTTP. If so, redirect the user to HTTPS. req.ensureSecure() # The above call would have raised if a redirect was meaningful. If # it didn't, the has_sid cookie is bogus, so delete it. req.deleteCookie("has_sid") elif req.cookies.get("has_sid") == "0": # This indicates that the user just signed out. If anonymous access # is not allowed, we'll redirect the user to the login page again, # which is sort of a bit unhelpful. # # Worse yet; if use of an external authentication provider is # enforced, the login page will redirect there, which might sign the # user back in, non-interactively. In that case, signing out would # be impossible. # # So, instead, detect the sign-out and return a simple "you have # signed out" page in this case. # Delete the cookie. This means that on reload, the user is # redirected to the login page again. (This is to prevent the user # from getting stuck on this "you have signed out" page.) req.deleteCookie("has_sid") # Do the redirect if anonymous access isn't allowed. Also don't do # it on the actual login page. if not configuration.base.ALLOW_ANONYMOUS_USER \ and req.path != "login": raise request.DisplayMessage( title="You have signed out", body="To use this system, you will need to sign in again.") # Step 3(a): Check if there's a valid HTTP Authorization header (even if # cookie based sessions are typically used.) If there is such a # header, we assume HTTP authentication was meant to be used, and # respond with a 401 Unauthorized response if authentication # using the header fails. authorization_header = req.getRequestHeader("Authorization") if authorization_header: import base64 try: authtype, base64_credentials = authorization_header.split(None, 1) except ValueError: authtype = "invalid" if authtype.lower() != "basic": raise request.RequestHTTPAuthentication() try: credentials = base64.b64decode(base64_credentials) except (ValueError, TypeError) as error: raise request.RequestHTTPAuthentication() username, _, password = credentials.partition(":") username = username.strip() if username and password: try: auth.DATABASE.performHTTPAuthentication(db, username, password) req.session_type = "httpauth" return except auth.AuthenticationFailed: pass raise request.RequestHTTPAuthentication() # Step 3(b): If the request has a "use_httpauth" cookie, request/require # HTTP authentication. This is a just a convenience feature for # clients using HTTP stacks that only send credentials in # response to server challenges. (If cookie sessions are used, # no such challenge would normally be returned, we'd rather # redirect to the login page.) if req.cookies.get("use_httpauth"): raise request.RequestHTTPAuthentication() # Also do this for requests with a "httpauth=yes" query parameter. if req.getParameter("httpauth", "no") == "yes": raise request.RequestHTTPAuthentication() # Step 4: If anonymous access is supported or if it should be allowed as an # exception for the accessed path, leave the session anonymous. if configuration.base.ALLOW_ANONYMOUS_USER or isInsecurePath(req): db.setUser(dbutils.User.makeAnonymous()) req.session_type = None return # Step 5: If HTTP authentication is required (i.e. no session cookies) then # request that now. if configuration.base.SESSION_TYPE == "httpauth": raise request.RequestHTTPAuthentication() # Step 6: Cookie based sessions are enabled, and not anonymous access. If # this is a POST or PUT request, respond with 403 Forbidden, and # otherwise redirect to the login page. if not req.allowRedirect(307): raise request.Forbidden("Valid user session required") raise request.NeedLogin(req, optional=req.cookies.has_key("has_sid"))
def main(environ, start_response): request_start = time.time() db = dbutils.Database() user = None try: try: req = request.Request(db, environ, start_response) if req.user is None: if configuration.base.AUTHENTICATION_MODE == "critic": if configuration.base.SESSION_TYPE == "httpauth": req.setStatus(401) req.addResponseHeader("WWW-Authenticate", "Basic realm=\"Critic\"") req.start() return elif configuration.base.ALLOW_ANONYMOUS_USER or req.path in ("login", "validatelogin"): user = dbutils.User.makeAnonymous() elif req.method == "GET": raise page.utils.NeedLogin, req else: # Don't try to redirect POST requests to the login page. req.setStatus(403) req.start() return else: try: user = dbutils.User.fromName(db, req.user) except dbutils.NoSuchUser: cursor = db.cursor() cursor.execute("""INSERT INTO users (name, email, fullname) VALUES (%s, %s, %s) RETURNING id""", (req.user, getUserEmailAddress(req.user), req.user)) user = dbutils.User.fromId(db, cursor.fetchone()[0]) db.commit() user.loadPreferences(db) if user.status == 'retired': cursor = db.cursor() cursor.execute("UPDATE users SET status='current' WHERE id=%s", (user.id,)) user = dbutils.User.fromId(db, user.id) db.commit() if not user.getPreference(db, "debug.profiling.databaseQueries"): db.disableProfiling() if not req.path: if user.isAnonymous(): location = "tutorial" else: location = user.getPreference(db, "defaultPage") if req.query: location += "?" + req.query req.setStatus(307) req.addResponseHeader("Location", location) req.start() return if req.path == "redirect": target = req.getParameter("target", "/") if req.method == "POST": # Don't use HTTP redirect for POST requests. req.setContentType("text/html") req.start() yield "<meta http-equiv='refresh' content='0; %s'>" % htmlify(target) return else: raise page.utils.MovedTemporarily, target if req.path.startswith("!/"): req.path = req.path[2:] elif configuration.extensions.ENABLED: handled = extensions.executePage(db, req, user) if handled: req.start() yield handled return if req.path.startswith("r/"): req.query = "id=" + req.path[2:] + ("&" + req.query if req.query else "") req.path = "showreview" if configuration.extensions.ENABLED: match = RE_EXTENSION_RESOURCE.match(req.path) if match: content_type, resource = extensions.getExtensionResource(req, db, user, match.group(1)) if resource: req.setContentType(content_type) req.start() yield resource return else: req.setStatus(404) req.start() return if req.path.startswith("download/"): operation = download else: operation = operations.get(req.path) if operation: req.setContentType("text/plain") try: result = operation(req, db, user) except OperationError, error: result = error except page.utils.DisplayMessage, message: result = "error:" + message.title if message.body: result += " " + message.body except Exception, exception: result = "error:\n" + "".join(traceback.format_exception(*sys.exc_info())) if isinstance(result, (OperationResult, OperationError)): req.setContentType("text/json") if isinstance(result, OperationResult): if db.profiling: result.set("__profiling__", formatDBProfiling(db)) result.addResponseHeaders(req) else: req.setContentType("text/plain") req.start() if isinstance(result, unicode): yield result.encode("utf8") else: yield str(result) return
def main(environ, start_response): request_start = time.time() db = dbutils.Database() user = None try: try: req = request.Request(db, environ, start_response) if configuration.base.AUTHENTICATION_MODE == "critic" and req.user is None: req.setStatus(401) req.addResponseHeader("WWW-Authenticate", "Basic realm=\"Critic\"") req.start() return try: user = dbutils.User.fromName(db, req.user) except dbutils.NoSuchUser: cursor.execute("""INSERT INTO users (name, email, fullname) VALUES (%s, %s, %s) RETURNING id""", (req.user, getUserEmailAddress(req.user), req.user)) user = dbutils.User.fromId(db, cursor.fetchone()[0]) db.commit() user.loadPreferences(db) if user.status == 'retired': cursor = db.cursor() cursor.execute("UPDATE users SET status='current' WHERE id=%s", (user.id,)) user = dbutils.User.fromId(db, user.id) db.commit() if not user.getPreference(db, "debug.profiling.databaseQueries"): db.disableProfiling() if not req.path: location = user.getPreference(db, "defaultPage") if req.query: location += "?" + req.query req.setStatus(307) req.addResponseHeader("Location", location) req.start() return if req.path.startswith("!/"): req.path = req.path[2:] elif configuration.extensions.ENABLED: handled = extensions.executePage(db, req, user) if handled: req.start() yield handled return if req.path.startswith("r/"): req.query = "id=" + req.path[2:] + ("&" + req.query if req.query else "") req.path = "showreview" if configuration.extensions.ENABLED: match = RE_EXTENSION_RESOURCE.match(req.path) if match: content_type, resource = extensions.getExtensionResource(req, db, user, match.group(1)) if resource: req.setContentType(content_type) req.start() yield resource return else: req.setStatus(404) req.start() return if req.path.startswith("download/"): operation = download else: operation = operations.get(req.path) if operation: req.setContentType("text/plain") try: result = operation(req, db, user) except OperationError, error: result = error except page.utils.DisplayMessage, message: result = "error:" + message.title if message.body: result += " " + message.body except Exception, exception: result = "error:\n" + "".join(traceback.format_exception(*sys.exc_info())) if isinstance(result, (OperationResult, OperationError)): req.setContentType("text/json") if db.profiling and isinstance(result, OperationResult): result.set("__profiling__", formatDBProfiling(db)) else: req.setContentType("text/plain") req.start() if isinstance(result, unicode): yield result.encode("utf8") else: yield str(result) return