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