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