##// END OF EJS Templates
hgweb: initialize permhooks at definition time...
Martin Geisler -
r14058:3233b39d default
parent child Browse files
Show More
@@ -1,186 +1,186 b''
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import errno, mimetypes, os
9 import errno, mimetypes, os
10
10
11 HTTP_OK = 200
11 HTTP_OK = 200
12 HTTP_NOT_MODIFIED = 304
12 HTTP_NOT_MODIFIED = 304
13 HTTP_BAD_REQUEST = 400
13 HTTP_BAD_REQUEST = 400
14 HTTP_UNAUTHORIZED = 401
14 HTTP_UNAUTHORIZED = 401
15 HTTP_FORBIDDEN = 403
15 HTTP_FORBIDDEN = 403
16 HTTP_NOT_FOUND = 404
16 HTTP_NOT_FOUND = 404
17 HTTP_METHOD_NOT_ALLOWED = 405
17 HTTP_METHOD_NOT_ALLOWED = 405
18 HTTP_SERVER_ERROR = 500
18 HTTP_SERVER_ERROR = 500
19
19
20 # Hooks for hgweb permission checks; extensions can add hooks here. Each hook
21 # is invoked like this: hook(hgweb, request, operation), where operation is
22 # either read, pull or push. Hooks should either raise an ErrorResponse
23 # exception, or just return.
24 # It is possible to do both authentication and authorization through this.
25 permhooks = []
26
20
27 def checkauthz(hgweb, req, op):
21 def checkauthz(hgweb, req, op):
28 '''Check permission for operation based on request data (including
22 '''Check permission for operation based on request data (including
29 authentication info). Return if op allowed, else raise an ErrorResponse
23 authentication info). Return if op allowed, else raise an ErrorResponse
30 exception.'''
24 exception.'''
31
25
32 user = req.env.get('REMOTE_USER')
26 user = req.env.get('REMOTE_USER')
33
27
34 deny_read = hgweb.configlist('web', 'deny_read')
28 deny_read = hgweb.configlist('web', 'deny_read')
35 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
29 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
36 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
30 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
37
31
38 allow_read = hgweb.configlist('web', 'allow_read')
32 allow_read = hgweb.configlist('web', 'allow_read')
39 result = (not allow_read) or (allow_read == ['*'])
33 result = (not allow_read) or (allow_read == ['*'])
40 if not (result or user in allow_read):
34 if not (result or user in allow_read):
41 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
35 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
42
36
43 if op == 'pull' and not hgweb.allowpull:
37 if op == 'pull' and not hgweb.allowpull:
44 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
38 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
45 elif op == 'pull' or op is None: # op is None for interface requests
39 elif op == 'pull' or op is None: # op is None for interface requests
46 return
40 return
47
41
48 # enforce that you can only push using POST requests
42 # enforce that you can only push using POST requests
49 if req.env['REQUEST_METHOD'] != 'POST':
43 if req.env['REQUEST_METHOD'] != 'POST':
50 msg = 'push requires POST request'
44 msg = 'push requires POST request'
51 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
45 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
52
46
53 # require ssl by default for pushing, auth info cannot be sniffed
47 # require ssl by default for pushing, auth info cannot be sniffed
54 # and replayed
48 # and replayed
55 scheme = req.env.get('wsgi.url_scheme')
49 scheme = req.env.get('wsgi.url_scheme')
56 if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
50 if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
57 raise ErrorResponse(HTTP_OK, 'ssl required')
51 raise ErrorResponse(HTTP_OK, 'ssl required')
58
52
59 deny = hgweb.configlist('web', 'deny_push')
53 deny = hgweb.configlist('web', 'deny_push')
60 if deny and (not user or deny == ['*'] or user in deny):
54 if deny and (not user or deny == ['*'] or user in deny):
61 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
55 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
62
56
63 allow = hgweb.configlist('web', 'allow_push')
57 allow = hgweb.configlist('web', 'allow_push')
64 result = allow and (allow == ['*'] or user in allow)
58 result = allow and (allow == ['*'] or user in allow)
65 if not result:
59 if not result:
66 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
60 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
67
61
68 # Add the default permhook, which provides simple authorization.
62 # Hooks for hgweb permission checks; extensions can add hooks here.
69 permhooks.append(checkauthz)
63 # Each hook is invoked like this: hook(hgweb, request, operation),
64 # where operation is either read, pull or push. Hooks should either
65 # raise an ErrorResponse exception, or just return.
66 #
67 # It is possible to do both authentication and authorization through
68 # this.
69 permhooks = [checkauthz]
70
70
71
71
72 class ErrorResponse(Exception):
72 class ErrorResponse(Exception):
73 def __init__(self, code, message=None, headers=[]):
73 def __init__(self, code, message=None, headers=[]):
74 if message is None:
74 if message is None:
75 message = _statusmessage(code)
75 message = _statusmessage(code)
76 Exception.__init__(self)
76 Exception.__init__(self)
77 self.code = code
77 self.code = code
78 self.message = message
78 self.message = message
79 self.headers = headers
79 self.headers = headers
80 def __str__(self):
80 def __str__(self):
81 return self.message
81 return self.message
82
82
83 class continuereader(object):
83 class continuereader(object):
84 def __init__(self, f, write):
84 def __init__(self, f, write):
85 self.f = f
85 self.f = f
86 self._write = write
86 self._write = write
87 self.continued = False
87 self.continued = False
88
88
89 def read(self, amt=-1):
89 def read(self, amt=-1):
90 if not self.continued:
90 if not self.continued:
91 self.continued = True
91 self.continued = True
92 self._write('HTTP/1.1 100 Continue\r\n\r\n')
92 self._write('HTTP/1.1 100 Continue\r\n\r\n')
93 return self.f.read(amt)
93 return self.f.read(amt)
94
94
95 def __getattr__(self, attr):
95 def __getattr__(self, attr):
96 if attr in ('close', 'readline', 'readlines', '__iter__'):
96 if attr in ('close', 'readline', 'readlines', '__iter__'):
97 return getattr(self.f, attr)
97 return getattr(self.f, attr)
98 raise AttributeError()
98 raise AttributeError()
99
99
100 def _statusmessage(code):
100 def _statusmessage(code):
101 from BaseHTTPServer import BaseHTTPRequestHandler
101 from BaseHTTPServer import BaseHTTPRequestHandler
102 responses = BaseHTTPRequestHandler.responses
102 responses = BaseHTTPRequestHandler.responses
103 return responses.get(code, ('Error', 'Unknown error'))[0]
103 return responses.get(code, ('Error', 'Unknown error'))[0]
104
104
105 def statusmessage(code, message=None):
105 def statusmessage(code, message=None):
106 return '%d %s' % (code, message or _statusmessage(code))
106 return '%d %s' % (code, message or _statusmessage(code))
107
107
108 def get_stat(spath):
108 def get_stat(spath):
109 """stat changelog if it exists, spath otherwise"""
109 """stat changelog if it exists, spath otherwise"""
110 cl_path = os.path.join(spath, "00changelog.i")
110 cl_path = os.path.join(spath, "00changelog.i")
111 if os.path.exists(cl_path):
111 if os.path.exists(cl_path):
112 return os.stat(cl_path)
112 return os.stat(cl_path)
113 else:
113 else:
114 return os.stat(spath)
114 return os.stat(spath)
115
115
116 def get_mtime(spath):
116 def get_mtime(spath):
117 return get_stat(spath).st_mtime
117 return get_stat(spath).st_mtime
118
118
119 def staticfile(directory, fname, req):
119 def staticfile(directory, fname, req):
120 """return a file inside directory with guessed Content-Type header
120 """return a file inside directory with guessed Content-Type header
121
121
122 fname always uses '/' as directory separator and isn't allowed to
122 fname always uses '/' as directory separator and isn't allowed to
123 contain unusual path components.
123 contain unusual path components.
124 Content-Type is guessed using the mimetypes module.
124 Content-Type is guessed using the mimetypes module.
125 Return an empty string if fname is illegal or file not found.
125 Return an empty string if fname is illegal or file not found.
126
126
127 """
127 """
128 parts = fname.split('/')
128 parts = fname.split('/')
129 for part in parts:
129 for part in parts:
130 if (part in ('', os.curdir, os.pardir) or
130 if (part in ('', os.curdir, os.pardir) or
131 os.sep in part or os.altsep is not None and os.altsep in part):
131 os.sep in part or os.altsep is not None and os.altsep in part):
132 return ""
132 return ""
133 fpath = os.path.join(*parts)
133 fpath = os.path.join(*parts)
134 if isinstance(directory, str):
134 if isinstance(directory, str):
135 directory = [directory]
135 directory = [directory]
136 for d in directory:
136 for d in directory:
137 path = os.path.join(d, fpath)
137 path = os.path.join(d, fpath)
138 if os.path.exists(path):
138 if os.path.exists(path):
139 break
139 break
140 try:
140 try:
141 os.stat(path)
141 os.stat(path)
142 ct = mimetypes.guess_type(path)[0] or "text/plain"
142 ct = mimetypes.guess_type(path)[0] or "text/plain"
143 req.respond(HTTP_OK, ct, length = os.path.getsize(path))
143 req.respond(HTTP_OK, ct, length = os.path.getsize(path))
144 fp = open(path, 'rb')
144 fp = open(path, 'rb')
145 data = fp.read()
145 data = fp.read()
146 fp.close()
146 fp.close()
147 return data
147 return data
148 except TypeError:
148 except TypeError:
149 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
149 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
150 except OSError, err:
150 except OSError, err:
151 if err.errno == errno.ENOENT:
151 if err.errno == errno.ENOENT:
152 raise ErrorResponse(HTTP_NOT_FOUND)
152 raise ErrorResponse(HTTP_NOT_FOUND)
153 else:
153 else:
154 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
154 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
155
155
156 def paritygen(stripecount, offset=0):
156 def paritygen(stripecount, offset=0):
157 """count parity of horizontal stripes for easier reading"""
157 """count parity of horizontal stripes for easier reading"""
158 if stripecount and offset:
158 if stripecount and offset:
159 # account for offset, e.g. due to building the list in reverse
159 # account for offset, e.g. due to building the list in reverse
160 count = (stripecount + offset) % stripecount
160 count = (stripecount + offset) % stripecount
161 parity = (stripecount + offset) / stripecount & 1
161 parity = (stripecount + offset) / stripecount & 1
162 else:
162 else:
163 count = 0
163 count = 0
164 parity = 0
164 parity = 0
165 while True:
165 while True:
166 yield parity
166 yield parity
167 count += 1
167 count += 1
168 if stripecount and count >= stripecount:
168 if stripecount and count >= stripecount:
169 parity = 1 - parity
169 parity = 1 - parity
170 count = 0
170 count = 0
171
171
172 def get_contact(config):
172 def get_contact(config):
173 """Return repo contact information or empty string.
173 """Return repo contact information or empty string.
174
174
175 web.contact is the primary source, but if that is not set, try
175 web.contact is the primary source, but if that is not set, try
176 ui.username or $EMAIL as a fallback to display something useful.
176 ui.username or $EMAIL as a fallback to display something useful.
177 """
177 """
178 return (config("web", "contact") or
178 return (config("web", "contact") or
179 config("ui", "username") or
179 config("ui", "username") or
180 os.environ.get("EMAIL") or "")
180 os.environ.get("EMAIL") or "")
181
181
182 def caching(web, req):
182 def caching(web, req):
183 tag = str(web.mtime)
183 tag = str(web.mtime)
184 if req.env.get('HTTP_IF_NONE_MATCH') == tag:
184 if req.env.get('HTTP_IF_NONE_MATCH') == tag:
185 raise ErrorResponse(HTTP_NOT_MODIFIED)
185 raise ErrorResponse(HTTP_NOT_MODIFIED)
186 req.headers.append(('ETag', tag))
186 req.headers.append(('ETag', tag))
General Comments 0
You need to be logged in to leave comments. Login now