##// END OF EJS Templates
fixes #20 hg middleware breaks ui() instance when repository has hgrc file....
marcink -
r385:eda5f01d default
parent child Browse files
Show More
@@ -1,233 +1,250 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 # middleware to handle mercurial api calls
3 # middleware to handle mercurial api calls
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; version 2
8 # as published by the Free Software Foundation; version 2
9 # of the License or (at your opinion) any later version of the license.
9 # of the License or (at your opinion) any later version of the license.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 # MA 02110-1301, USA.
19 # MA 02110-1301, USA.
20
21 """
22 Created on 2010-04-28
23
24 @author: marcink
25 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
26 It's implemented with basic auth function
27 """
28 from datetime import datetime
20 from datetime import datetime
29 from itertools import chain
21 from itertools import chain
30 from mercurial.error import RepoError
22 from mercurial.error import RepoError
31 from mercurial.hgweb import hgweb
23 from mercurial.hgweb import hgweb
32 from mercurial.hgweb.request import wsgiapplication
24 from mercurial.hgweb.request import wsgiapplication
33 from paste.auth.basic import AuthBasicAuthenticator
25 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
26 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from pylons_app.lib.auth import authfunc, HasPermissionAnyMiddleware, \
27 from pylons_app.lib.auth import authfunc, HasPermissionAnyMiddleware, \
36 get_user_cached
28 get_user_cached
37 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache, \
29 from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache, \
38 check_repo_fast
30 check_repo_fast, ui_sections
39 from pylons_app.model import meta
31 from pylons_app.model import meta
40 from pylons_app.model.db import UserLog, User
32 from pylons_app.model.db import UserLog, User
41 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
33 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
42 import logging
34 import logging
43 import os
35 import os
44 import pylons_app.lib.helpers as h
36 import pylons_app.lib.helpers as h
45 import traceback
37 import traceback
46
38
39 """
40 Created on 2010-04-28
41
42 @author: marcink
43 SimpleHG middleware for handling mercurial protocol request (push/clone etc.)
44 It's implemented with basic auth function
45 """
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49 class SimpleHg(object):
49 class SimpleHg(object):
50
50
51 def __init__(self, application, config):
51 def __init__(self, application, config):
52 self.application = application
52 self.application = application
53 self.config = config
53 self.config = config
54 #authenticate this mercurial request using
54 #authenticate this mercurial request using
55 self.authenticate = AuthBasicAuthenticator('', authfunc)
55 self.authenticate = AuthBasicAuthenticator('', authfunc)
56
56
57 def __call__(self, environ, start_response):
57 def __call__(self, environ, start_response):
58 if not is_mercurial(environ):
58 if not is_mercurial(environ):
59 return self.application(environ, start_response)
59 return self.application(environ, start_response)
60
60
61 #===================================================================
61 #===================================================================
62 # AUTHENTICATE THIS MERCURIAL REQUEST
62 # AUTHENTICATE THIS MERCURIAL REQUEST
63 #===================================================================
63 #===================================================================
64 username = REMOTE_USER(environ)
64 username = REMOTE_USER(environ)
65 if not username:
65 if not username:
66 self.authenticate.realm = self.config['hg_app_realm']
66 self.authenticate.realm = self.config['hg_app_realm']
67 result = self.authenticate(environ)
67 result = self.authenticate(environ)
68 if isinstance(result, str):
68 if isinstance(result, str):
69 AUTH_TYPE.update(environ, 'basic')
69 AUTH_TYPE.update(environ, 'basic')
70 REMOTE_USER.update(environ, result)
70 REMOTE_USER.update(environ, result)
71 else:
71 else:
72 return result.wsgi_application(environ, start_response)
72 return result.wsgi_application(environ, start_response)
73
73
74 try:
74 try:
75 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
75 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
76 except:
76 except:
77 log.error(traceback.format_exc())
77 log.error(traceback.format_exc())
78 return HTTPInternalServerError()(environ, start_response)
78 return HTTPInternalServerError()(environ, start_response)
79
79
80 #===================================================================
80 #===================================================================
81 # CHECK PERMISSIONS FOR THIS REQUEST
81 # CHECK PERMISSIONS FOR THIS REQUEST
82 #===================================================================
82 #===================================================================
83 action = self.__get_action(environ)
83 action = self.__get_action(environ)
84 if action:
84 if action:
85 username = self.__get_environ_user(environ)
85 username = self.__get_environ_user(environ)
86 try:
86 try:
87 user = self.__get_user(username)
87 user = self.__get_user(username)
88 except:
88 except:
89 log.error(traceback.format_exc())
89 log.error(traceback.format_exc())
90 return HTTPInternalServerError()(environ, start_response)
90 return HTTPInternalServerError()(environ, start_response)
91 #check permissions for this repository
91 #check permissions for this repository
92 if action == 'pull':
92 if action == 'pull':
93 if not HasPermissionAnyMiddleware('repository.read',
93 if not HasPermissionAnyMiddleware('repository.read',
94 'repository.write',
94 'repository.write',
95 'repository.admin')\
95 'repository.admin')\
96 (user, repo_name):
96 (user, repo_name):
97 return HTTPForbidden()(environ, start_response)
97 return HTTPForbidden()(environ, start_response)
98 if action == 'push':
98 if action == 'push':
99 if not HasPermissionAnyMiddleware('repository.write',
99 if not HasPermissionAnyMiddleware('repository.write',
100 'repository.admin')\
100 'repository.admin')\
101 (user, repo_name):
101 (user, repo_name):
102 return HTTPForbidden()(environ, start_response)
102 return HTTPForbidden()(environ, start_response)
103
103
104 #log action
104 #log action
105 proxy_key = 'HTTP_X_REAL_IP'
105 proxy_key = 'HTTP_X_REAL_IP'
106 def_key = 'REMOTE_ADDR'
106 def_key = 'REMOTE_ADDR'
107 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
107 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
108 self.__log_user_action(user, action, repo_name, ipaddr)
108 self.__log_user_action(user, action, repo_name, ipaddr)
109
109
110 #===================================================================
110 #===================================================================
111 # MERCURIAL REQUEST HANDLING
111 # MERCURIAL REQUEST HANDLING
112 #===================================================================
112 #===================================================================
113 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
113 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
114 self.baseui = make_ui('db')
114 self.baseui = make_ui('db')
115 self.basepath = self.config['base_path']
115 self.basepath = self.config['base_path']
116 self.repo_path = os.path.join(self.basepath, repo_name)
116 self.repo_path = os.path.join(self.basepath, repo_name)
117
117
118 #quick check if that dir exists...
118 #quick check if that dir exists...
119 if check_repo_fast(repo_name, self.basepath):
119 if check_repo_fast(repo_name, self.basepath):
120 return HTTPNotFound()(environ, start_response)
120 return HTTPNotFound()(environ, start_response)
121 try:
121 try:
122 app = wsgiapplication(self.__make_app)
122 app = wsgiapplication(self.__make_app)
123 except RepoError as e:
123 except RepoError as e:
124 if str(e).find('not found') != -1:
124 if str(e).find('not found') != -1:
125 return HTTPNotFound()(environ, start_response)
125 return HTTPNotFound()(environ, start_response)
126 except Exception:
126 except Exception:
127 log.error(traceback.format_exc())
127 log.error(traceback.format_exc())
128 return HTTPInternalServerError()(environ, start_response)
128 return HTTPInternalServerError()(environ, start_response)
129
129
130 #invalidate cache on push
130 #invalidate cache on push
131 if action == 'push':
131 if action == 'push':
132 self.__invalidate_cache(repo_name)
132 self.__invalidate_cache(repo_name)
133 messages = []
133 messages = []
134 messages.append('thank you for using hg-app')
134 messages.append('thank you for using hg-app')
135
135
136 return self.msg_wrapper(app, environ, start_response, messages)
136 return self.msg_wrapper(app, environ, start_response, messages)
137 else:
137 else:
138 return app(environ, start_response)
138 return app(environ, start_response)
139
139
140
140
141 def msg_wrapper(self, app, environ, start_response, messages=[]):
141 def msg_wrapper(self, app, environ, start_response, messages=[]):
142 """
142 """
143 Wrapper for custom messages that come out of mercurial respond messages
143 Wrapper for custom messages that come out of mercurial respond messages
144 is a list of messages that the user will see at the end of response
144 is a list of messages that the user will see at the end of response
145 from merurial protocol actions that involves remote answers
145 from merurial protocol actions that involves remote answers
146 @param app:
146 @param app:
147 @param environ:
147 @param environ:
148 @param start_response:
148 @param start_response:
149 """
149 """
150 def custom_messages(msg_list):
150 def custom_messages(msg_list):
151 for msg in msg_list:
151 for msg in msg_list:
152 yield msg + '\n'
152 yield msg + '\n'
153 org_response = app(environ, start_response)
153 org_response = app(environ, start_response)
154 return chain(org_response, custom_messages(messages))
154 return chain(org_response, custom_messages(messages))
155
155
156 def __make_app(self):
156 def __make_app(self):
157 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
157 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
158 return self.__load_web_settings(hgserve)
158 return self.__load_web_settings(hgserve)
159
159
160 def __get_environ_user(self, environ):
160 def __get_environ_user(self, environ):
161 return environ.get('REMOTE_USER')
161 return environ.get('REMOTE_USER')
162
162
163 def __get_user(self, username):
163 def __get_user(self, username):
164 return get_user_cached(username)
164 return get_user_cached(username)
165
165
166
166
167
167
168 def __get_size(self, repo_path, content_size):
168 def __get_size(self, repo_path, content_size):
169 size = int(content_size)
169 size = int(content_size)
170 for path, dirs, files in os.walk(repo_path):
170 for path, dirs, files in os.walk(repo_path):
171 if path.find('.hg') == -1:
171 if path.find('.hg') == -1:
172 for f in files:
172 for f in files:
173 size += os.path.getsize(os.path.join(path, f))
173 size += os.path.getsize(os.path.join(path, f))
174 return size
174 return size
175 return h.format_byte_size(size)
175 return h.format_byte_size(size)
176
176
177 def __get_action(self, environ):
177 def __get_action(self, environ):
178 """
178 """
179 Maps mercurial request commands into a pull or push command.
179 Maps mercurial request commands into a pull or push command.
180 @param environ:
180 @param environ:
181 """
181 """
182 mapping = {'changegroup': 'pull',
182 mapping = {'changegroup': 'pull',
183 'changegroupsubset': 'pull',
183 'changegroupsubset': 'pull',
184 'stream_out': 'pull',
184 'stream_out': 'pull',
185 'listkeys': 'pull',
185 'listkeys': 'pull',
186 'unbundle': 'push',
186 'unbundle': 'push',
187 'pushkey': 'push', }
187 'pushkey': 'push', }
188
188
189 for qry in environ['QUERY_STRING'].split('&'):
189 for qry in environ['QUERY_STRING'].split('&'):
190 if qry.startswith('cmd'):
190 if qry.startswith('cmd'):
191 cmd = qry.split('=')[-1]
191 cmd = qry.split('=')[-1]
192 if mapping.has_key(cmd):
192 if mapping.has_key(cmd):
193 return mapping[cmd]
193 return mapping[cmd]
194
194
195 def __log_user_action(self, user, action, repo, ipaddr):
195 def __log_user_action(self, user, action, repo, ipaddr):
196 sa = meta.Session
196 sa = meta.Session
197 try:
197 try:
198 user_log = UserLog()
198 user_log = UserLog()
199 user_log.user_id = user.user_id
199 user_log.user_id = user.user_id
200 user_log.action = action
200 user_log.action = action
201 user_log.repository = repo.replace('/', '')
201 user_log.repository = repo.replace('/', '')
202 user_log.action_date = datetime.now()
202 user_log.action_date = datetime.now()
203 user_log.user_ip = ipaddr
203 user_log.user_ip = ipaddr
204 sa.add(user_log)
204 sa.add(user_log)
205 sa.commit()
205 sa.commit()
206 log.info('Adding user %s, action %s on %s',
206 log.info('Adding user %s, action %s on %s',
207 user.username, action, repo)
207 user.username, action, repo)
208 except Exception as e:
208 except Exception as e:
209 sa.rollback()
209 sa.rollback()
210 log.error('could not log user action:%s', str(e))
210 log.error('could not log user action:%s', str(e))
211 finally:
211 finally:
212 meta.Session.remove()
212 meta.Session.remove()
213
213
214 def __invalidate_cache(self, repo_name):
214 def __invalidate_cache(self, repo_name):
215 """we know that some change was made to repositories and we should
215 """we know that some change was made to repositories and we should
216 invalidate the cache to see the changes right away but only for
216 invalidate the cache to see the changes right away but only for
217 push requests"""
217 push requests"""
218 invalidate_cache('cached_repo_list')
218 invalidate_cache('cached_repo_list')
219 invalidate_cache('full_changelog', repo_name)
219 invalidate_cache('full_changelog', repo_name)
220
220
221
221
222 def __load_web_settings(self, hgserve):
222 def __load_web_settings(self, hgserve):
223 #set the global ui for hgserve
223 #set the global ui for hgserve
224 hgserve.repo.ui = self.baseui
224 hgserve.repo.ui = self.baseui
225
225
226 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
226 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
227 repoui = make_ui('file', hgrc, False)
227 repoui = make_ui('file', hgrc, False)
228
228
229
229 if repoui:
230 if repoui:
230 #set the repository based config
231 #overwrite our ui instance with the section from hgrc file
231 hgserve.repo.ui = repoui
232 for section in ui_sections:
233 for k, v in repoui.configitems(section):
234 hgserve.repo.ui.setconfig(section, k, v)
232
235
233 return hgserve
236 return hgserve
237
238
239
240
241
242
243
244
245
246
247
248
249
250
General Comments 0
You need to be logged in to leave comments. Login now