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() critic = api.critic.startSession(for_user=True) db = critic.database user = None try: try: req = request.Request(db, environ, start_response) # Handle static resources very early. We don't bother with checking # for an authenticated user; static resources aren't sensitive, and # are referenced from special-case resources like the login page and # error messages that, that we want to display even if something # went wrong with the authentication. if req.path.startswith("static-resource/"): return handleStaticResource(req) if req.path.startswith("externalauth/"): provider_name = req.path[len("externalauth/"):] if provider_name in auth.PROVIDERS: provider = auth.PROVIDERS[provider_name] authorize_url = provider.start(db, req) if authorize_url: raise request.Found(authorize_url) if 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): finishOAuth(db, req, provider) auth.checkSession(db, req) auth.AccessControl.accessHTTP(db, req) user = req.user user.loadPreferences(db) if user.status == 'retired': # If a retired user accesses the system, change the status back # to 'current' again. with db.updating_cursor("users") as cursor: cursor.execute("""UPDATE users SET status='current' WHERE id=%s""", (user.id,)) user.status = 'current' if not user.getPreference(db, "debug.profiling.databaseQueries"): db.disableProfiling() original_path = req.path if not req.path: if user.isAnonymous(): location = "tutorial" else: location = user.getPreference(db, "defaultPage") if req.query: location += "?" + req.query raise request.MovedTemporarily(location) if req.path == "redirect": target = req.getParameter("target", "/") raise request.SeeOther(target) if req.path == "findreview": # This raises either DisplayMessage or MovedTemporarily. findreview(req, db) # 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 [] # Extension "page" roles. Prefixing a path with "!/" bypasses all # extensions. # # Also bypass extensions if the user is anonymous unless general # anonymous access is allowed. If it's not and the user is still # anonymous, access was allowed because of a path-based exception, # which was not intended to allow access to an extension. 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("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/"): return handleDownload(db, req, user) if req.path == "api" or req.path.startswith("api/"): try: result = jsonapi.handleRequest(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)] 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)] impersonate_user = user if not user.isAnonymous(): user_parameter = req.getParameter("user", None) if user_parameter: impersonate_user = dbutils.User.fromName(db, user_parameter) while True: pagefn = PAGES.get(req.path) if pagefn: try: result = pagefn(req, db, impersonate_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, page.utils.ResponseBody): req.setContentType(result.content_type) req.start() return [result.data] if isinstance(result, str) or isinstance(result, Document): req.setContentType("text/html") 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] result = WrappedResult(db, req, user, result) req.setContentType("text/html") 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) if "/" in req.path: repository_name, _, rest = req.path.partition("/") repository = gitutils.Repository.fromName(db, repository_name) if repository: req.path = rest 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 = user.getDefaultRepository(db) if gitutils.re_sha1.match(req.path): if repository and not repository.iscommit(req.path): repository = None if not repository: try: repository = gitutils.Repository.fromSHA1(db, req.path) except gitutils.GitReferenceError: repository = None if repository: try: items = filter(None, map(revparse, req.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" % original_path, status=404) except GeneratorExit: raise except auth.AccessDenied as error: return handleDisplayMessage( db, req, request.DisplayMessage( title="Access denied", body=error.message, status=403)) except request.HTTPResponse as response: return response.execute(db, req) except request.MissingWSGIRemoteUser as error: return handleMissingWSGIRemoteUser(db, req) except page.utils.DisplayMessage as message: return handleDisplayMessage(db, req, message) except page.utils.DisplayFormattedText as formatted_text: return handleDisplayFormattedText(db, req, formatted_text) 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()
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 override_user = req.getParameter("user", None) while True: pagefn = pages.get(req.path)
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 finishGET(critic, req, parameters, resource_class, value, values): assert (value is None) != (values is None) api_version = getAPIVersion(req) try: if values is not None: values_json = [] for value in values: try: values_json.append(resource_class.json(value, parameters)) except ResourceSkipped: pass resource_json = { resource_class.name: values_json } else: try: resource_json = resource_class.json(value, parameters) except ResourceSkipped as error: raise PathError("Resource not found: %s" % error.message) if parameters.output_format == "static": resource_json = { resource_class.name: [resource_json] } except resource_class.exceptions as error: raise PathError("Resource not found: %s" % error.message) except IndexError: raise PathError("List index out of range") if req.method != "DELETE" and parameters.subresource_path: subresource_json = resource_json for component in parameters.subresource_path: subresource_json = subresource_json[component] resource_json = { "/".join(parameters.subresource_path): subresource_json } linked = Linked(req) resource_json = linked.filter_referenced(resource_json) if linked.linked_per_type: all_linked = linked.copy() linked_json = resource_json["linked"] = { resource_type: [] for resource_type in linked.linked_per_type } while not linked.isEmpty(): additional_linked = Linked(req) for resource_type, linked_values in linked.linked_per_type.items(): resource_class = lookup([api_version, resource_type]) for linked_value in linked_values: try: linked_value_json = resource_class.json(linked_value, parameters) except ResourceSkipped: continue linked_json[resource_type].append( additional_linked.filter_referenced(linked_value_json)) for resource_type in linked.linked_per_type.keys(): additional_linked[resource_type] -= all_linked[resource_type] all_linked[resource_type] |= linked[resource_type] linked = additional_linked for linked_items in linked_json.values(): if linked_items and "id" in linked_items[0]: linked_items.sort(key=lambda item: item["id"]) if critic.database.profiling and "dbqueries" in parameters.debug: import profiling # Sort items by accumulated time. items = sorted(critic.database.profiling.items(), key=lambda item: item[1][1], reverse=True) resource_json.setdefault("debug", {})["dbqueries"] = { "formatted": profiling.formatDBProfiling(critic.database), "items": [ { "query": re.sub(r"\s+", " ", query), "count": count, "accumulated": { "time": accumulated_ms, "rows": accumulated_rows }, "maximum": { "time": maximum_ms, "rows": maximum_rows } } for query, (count, accumulated_ms, maximum_ms, accumulated_rows, maximum_rows) in items ] } return resource_json
def finishGET(critic, req, parameters, resource_class, value, values): assert (value is None) != (values is None) api_version = getAPIVersion(req) try: if values is not None: values_json = [] for value in values: try: values_json.append(resource_class.json(value, parameters)) except ResourceSkipped: pass resource_json = {resource_class.name: values_json} else: try: resource_json = resource_class.json(value, parameters) except ResourceSkipped as error: raise PathError("Resource not found: %s" % error.message) if parameters.output_format == "static": resource_json = {resource_class.name: [resource_json]} except resource_class.exceptions as error: raise PathError("Resource not found: %s" % error.message) except IndexError: raise PathError("List index out of range") if req.method != "DELETE" and parameters.subresource_path: subresource_json = resource_json for component in parameters.subresource_path: subresource_json = subresource_json[component] resource_json = { "/".join(parameters.subresource_path): subresource_json } linked = Linked(req) resource_json = linked.filter_referenced(resource_json) if linked.linked_per_type: all_linked = linked.copy() linked_json = resource_json["linked"] = { resource_type: [] for resource_type in linked.linked_per_type } while not linked.isEmpty(): additional_linked = Linked(req) for resource_type, linked_values in linked.linked_per_type.items(): resource_class = lookup([api_version, resource_type]) for linked_value in linked_values: try: linked_value_json = resource_class.json( linked_value, parameters) except ResourceSkipped: continue linked_json[resource_type].append( additional_linked.filter_referenced(linked_value_json)) for resource_type in linked.linked_per_type.keys(): additional_linked[resource_type] -= all_linked[resource_type] all_linked[resource_type] |= linked[resource_type] linked = additional_linked for linked_items in linked_json.values(): if linked_items and "id" in linked_items[0]: linked_items.sort(key=lambda item: item["id"]) if critic.database.profiling and "dbqueries" in parameters.debug: import profiling # Sort items by accumulated time. items = sorted(critic.database.profiling.items(), key=lambda item: item[1][1], reverse=True) resource_json.setdefault("debug", {})["dbqueries"] = { "formatted": profiling.formatDBProfiling(critic.database), "items": [{ "query": re.sub(r"\s+", " ", query), "count": count, "accumulated": { "time": accumulated_ms, "rows": accumulated_rows }, "maximum": { "time": maximum_ms, "rows": maximum_rows } } for query, (count, accumulated_ms, maximum_ms, accumulated_rows, maximum_rows) in items] } return resource_json
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
raise except dbutils.NoSuchUser, error: raise page.utils.DisplayMessage("Invalid URI Parameter!", error.message) if isinstance(result, str) or isinstance(result, Document): req.start() yield str(result) else: for fragment in result: req.start() yield str(fragment) yield "<!-- total request time: %.2f ms -->" % ((time.time() - request_start) * 1000) if db.profiling: yield "<!--\n\n%s\n\n -->" % formatDBProfiling(db) return path = req.path try: repository = gitutils.Repository.fromName(db, path.split("/", 1)[0]) if repository: path = path.split("/", 1)[1] except: repository = None def revparse(item): try: return gitutils.getTaggedCommit(repository, repository.revparse(item)) except: raise
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