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