This commit is contained in:
Matthew Scragg 2013-11-08 12:20:40 -06:00
parent ba1ec10a34
commit 36cf728862
24 changed files with 181 additions and 488 deletions

View file

@ -4,19 +4,21 @@ import time
from threading import Lock
import rethinkdb as rdb
from flask import Flask, request, render_template, url_for, redirect, flash
from flask import Flask, g, request, render_template, url_for, redirect, flash, session
from flask.ctx import _AppCtxGlobals
from flask.ext.login import LoginManager, login_required
from flask.ext.assets import Environment, Bundle
from recaptcha.client import captcha
from werkzeug.routing import BaseConverter
from werkzeug.utils import cached_property
from session import RedisSessionInterface
import config
from wiki import Wiki
from util import to_canonical, remove_ext, mkdir_safe, gravatar_url
from realms.lib.ratelimit import get_view_rate_limit, ratelimiter
from realms.lib.session import RedisSessionInterface
from realms.lib.wiki import Wiki
from realms.lib.util import to_canonical, remove_ext, mkdir_safe, gravatar_url
from realms.lib.services import db
from models import Site, User, CurrentUser
from ratelimit import get_view_rate_limit, ratelimiter
from services import db
# Flask instance container
instances = {}
@ -26,6 +28,17 @@ login_manager = LoginManager()
assets = Environment()
class AppCtxGlobals(_AppCtxGlobals):
@cached_property
def current_user(self):
return session.get('user') if session.get('user') else {'username': 'Anon'}
class Application(Flask):
app_ctx_globals_class = AppCtxGlobals
class SubdomainDispatcher(object):
"""
Application factory
@ -90,19 +103,12 @@ def redirect_url(referrer=None):
return request.args.get('next') or referrer or url_for('index')
def validate_captcha():
response = captcha.submit(
request.form['recaptcha_challenge_field'],
request.form['recaptcha_response_field'],
config.flask['RECAPTCHA_PRIVATE_KEY'],
request.remote_addr)
return response.is_valid
def format_subdomain(s):
if not config.repos['enable_subrepos']:
return ""
s = s.lower()
s = to_canonical(s)
if s in ['www', 'api']:
if s in config.repos['forbidden_subrepos']:
# Not allowed
s = ""
return s
@ -116,7 +122,7 @@ def make_app(subdomain):
def create_app(subdomain=None):
app = Flask(__name__)
app = Application(__name__)
app.config.update(config.flask)
app.debug = (config.ENV is not 'PROD')
app.secret_key = config.secret_key
@ -199,7 +205,7 @@ def create_app(subdomain=None):
def home():
return redirect(url_for('root'))
@app.route("/account/")
@app.route("/_account/")
@login_required
def account():
return render_template('account/index.html')
@ -215,13 +221,13 @@ def create_app(subdomain=None):
return redirect(redirect_url())
else:
s = Site()
s.create(name=wiki_name, repo=wiki_name, founder=CurrentUser.get('id'))
s.create(name=wiki_name, repo=wiki_name, founder=g.current_user.get('id'))
instances.pop(wiki_name, None)
return redirect('http://%s.%s' % (wiki_name, config.hostname))
else:
return render_template('_new/index.html')
@app.route("/logout/")
@app.route("/_logout/")
def logout():
User.logout()
return redirect(url_for('root'))
@ -234,7 +240,7 @@ def create_app(subdomain=None):
if data:
return render_template('page/page.html', name=name, page=data, commit=sha)
else:
return redirect('/create/'+cname)
return redirect('/_create/'+cname)
@app.route("/_compare/<name>/<regex('[^.]+'):fsha><regex('\.{2,3}'):dots><regex('.+'):lsha>")
def compare(name, fsha, dots, lsha):
@ -247,11 +253,11 @@ def create_app(subdomain=None):
name = request.form.get('name')
commit = request.form.get('commit')
cname = to_canonical(name)
w.revert_page(name, commit, message="Reverting %s" % cname, username=CurrentUser.get('username'))
w.revert_page(name, commit, message="Reverting %s" % cname, username=g.current_user.get('username'))
flash('Page reverted', 'success')
return redirect("/" + cname)
@app.route("/register", methods=['GET', 'POST'])
@app.route("/_register", methods=['GET', 'POST'])
def register():
if request.method == 'POST':
if User.register(request.form.get('username'), request.form.get('email'), request.form.get('password')):
@ -262,14 +268,14 @@ def create_app(subdomain=None):
else:
return render_template('account/register.html')
@app.route("/login", methods=['GET', 'POST'])
@app.route("/_login", methods=['GET', 'POST'])
def login():
if request.method == 'POST':
if User.auth(request.form['email'], request.form['password']):
return redirect(redirect_url(referrer=url_for('root')))
else:
flash("Email or Password invalid")
return redirect("/login")
return redirect("/_login")
else:
return render_template('account/login.html')
@ -288,7 +294,7 @@ def create_app(subdomain=None):
w.rename_page(cname, edit_cname)
w.write_page(edit_cname, request.form['content'],
message=request.form['message'],
username=CurrentUser.get('username'))
username=g.current_user.get('username'))
return redirect("/" + edit_cname)
else:
if data:
@ -296,7 +302,7 @@ def create_app(subdomain=None):
content = data['data']
return render_template('page/edit.html', name=name, content=content)
else:
return redirect('/create/'+cname)
return redirect('/_create/'+cname)
@app.route("/_delete/<name>", methods=['POST'])
@login_required
@ -316,7 +322,7 @@ def create_app(subdomain=None):
w.write_page(request.form['name'], request.form['content'],
message=request.form['message'],
create=True,
username=CurrentUser.get('username'))
username=g.current_user.get('username'))
return redirect("/" + cname)
else:
return render_template('page/edit.html', name=cname, content="")
@ -331,6 +337,6 @@ def create_app(subdomain=None):
if data:
return render_template('page/page.html', name=cname, page=data)
else:
return redirect('/create/'+cname)
return redirect('/_create/'+cname)
return app

0
realms/lib/__init__.py Normal file
View file

View file

@ -1,7 +1,6 @@
import rethinkdb as rdb
import redis
import config
from realms import config
# Default DB connection

128
realms/lib/util.py Normal file
View file

@ -0,0 +1,128 @@
import re
import os
import hashlib
import json
from flask import request
from recaptcha.client import captcha
from realms import config
from realms.lib.services import cache
def cache_it(fn):
def wrap(*args, **kw):
key = "%s:%s" % (args[0].table, args[1])
data = cache.get(key)
# Assume strings are JSON encoded
try:
data = json.loads(data)
except TypeError:
pass
except ValueError:
pass
if data is not None:
return data
else:
data = fn(*args)
print data
ret = data
if data is None:
data = ''
if not isinstance(data, basestring):
try:
data = json.dumps(data, separators=(',', ':'))
except TypeError:
pass
cache.set(key, data)
return ret
return wrap
def to_json(res, first=False):
"""
Jsonify query result.
"""
res = to_dict(res, first)
return json.dumps(res, separators=(',', ':'))
def to_dict(cur, first=False):
if not cur:
return None
ret = []
for row in cur:
ret.append(row)
if ret and first:
return ret[0]
else:
return ret
def validate_captcha():
response = captcha.submit(
request.form['recaptcha_challenge_field'],
request.form['recaptcha_response_field'],
config.flask['RECAPTCHA_PRIVATE_KEY'],
request.remote_addr)
return response.is_valid
def mkdir_safe(path):
if path and not(os.path.exists(path)):
os.makedirs(path)
return path
def extract_path(file_path):
if not file_path:
return None
last_slash = file_path.rindex("/")
if last_slash:
return file_path[0, last_slash]
def clean_path(path):
if path:
if path[0] != '/':
path.insert(0, '/')
return re.sub(r"//+", '/', path)
def extract_name(file_path):
if file_path[-1] == "/":
return None
return os.path.basename(file_path)
def remove_ext(path):
return os.path.splitext(path)[0]
def clean_url(url):
if not url:
return url
url = url.replace('%2F', '/')
url = re.sub(r"^/+", "", url)
return re.sub(r"//+", '/', url)
def to_canonical(s):
"""
Double space -> single dash
Double dash -> single dash
Remove all non alphanumeric and dash
Limit to first 64 chars
"""
s = s.encode('ascii', 'ignore')
s = str(s)
s = re.sub(r"\s\s*", "-", s)
s = re.sub(r"\-\-+", "-", s)
s = re.sub(r"[^a-zA-Z0-9\-]", "", s)
s = s[:64]
return s
def gravatar_url(email):
return "https://www.gravatar.com/avatar/" + hashlib.md5(email).hexdigest()

View file

@ -7,8 +7,8 @@ import ghdiff
import gittle.utils
from gittle import Gittle
from dulwich.repo import NotGitRepository
from util import to_canonical, escape_repl, unescape_repl
from werkzeug.utils import escape, unescape
from util import to_canonical
from models import Site
@ -80,6 +80,14 @@ class Wiki():
def write_page(self, name, content, message=None, create=False, username=None, email=None):
def escape_repl(m):
if m.group(1):
return "```" + escape(m.group(1)) + "```"
def unescape_repl(m):
if m.group(1):
return "```" + unescape(m.group(1)) + "```"
# prevents p tag from being added, we remove this later
content = '<div>' + content + '</div>'
content = re.sub(r"```(.*?)```", escape_repl, content, flags=re.DOTALL)
@ -93,9 +101,11 @@ class Wiki():
# post processing to fix errors
content = content[5:-6]
# FIXME this is for block quotes, doesn't work for double ">"
content = re.sub(r"(\n&gt;)", "\n>", content)
content = re.sub(r"(^&gt;)", ">", content)
content = re.sub(r"```(.*?)```", unescape_repl, content, flags=re.DOTALL)
filename = self.cname_to_filename(to_canonical(name))
@ -129,7 +139,7 @@ class Wiki():
files=[old_name])
def get_page(self, name, sha='HEAD'):
commit = gittle.utils.git.commit_info(self.repo[sha])
# commit = gittle.utils.git.commit_info(self.repo[sha])
name = self.cname_to_filename(name)
try:
return self.repo.get_commit_files(sha, paths=[name]).get(name)

View file

@ -1,60 +1,9 @@
import json
import rethinkdb as rdb
import bcrypt
from flask import session, flash
from flask.ext.login import login_user, logout_user
from util import gravatar_url
from services import db, cache
def to_json(res, first=False):
"""
Jsonify query result.
"""
res = to_dict(res, first)
return json.dumps(res, separators=(',',':'))
def to_dict(cur, first=False):
if not cur:
return None
ret = []
for row in cur:
ret.append(row)
if ret and first:
return ret[0]
else:
return ret
def cache_it(fn):
def wrap(*args, **kw):
key = "%s:%s" % (args[0].table, args[1])
data = cache.get(key)
# Assume strings are JSON encoded
try:
data = json.loads(data)
except TypeError:
pass
except ValueError:
pass
if data is not None:
return data
else:
data = fn(*args)
print data
ret = data
if data is None:
data = ''
if not isinstance(data, basestring):
try:
data = json.dumps(data, separators=(',', ':'))
except TypeError:
pass
cache.set(key, data)
return ret
return wrap
from realms.lib.util import gravatar_url, to_dict, cache_it
from realms.lib.services import db
class BaseModel():
@ -162,7 +111,7 @@ class User(BaseModel):
User.login(u.id)
@classmethod
def login(cls, id, data=None):
def login(cls, id):
login_user(CurrentUser(id), True)
@classmethod

View file

@ -1,95 +0,0 @@
import re
import os
import hashlib
def escape_repl(m):
print "group 0"
print m.group(0)
print "group 1"
print m.group(1)
if m.group(1):
return "```" + escape_html(m.group(1)) + "```"
def unescape_repl(m):
if m.group(1):
return "```" + unescape_html(m.group(1)) + "```"
def escape_html(s):
s = s.replace("&", '&amp;')
s = s.replace("<", '&lt;')
s = s.replace(">", '&gt;')
s = s.replace('"', '&quot;')
s = s.replace("'", '&#39;')
return s
def unescape_html(s):
s = s.replace('&amp;', "&")
s = s.replace('&lt;', "<")
s = s.replace('&gt;', ">")
s = s.replace('&quot;', '"')
s = s.replace('&#39;', "'")
return s
def mkdir_safe(path):
if path and not(os.path.exists(path)):
os.makedirs(path)
return path
def extract_path(file_path):
if not file_path:
return None
last_slash = file_path.rindex("/")
if last_slash:
return file_path[0, last_slash]
def clean_path(path):
if path:
if path[0] != '/':
path.insert(0, '/')
return re.sub(r"//+", '/', path)
def extract_name(file_path):
if file_path[-1] == "/":
return None
return os.path.basename(file_path)
def remove_ext(path):
return os.path.splitext(path)[0]
def clean_url(url):
if not url:
return url
url = url.replace('%2F', '/')
url = re.sub(r"^/+", "", url)
return re.sub(r"//+", '/', url)
def to_canonical(s):
"""
Double space -> single dash
Double dash -> single dash
Remove all non alphanumeric and dash
Limit to first 64 chars
"""
s = s.encode('ascii', 'ignore')
s = str(s)
s = re.sub(r"\s\s*", "-", s)
s = re.sub(r"\-\-+", "-", s)
s = re.sub(r"[^a-zA-Z0-9\-]", "", s)
s = s[:64]
return s
def gravatar_url(email):
return "https://www.gravatar.com/avatar/" + hashlib.md5(email).hexdigest()