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