Mercurial > kallithea
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 |