1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
import jinja2
import flask
import httpauth
import dulwich.web
from klaus import views, utils
from klaus.repo import FancyRepo
KLAUS_VERSION = utils.guess_git_revision() or '0.2.3'
class Klaus(flask.Flask):
jinja_options = {
'extensions': ['jinja2.ext.autoescape'],
'undefined': jinja2.StrictUndefined
}
def __init__(self, repo_paths, site_name, use_smarthttp):
self.repos = map(FancyRepo, repo_paths)
self.repo_map = dict((repo.name, repo) for repo in self.repos)
self.site_name = site_name
self.use_smarthttp = use_smarthttp
flask.Flask.__init__(self, __name__)
self.setup_routes()
def create_jinja_environment(self):
""" Called by Flask.__init__ """
env = super(Klaus, self).create_jinja_environment()
for func in [
'force_unicode',
'timesince',
'shorten_sha1',
'shorten_message',
'extract_author_name',
'formattimestamp',
]:
env.filters[func] = getattr(utils, func)
env.globals['KLAUS_VERSION'] = KLAUS_VERSION
env.globals['USE_SMARTHTTP'] = self.use_smarthttp
env.globals['SITE_NAME'] = self.site_name
return env
def setup_routes(self):
for endpoint, rule in [
('repo_list', '/'),
('blob', '/<repo>/blob/<rev>/'),
('blob', '/<repo>/blob/<rev>/<path:path>'),
('raw', '/<repo>/raw/<rev>/'),
('raw', '/<repo>/raw/<rev>/<path:path>'),
('commit', '/<repo>/commit/<rev>/'),
('history', '/<repo>/'),
('history', '/<repo>/tree/<rev>/'),
('history', '/<repo>/tree/<rev>/<path:path>'),
]:
self.add_url_rule(rule, view_func=getattr(views, endpoint))
def make_app(repos, site_name, use_smarthttp=False, htdigest_file=None):
"""
Returns a WSGI with all the features (smarthttp, authentication) already
patched in.
"""
app = Klaus(
repos,
site_name,
use_smarthttp,
)
app.wsgi_app = utils.SubUri(app.wsgi_app)
if use_smarthttp:
# `path -> Repo` mapping for Dulwich's web support
dulwich_backend = dulwich.server.DictBackend(
dict(('/'+repo.name, repo) for repo in app.repos)
)
# Dulwich takes care of all Git related requests/URLs
# and passes through everything else to klaus
dulwich_wrapped_app = dulwich.web.make_wsgi_chain(
backend=dulwich_backend,
fallback_app=app.wsgi_app,
)
# `receive-pack` is requested by the "client" on a push
# (the "server" is asked to *receive* packs), i.e. we need to secure
# it using authentication or deny access completely to make the repo
# read-only.
#
# Git first sends requests to /<repo-name>/info/refs?service=git-receive-pack.
# If this request is responded to using HTTP 401 Unauthorized, the user
# is prompted for username and password. If we keep responding 401, Git
# interprets this as an authentication failure. (We can't respond 403
# because this results in horrible, unhelpful Git error messages.)
#
# Git will never call /<repo-name>/git-receive-pack if authentication
# failed for /info/refs, but since it's used to upload stuff to the server
# we must secure it anyway for security reasons.
PATTERN = r'^/[^/]+/(info/refs\?service=git-receive-pack|git-receive-pack)$'
if htdigest_file:
# .htdigest file given. Use it to read the push-er credentials from.
app.wsgi_app = httpauth.DigestFileHttpAuthMiddleware(
htdigest_file,
wsgi_app=utils.SubUri(dulwich_wrapped_app),
routes=[PATTERN],
)
else:
# no .htdigest file given. Disable push-ing. Semantically we should
# use HTTP 403 here but since that results in freaky error messages
# (see above) we keep asking for authentication (401) instead.
# Git will print a nice error message after a few tries.
app.wsgi_app = httpauth.AlwaysFailingAuthMiddleware(
wsgi_app=utils.SubUri(dulwich_wrapped_app),
routes=[PATTERN],
)
return app
|