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