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