##// END OF EJS Templates
#286 raise HttpForbidden if username lookup failed instead of internal server error
marcink -
r1595:7cd8fd4d beta
parent child Browse files
Show More
@@ -1,289 +1,291 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplegit
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 7 It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 30
31 31 from dulwich import server as dulserver
32 32
33 33
34 34 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35 35
36 36 def handle(self):
37 37 write = lambda x: self.proto.write_sideband(1, x)
38 38
39 39 graph_walker = dulserver.ProtocolGraphWalker(self,
40 40 self.repo.object_store,
41 41 self.repo.get_peeled)
42 42 objects_iter = self.repo.fetch_objects(
43 43 graph_walker.determine_wants, graph_walker, self.progress,
44 44 get_tagged=self.get_tagged)
45 45
46 46 # Do they want any objects?
47 47 if objects_iter is None or len(objects_iter) == 0:
48 48 return
49 49
50 50 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 51 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
52 52 objects_iter, len(objects_iter))
53 53 messages = []
54 54 messages.append('thank you for using rhodecode')
55 55
56 56 for msg in messages:
57 57 self.progress(msg + "\n")
58 58 # we are done
59 59 self.proto.write("0000")
60 60
61 61 dulserver.DEFAULT_HANDLERS = {
62 62 'git-upload-pack': SimpleGitUploadPackHandler,
63 63 'git-receive-pack': dulserver.ReceivePackHandler,
64 64 }
65 65
66 66 from dulwich.repo import Repo
67 67 from dulwich.web import HTTPGitApplication
68 68
69 69 from paste.auth.basic import AuthBasicAuthenticator
70 70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71 71
72 72 from rhodecode.lib import safe_str
73 73 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
74 74 from rhodecode.lib.utils import invalidate_cache, is_valid_repo
75 75 from rhodecode.model.db import User
76 76
77 77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
78 78
79 79 log = logging.getLogger(__name__)
80 80
81 81
82 82 def is_git(environ):
83 83 """Returns True if request's target is git server.
84 84 ``HTTP_USER_AGENT`` would then have git client version given.
85 85
86 86 :param environ:
87 87 """
88 88 http_user_agent = environ.get('HTTP_USER_AGENT')
89 89 if http_user_agent and http_user_agent.startswith('git'):
90 90 return True
91 91 return False
92 92
93 93
94 94 class SimpleGit(object):
95 95
96 96 def __init__(self, application, config):
97 97 self.application = application
98 98 self.config = config
99 99 # base path of repo locations
100 100 self.basepath = self.config['base_path']
101 101 #authenticate this mercurial request using authfunc
102 102 self.authenticate = AuthBasicAuthenticator('', authfunc)
103 103
104 104 def __call__(self, environ, start_response):
105 105 if not is_git(environ):
106 106 return self.application(environ, start_response)
107 107
108 108 proxy_key = 'HTTP_X_REAL_IP'
109 109 def_key = 'REMOTE_ADDR'
110 110 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
111 111 username = None
112 112 # skip passing error to error controller
113 113 environ['pylons.status_code_redirect'] = True
114 114
115 115 #======================================================================
116 116 # EXTRACT REPOSITORY NAME FROM ENV
117 117 #======================================================================
118 118 try:
119 119 repo_name = self.__get_repository(environ)
120 120 log.debug('Extracted repo name is %s' % repo_name)
121 121 except:
122 122 return HTTPInternalServerError()(environ, start_response)
123 123
124 124 #======================================================================
125 125 # GET ACTION PULL or PUSH
126 126 #======================================================================
127 127 action = self.__get_action(environ)
128 128
129 129 #======================================================================
130 130 # CHECK ANONYMOUS PERMISSION
131 131 #======================================================================
132 132 if action in ['pull', 'push']:
133 133 anonymous_user = self.__get_user('default')
134 134 username = anonymous_user.username
135 135 anonymous_perm = self.__check_permission(action,
136 136 anonymous_user,
137 137 repo_name)
138 138
139 139 if anonymous_perm is not True or anonymous_user.active is False:
140 140 if anonymous_perm is not True:
141 141 log.debug('Not enough credentials to access this '
142 142 'repository as anonymous user')
143 143 if anonymous_user.active is False:
144 144 log.debug('Anonymous access is disabled, running '
145 145 'authentication')
146 146 #==============================================================
147 147 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
148 148 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
149 149 #==============================================================
150 150
151 151 if not REMOTE_USER(environ):
152 152 self.authenticate.realm = \
153 153 safe_str(self.config['rhodecode_realm'])
154 154 result = self.authenticate(environ)
155 155 if isinstance(result, str):
156 156 AUTH_TYPE.update(environ, 'basic')
157 157 REMOTE_USER.update(environ, result)
158 158 else:
159 159 return result.wsgi_application(environ, start_response)
160 160
161 161 #==============================================================
162 162 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
163 163 # BASIC AUTH
164 164 #==============================================================
165 165
166 166 if action in ['pull', 'push']:
167 167 username = REMOTE_USER(environ)
168 168 try:
169 169 user = self.__get_user(username)
170 if user is None:
171 return HTTPForbidden()(environ, start_response)
170 172 username = user.username
171 173 except:
172 174 log.error(traceback.format_exc())
173 175 return HTTPInternalServerError()(environ,
174 176 start_response)
175 177
176 178 #check permissions for this repository
177 179 perm = self.__check_permission(action, user,
178 180 repo_name)
179 181 if perm is not True:
180 182 return HTTPForbidden()(environ, start_response)
181 183
182 184 extras = {'ip': ipaddr,
183 185 'username': username,
184 186 'action': action,
185 187 'repository': repo_name}
186 188
187 189 #===================================================================
188 190 # GIT REQUEST HANDLING
189 191 #===================================================================
190 192
191 193 repo_path = safe_str(os.path.join(self.basepath, repo_name))
192 194 log.debug('Repository path is %s' % repo_path)
193 195
194 196 # quick check if that dir exists...
195 197 if is_valid_repo(repo_name, self.basepath) is False:
196 198 return HTTPNotFound()(environ, start_response)
197 199
198 200 try:
199 201 #invalidate cache on push
200 202 if action == 'push':
201 203 self.__invalidate_cache(repo_name)
202 204
203 205 app = self.__make_app(repo_name, repo_path)
204 206 return app(environ, start_response)
205 207 except Exception:
206 208 log.error(traceback.format_exc())
207 209 return HTTPInternalServerError()(environ, start_response)
208 210
209 211 def __make_app(self, repo_name, repo_path):
210 212 """
211 213 Make an wsgi application using dulserver
212 214
213 215 :param repo_name: name of the repository
214 216 :param repo_path: full path to the repository
215 217 """
216 218
217 219 _d = {'/' + repo_name: Repo(repo_path)}
218 220 backend = dulserver.DictBackend(_d)
219 221 gitserve = HTTPGitApplication(backend)
220 222
221 223 return gitserve
222 224
223 225 def __check_permission(self, action, user, repo_name):
224 226 """
225 227 Checks permissions using action (push/pull) user and repository
226 228 name
227 229
228 230 :param action: push or pull action
229 231 :param user: user instance
230 232 :param repo_name: repository name
231 233 """
232 234 if action == 'push':
233 235 if not HasPermissionAnyMiddleware('repository.write',
234 236 'repository.admin')(user,
235 237 repo_name):
236 238 return False
237 239
238 240 else:
239 241 #any other action need at least read permission
240 242 if not HasPermissionAnyMiddleware('repository.read',
241 243 'repository.write',
242 244 'repository.admin')(user,
243 245 repo_name):
244 246 return False
245 247
246 248 return True
247 249
248 250 def __get_repository(self, environ):
249 251 """
250 252 Get's repository name out of PATH_INFO header
251 253
252 254 :param environ: environ where PATH_INFO is stored
253 255 """
254 256 try:
255 257 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
256 258 if repo_name.endswith('/'):
257 259 repo_name = repo_name.rstrip('/')
258 260 except:
259 261 log.error(traceback.format_exc())
260 262 raise
261 263 repo_name = repo_name.split('/')[0]
262 264 return repo_name
263 265
264 266 def __get_user(self, username):
265 267 return User.get_by_username(username)
266 268
267 269 def __get_action(self, environ):
268 270 """Maps git request commands into a pull or push command.
269 271
270 272 :param environ:
271 273 """
272 274 service = environ['QUERY_STRING'].split('=')
273 275 if len(service) > 1:
274 276 service_cmd = service[1]
275 277 mapping = {'git-receive-pack': 'push',
276 278 'git-upload-pack': 'pull',
277 279 }
278 280
279 281 return mapping.get(service_cmd,
280 282 service_cmd if service_cmd else 'other')
281 283 else:
282 284 return 'other'
283 285
284 286 def __invalidate_cache(self, repo_name):
285 287 """we know that some change was made to repositories and we should
286 288 invalidate the cache to see the changes right away but only for
287 289 push requests"""
288 290 invalidate_cache('get_repo_cached_%s' % repo_name)
289 291
@@ -1,288 +1,290 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.middleware.simplehg
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 SimpleHG middleware for handling mercurial protocol request
7 7 (push/clone etc.). It's implemented with basic auth function
8 8
9 9 :created_on: Apr 28, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26
27 27 import os
28 28 import logging
29 29 import traceback
30 30
31 31 from mercurial.error import RepoError
32 32 from mercurial.hgweb import hgweb_mod
33 33
34 34 from paste.auth.basic import AuthBasicAuthenticator
35 35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36 36
37 37 from rhodecode.lib import safe_str
38 38 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
39 39 from rhodecode.lib.utils import make_ui, invalidate_cache, \
40 40 is_valid_repo, ui_sections
41 41 from rhodecode.model.db import User
42 42
43 43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 def is_mercurial(environ):
49 49 """Returns True if request's target is mercurial server - header
50 50 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 51 """
52 52 http_accept = environ.get('HTTP_ACCEPT')
53 53 if http_accept and http_accept.startswith('application/mercurial'):
54 54 return True
55 55 return False
56 56
57 57
58 58 class SimpleHg(object):
59 59
60 60 def __init__(self, application, config):
61 61 self.application = application
62 62 self.config = config
63 63 # base path of repo locations
64 64 self.basepath = self.config['base_path']
65 65 #authenticate this mercurial request using authfunc
66 66 self.authenticate = AuthBasicAuthenticator('', authfunc)
67 67 self.ipaddr = '0.0.0.0'
68 68
69 69 def __call__(self, environ, start_response):
70 70 if not is_mercurial(environ):
71 71 return self.application(environ, start_response)
72 72
73 73 proxy_key = 'HTTP_X_REAL_IP'
74 74 def_key = 'REMOTE_ADDR'
75 75 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
76 76
77 77 # skip passing error to error controller
78 78 environ['pylons.status_code_redirect'] = True
79 79
80 80 #======================================================================
81 81 # EXTRACT REPOSITORY NAME FROM ENV
82 82 #======================================================================
83 83 try:
84 84 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
85 85 log.debug('Extracted repo name is %s' % repo_name)
86 86 except:
87 87 return HTTPInternalServerError()(environ, start_response)
88 88
89 89 #======================================================================
90 90 # GET ACTION PULL or PUSH
91 91 #======================================================================
92 92 action = self.__get_action(environ)
93 93
94 94 #======================================================================
95 95 # CHECK ANONYMOUS PERMISSION
96 96 #======================================================================
97 97 if action in ['pull', 'push']:
98 98 anonymous_user = self.__get_user('default')
99 99
100 100 username = anonymous_user.username
101 101 anonymous_perm = self.__check_permission(action,
102 102 anonymous_user,
103 103 repo_name)
104 104
105 105 if anonymous_perm is not True or anonymous_user.active is False:
106 106 if anonymous_perm is not True:
107 107 log.debug('Not enough credentials to access this '
108 108 'repository as anonymous user')
109 109 if anonymous_user.active is False:
110 110 log.debug('Anonymous access is disabled, running '
111 111 'authentication')
112 112 #==============================================================
113 113 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
114 114 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
115 115 #==============================================================
116 116
117 117 if not REMOTE_USER(environ):
118 118 self.authenticate.realm = \
119 119 safe_str(self.config['rhodecode_realm'])
120 120 result = self.authenticate(environ)
121 121 if isinstance(result, str):
122 122 AUTH_TYPE.update(environ, 'basic')
123 123 REMOTE_USER.update(environ, result)
124 124 else:
125 125 return result.wsgi_application(environ, start_response)
126 126
127 127 #==============================================================
128 128 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
129 129 # BASIC AUTH
130 130 #==============================================================
131 131
132 132 if action in ['pull', 'push']:
133 133 username = REMOTE_USER(environ)
134 134 try:
135 135 user = self.__get_user(username)
136 if user is None:
137 return HTTPForbidden()(environ, start_response)
136 138 username = user.username
137 139 except:
138 140 log.error(traceback.format_exc())
139 141 return HTTPInternalServerError()(environ,
140 142 start_response)
141 143
142 144 #check permissions for this repository
143 145 perm = self.__check_permission(action, user,
144 146 repo_name)
145 147 if perm is not True:
146 148 return HTTPForbidden()(environ, start_response)
147 149
148 150 extras = {'ip': ipaddr,
149 151 'username': username,
150 152 'action': action,
151 153 'repository': repo_name}
152 154
153 155 #======================================================================
154 156 # MERCURIAL REQUEST HANDLING
155 157 #======================================================================
156 158
157 159 repo_path = safe_str(os.path.join(self.basepath, repo_name))
158 160 log.debug('Repository path is %s' % repo_path)
159 161
160 162 baseui = make_ui('db')
161 163 self.__inject_extras(repo_path, baseui, extras)
162 164
163 165
164 166 # quick check if that dir exists...
165 167 if is_valid_repo(repo_name, self.basepath) is False:
166 168 return HTTPNotFound()(environ, start_response)
167 169
168 170 try:
169 171 #invalidate cache on push
170 172 if action == 'push':
171 173 self.__invalidate_cache(repo_name)
172 174
173 175 app = self.__make_app(repo_path, baseui, extras)
174 176 return app(environ, start_response)
175 177 except RepoError, e:
176 178 if str(e).find('not found') != -1:
177 179 return HTTPNotFound()(environ, start_response)
178 180 except Exception:
179 181 log.error(traceback.format_exc())
180 182 return HTTPInternalServerError()(environ, start_response)
181 183
182 184 def __make_app(self, repo_name, baseui, extras):
183 185 """
184 186 Make an wsgi application using hgweb, and inject generated baseui
185 187 instance, additionally inject some extras into ui object
186 188 """
187 189 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
188 190
189 191
190 192 def __check_permission(self, action, user, repo_name):
191 193 """
192 194 Checks permissions using action (push/pull) user and repository
193 195 name
194 196
195 197 :param action: push or pull action
196 198 :param user: user instance
197 199 :param repo_name: repository name
198 200 """
199 201 if action == 'push':
200 202 if not HasPermissionAnyMiddleware('repository.write',
201 203 'repository.admin')(user,
202 204 repo_name):
203 205 return False
204 206
205 207 else:
206 208 #any other action need at least read permission
207 209 if not HasPermissionAnyMiddleware('repository.read',
208 210 'repository.write',
209 211 'repository.admin')(user,
210 212 repo_name):
211 213 return False
212 214
213 215 return True
214 216
215 217 def __get_repository(self, environ):
216 218 """
217 219 Get's repository name out of PATH_INFO header
218 220
219 221 :param environ: environ where PATH_INFO is stored
220 222 """
221 223 try:
222 224 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
223 225 if repo_name.endswith('/'):
224 226 repo_name = repo_name.rstrip('/')
225 227 except:
226 228 log.error(traceback.format_exc())
227 229 raise
228 230
229 231 return repo_name
230 232
231 233 def __get_user(self, username):
232 234 return User.get_by_username(username)
233 235
234 236 def __get_action(self, environ):
235 237 """
236 238 Maps mercurial request commands into a clone,pull or push command.
237 239 This should always return a valid command string
238 240
239 241 :param environ:
240 242 """
241 243 mapping = {'changegroup': 'pull',
242 244 'changegroupsubset': 'pull',
243 245 'stream_out': 'pull',
244 246 'listkeys': 'pull',
245 247 'unbundle': 'push',
246 248 'pushkey': 'push', }
247 249 for qry in environ['QUERY_STRING'].split('&'):
248 250 if qry.startswith('cmd'):
249 251 cmd = qry.split('=')[-1]
250 252 if cmd in mapping:
251 253 return mapping[cmd]
252 254 else:
253 255 return 'pull'
254 256
255 257 def __invalidate_cache(self, repo_name):
256 258 """we know that some change was made to repositories and we should
257 259 invalidate the cache to see the changes right away but only for
258 260 push requests"""
259 261 invalidate_cache('get_repo_cached_%s' % repo_name)
260 262
261 263 def __inject_extras(self, repo_path, baseui, extras={}):
262 264 """
263 265 Injects some extra params into baseui instance
264 266
265 267 also overwrites global settings with those takes from local hgrc file
266 268
267 269 :param baseui: baseui instance
268 270 :param extras: dict with extra params to put into baseui
269 271 """
270 272
271 273 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
272 274
273 275 # make our hgweb quiet so it doesn't print output
274 276 baseui.setconfig('ui', 'quiet', 'true')
275 277
276 278 #inject some additional parameters that will be available in ui
277 279 #for hooks
278 280 for k, v in extras.items():
279 281 baseui.setconfig('rhodecode_extras', k, v)
280 282
281 283 repoui = make_ui('file', hgrc, False)
282 284
283 285 if repoui:
284 286 #overwrite our ui instance with the section from hgrc file
285 287 for section in ui_sections:
286 288 for k, v in repoui.configitems(section):
287 289 baseui.setconfig(section, k, v)
288 290
General Comments 0
You need to be logged in to leave comments. Login now