comparison rhodecode/lib/middleware/simplehg.py @ 547:1e757ac98988

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