comparison rhodecode/lib/middleware/simplehg.py @ 605:72bed56219d6

security bugfix simplehg wasn't checking for permissions on remote commands different than pull or push.
author Marcin Kuzminski <marcin@python-works.com>
date Mon, 18 Oct 2010 03:06:38 +0200
parents f99075170eb4
children f31f1327c1e9
comparison
equal deleted inserted replaced
604:5cc96df705b9 605:72bed56219d6
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 #===================================================================
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:
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 if action == 'pull': 90
91 if action == 'push':
92 if not HasPermissionAnyMiddleware('repository.write',
93 'repository.admin')\
94 (user, repo_name):
95 return HTTPForbidden()(environ, start_response)
96
97 else:
91 if not HasPermissionAnyMiddleware('repository.read', 98 if not HasPermissionAnyMiddleware('repository.read',
92 'repository.write', 99 'repository.write',
93 'repository.admin')\ 100 'repository.admin')\
94 (user, repo_name): 101 (user, repo_name):
95 return HTTPForbidden()(environ, start_response) 102 return HTTPForbidden()(environ, start_response)
96 if action == 'push': 103
97 if not HasPermissionAnyMiddleware('repository.write',
98 'repository.admin')\
99 (user, repo_name):
100 return HTTPForbidden()(environ, start_response)
101
102 #log action 104 #log action
103 proxy_key = 'HTTP_X_REAL_IP' 105 proxy_key = 'HTTP_X_REAL_IP'
104 def_key = 'REMOTE_ADDR' 106 def_key = 'REMOTE_ADDR'
105 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0')) 107 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
106 self.__log_user_action(user, action, repo_name, ipaddr) 108 self.__log_user_action(user, action, repo_name, ipaddr)
107 109
108 #=================================================================== 110 #===================================================================
109 # MERCURIAL REQUEST HANDLING 111 # MERCURIAL REQUEST HANDLING
110 #=================================================================== 112 #===================================================================
111 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path 113 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
112 self.baseui = make_ui('db') 114 self.baseui = make_ui('db')
122 if str(e).find('not found') != -1: 124 if str(e).find('not found') != -1:
123 return HTTPNotFound()(environ, start_response) 125 return HTTPNotFound()(environ, start_response)
124 except Exception: 126 except Exception:
125 log.error(traceback.format_exc()) 127 log.error(traceback.format_exc())
126 return HTTPInternalServerError()(environ, start_response) 128 return HTTPInternalServerError()(environ, start_response)
127 129
128 #invalidate cache on push 130 #invalidate cache on push
129 if action == 'push': 131 if action == 'push':
130 self.__invalidate_cache(repo_name) 132 self.__invalidate_cache(repo_name)
131 messages = [] 133 messages = []
132 messages.append('thank you for using rhodecode') 134 messages.append('thank you for using rhodecode')
133 135
134 return self.msg_wrapper(app, environ, start_response, messages) 136 return self.msg_wrapper(app, environ, start_response, messages)
135 else: 137 else:
136 return app(environ, start_response) 138 return app(environ, start_response)
137 139
138 140
139 def msg_wrapper(self, app, environ, start_response, messages=[]): 141 def msg_wrapper(self, app, environ, start_response, messages=[]):
140 """ 142 """
141 Wrapper for custom messages that come out of mercurial respond messages 143 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 144 is a list of messages that the user will see at the end of response
143 from merurial protocol actions that involves remote answers 145 from merurial protocol actions that involves remote answers
144 @param app: 146 :param app:
145 @param environ: 147 :param environ:
146 @param start_response: 148 :param start_response:
147 """ 149 """
148 def custom_messages(msg_list): 150 def custom_messages(msg_list):
149 for msg in msg_list: 151 for msg in msg_list:
150 yield msg + '\n' 152 yield msg + '\n'
151 org_response = app(environ, start_response) 153 org_response = app(environ, start_response)
152 return chain(org_response, custom_messages(messages)) 154 return chain(org_response, custom_messages(messages))
153 155
154 def __make_app(self): 156 def __make_app(self):
155 hgserve = hgweb(str(self.repo_path), baseui=self.baseui) 157 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
156 return self.__load_web_settings(hgserve) 158 return self.__load_web_settings(hgserve)
157 159
158 def __get_environ_user(self, environ): 160 def __get_environ_user(self, environ):
159 return environ.get('REMOTE_USER') 161 return environ.get('REMOTE_USER')
160 162
161 def __get_user(self, username): 163 def __get_user(self, username):
162 return get_user_cached(username) 164 return get_user_cached(username)
163 165
164 def __get_action(self, environ): 166 def __get_action(self, environ):
165 """ 167 """
166 Maps mercurial request commands into a pull or push command. 168 Maps mercurial request commands into a pull or push command.
167 @param environ: 169 This should return generally always something
170 :param environ:
168 """ 171 """
169 mapping = {'changegroup': 'pull', 172 mapping = {'changegroup': 'pull',
170 'changegroupsubset': 'pull', 173 'changegroupsubset': 'pull',
171 'stream_out': 'pull', 174 'stream_out': 'pull',
172 'listkeys': 'pull', 175 'listkeys': 'pull',
173 'unbundle': 'push', 176 'unbundle': 'push',
174 'pushkey': 'push', } 177 'pushkey': 'push', }
175
176 for qry in environ['QUERY_STRING'].split('&'): 178 for qry in environ['QUERY_STRING'].split('&'):
177 if qry.startswith('cmd'): 179 if qry.startswith('cmd'):
178 cmd = qry.split('=')[-1] 180 cmd = qry.split('=')[-1]
179 if mapping.has_key(cmd): 181 if mapping.has_key(cmd):
180 return mapping[cmd] 182 return mapping[cmd]
181 183 else:
184 return cmd
185
182 def __log_user_action(self, user, action, repo, ipaddr): 186 def __log_user_action(self, user, action, repo, ipaddr):
183 action_logger(user, action, repo, ipaddr) 187 action_logger(user, action, repo, ipaddr)
184 188
185 def __invalidate_cache(self, repo_name): 189 def __invalidate_cache(self, repo_name):
186 """we know that some change was made to repositories and we should 190 """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 191 invalidate the cache to see the changes right away but only for
188 push requests""" 192 push requests"""
189 invalidate_cache('cached_repo_list') 193 invalidate_cache('cached_repo_list')
190 invalidate_cache('full_changelog', repo_name) 194 invalidate_cache('full_changelog', repo_name)
191 195
192 196
193 def __load_web_settings(self, hgserve): 197 def __load_web_settings(self, hgserve):
194 #set the global ui for hgserve 198 #set the global ui for hgserve instance passed
195 hgserve.repo.ui = self.baseui 199 hgserve.repo.ui = self.baseui
196 200
197 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc') 201 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
198 repoui = make_ui('file', hgrc, False) 202 repoui = make_ui('file', hgrc, False)
199 203
200 204
201 if repoui: 205 if repoui:
202 #overwrite our ui instance with the section from hgrc file 206 #overwrite our ui instance with the section from hgrc file
203 for section in ui_sections: 207 for section in ui_sections:
204 for k, v in repoui.configitems(section): 208 for k, v in repoui.configitems(section):
205 hgserve.repo.ui.setconfig(section, k, v) 209 hgserve.repo.ui.setconfig(section, k, v)
206 210
207 return hgserve 211 return hgserve
208 212
209 213
210 214
211 215
212 216
213 217
214 218
215 219
216 220
217 221
218 222
219 223
220 224
221 225