##// END OF EJS Templates
fixes #97 in simplehg and simplegit, force casting to headers
marcink -
r918:b2d5868c beta
parent child Browse files
Show More
@@ -1,226 +1,277
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
15 15 # modify it under the terms of the GNU General Public License
16 16 # as published by the Free Software Foundation; version 2
17 17 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
26 26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 27 # MA 02110-1301, USA.
28 28
29 29 import os
30 30 import logging
31 31 import traceback
32 32
33 33 from dulwich import server as dulserver
34 34
35 35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
36 36
37 37 def handle(self):
38 38 write = lambda x: self.proto.write_sideband(1, x)
39 39
40 40 graph_walker = dulserver.ProtocolGraphWalker(self, 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 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_data(dulserver.ProtocolFile(None, write), objects_iter,
52 52 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.auth import authfunc, HasPermissionAnyMiddleware
73 73 from rhodecode.lib.utils import invalidate_cache, check_repo_fast
74 74 from rhodecode.model.user import UserModel
75 75
76 76 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
77 77
78 78 log = logging.getLogger(__name__)
79 79
80 80 def is_git(environ):
81 """Returns True if request's target is git server. ``HTTP_USER_AGENT`` would
82 then have git client version given.
81 """Returns True if request's target is git server.
82 ``HTTP_USER_AGENT`` would then have git client version given.
83 83
84 84 :param environ:
85 85 """
86 86 http_user_agent = environ.get('HTTP_USER_AGENT')
87 87 if http_user_agent and http_user_agent.startswith('git'):
88 88 return True
89 89 return False
90 90
91 91 class SimpleGit(object):
92 92
93 93 def __init__(self, application, config):
94 94 self.application = application
95 95 self.config = config
96 96 #authenticate this git request using
97 97 self.authenticate = AuthBasicAuthenticator('', authfunc)
98 98 self.ipaddr = '0.0.0.0'
99 99 self.repository = None
100 100 self.username = None
101 101 self.action = None
102 102
103 103 def __call__(self, environ, start_response):
104 104 if not is_git(environ):
105 105 return self.application(environ, start_response)
106 106
107 107 proxy_key = 'HTTP_X_REAL_IP'
108 108 def_key = 'REMOTE_ADDR'
109 109 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
110 110 # skip passing error to error controller
111 111 environ['pylons.status_code_redirect'] = True
112 #===================================================================
113 # AUTHENTICATE THIS GIT REQUEST
114 #===================================================================
115 username = REMOTE_USER(environ)
116 if not username:
117 self.authenticate.realm = self.config['rhodecode_realm']
118 result = self.authenticate(environ)
119 if isinstance(result, str):
120 AUTH_TYPE.update(environ, 'basic')
121 REMOTE_USER.update(environ, result)
122 else:
123 return result.wsgi_application(environ, start_response)
124 112
125 #=======================================================================
126 # GET REPOSITORY
127 #=======================================================================
113 #======================================================================
114 # GET ACTION PULL or PUSH
115 #======================================================================
116 self.action = self.__get_action(environ)
128 117 try:
129 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
130 if repo_name.endswith('/'):
131 repo_name = repo_name.rstrip('/')
132 self.repository = repo_name
118 #==================================================================
119 # GET REPOSITORY NAME
120 #==================================================================
121 self.repo_name = self.__get_repository(environ)
133 122 except:
134 log.error(traceback.format_exc())
135 123 return HTTPInternalServerError()(environ, start_response)
136 124
137 #===================================================================
138 # CHECK PERMISSIONS FOR THIS REQUEST
139 #===================================================================
140 self.action = self.__get_action(environ)
141 if self.action:
142 username = self.__get_environ_user(environ)
143 try:
144 user = self.__get_user(username)
145 self.username = user.username
146 except:
147 log.error(traceback.format_exc())
148 return HTTPInternalServerError()(environ, start_response)
125 #======================================================================
126 # CHECK ANONYMOUS PERMISSION
127 #======================================================================
128 if self.action in ['pull', 'push'] or self.action:
129 anonymous_user = self.__get_user('default')
130 self.username = anonymous_user.username
131 anonymous_perm = self.__check_permission(self.action, anonymous_user ,
132 self.repo_name)
133
134 if anonymous_perm is not True or anonymous_user.active is False:
135 if anonymous_perm is not True:
136 log.debug('Not enough credentials to access this repository'
137 'as anonymous user')
138 if anonymous_user.active is False:
139 log.debug('Anonymous access is disabled, running '
140 'authentication')
141 #==============================================================
142 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
143 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
144 #==============================================================
149 145
150 #check permissions for this repository
151 if self.action == 'push':
152 if not HasPermissionAnyMiddleware('repository.write',
153 'repository.admin')\
154 (user, repo_name):
155 return HTTPForbidden()(environ, start_response)
146 if not REMOTE_USER(environ):
147 self.authenticate.realm = str(self.config['rhodecode_realm'])
148 result = self.authenticate(environ)
149 if isinstance(result, str):
150 AUTH_TYPE.update(environ, 'basic')
151 REMOTE_USER.update(environ, result)
152 else:
153 return result.wsgi_application(environ, start_response)
154
155
156 #==============================================================
157 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
158 # BASIC AUTH
159 #==============================================================
156 160
157 else:
158 #any other action need at least read permission
159 if not HasPermissionAnyMiddleware('repository.read',
160 'repository.write',
161 'repository.admin')\
162 (user, repo_name):
163 return HTTPForbidden()(environ, start_response)
161 if self.action in ['pull', 'push'] or self.action:
162 username = self.__get_environ_user(environ)
163 try:
164 user = self.__get_user(username)
165 self.username = user.username
166 except:
167 log.error(traceback.format_exc())
168 return HTTPInternalServerError()(environ, start_response)
169
170 #check permissions for this repository
171 perm = self.__check_permission(self.action, user, self.repo_name)
172 if perm is not True:
173 print 'not allowed'
174 return HTTPForbidden()(environ, start_response)
164 175
165 176 self.extras = {'ip':self.ipaddr,
166 177 'username':self.username,
167 178 'action':self.action,
168 'repository':self.repository}
179 'repository':self.repo_name}
169 180
170 181 #===================================================================
171 182 # GIT REQUEST HANDLING
172 183 #===================================================================
173 184 self.basepath = self.config['base_path']
174 185 self.repo_path = os.path.join(self.basepath, self.repo_name)
175 186 #quick check if that dir exists...
176 187 if check_repo_fast(self.repo_name, self.basepath):
177 188 return HTTPNotFound()(environ, start_response)
178 189 try:
179 190 app = self.__make_app()
180 191 except:
181 192 log.error(traceback.format_exc())
182 193 return HTTPInternalServerError()(environ, start_response)
183 194
184 195 #invalidate cache on push
185 196 if self.action == 'push':
186 197 self.__invalidate_cache(self.repo_name)
187 198 messages = []
188 199 messages.append('thank you for using rhodecode')
189 200 return app(environ, start_response)
190 201 else:
191 202 return app(environ, start_response)
192 203
193 204
194 205 def __make_app(self):
195 206 backend = dulserver.DictBackend({'/' + self.repo_name: Repo(self.repo_path)})
196 207 gitserve = HTTPGitApplication(backend)
197 208
198 209 return gitserve
199 210
211 def __check_permission(self, action, user, repo_name):
212 """Checks permissions using action (push/pull) user and repository
213 name
214
215 :param action: push or pull action
216 :param user: user instance
217 :param repo_name: repository name
218 """
219 if action == 'push':
220 if not HasPermissionAnyMiddleware('repository.write',
221 'repository.admin')\
222 (user, repo_name):
223 return False
224
225 else:
226 #any other action need at least read permission
227 if not HasPermissionAnyMiddleware('repository.read',
228 'repository.write',
229 'repository.admin')\
230 (user, repo_name):
231 return False
232
233 return True
234
235
236 def __get_repository(self, environ):
237 """Get's repository name out of PATH_INFO header
238
239 :param environ: environ where PATH_INFO is stored
240 """
241 try:
242 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
243 if repo_name.endswith('/'):
244 repo_name = repo_name.rstrip('/')
245 except:
246 log.error(traceback.format_exc())
247 raise
248 repo_name = repo_name.split('/')[0]
249 return repo_name
250
200 251 def __get_environ_user(self, environ):
201 252 return environ.get('REMOTE_USER')
202 253
203 254 def __get_user(self, username):
204 255 return UserModel().get_by_username(username, cache=True)
205 256
206 257 def __get_action(self, environ):
207 258 """Maps git request commands into a pull or push command.
208 259
209 260 :param environ:
210 261 """
211 262 service = environ['QUERY_STRING'].split('=')
212 263 if len(service) > 1:
213 264 service_cmd = service[1]
214 265 mapping = {'git-receive-pack': 'push',
215 266 'git-upload-pack': 'pull',
216 267 }
217 268
218 269 return mapping.get(service_cmd, service_cmd if service_cmd else 'other')
219 270 else:
220 271 return 'other'
221 272
222 273 def __invalidate_cache(self, repo_name):
223 274 """we know that some change was made to repositories and we should
224 275 invalidate the cache to see the changes right away but only for
225 276 push requests"""
226 277 invalidate_cache('get_repo_cached_%s' % repo_name)
@@ -1,275 +1,275
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
15 15 # modify it under the terms of the GNU General Public License
16 16 # as published by the Free Software Foundation; version 2
17 17 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
26 26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 27 # MA 02110-1301, USA.
28 28
29 29 import os
30 30 import logging
31 31 import traceback
32 32
33 33 from mercurial.error import RepoError
34 34 from mercurial.hgweb import hgweb
35 35 from mercurial.hgweb.request import wsgiapplication
36 36
37 37 from paste.auth.basic import AuthBasicAuthenticator
38 38 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
39 39
40 40 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
41 41 from rhodecode.lib.utils import make_ui, invalidate_cache, \
42 42 check_repo_fast, ui_sections
43 43 from rhodecode.model.user import UserModel
44 44
45 45 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49 def is_mercurial(environ):
50 50 """Returns True if request's target is mercurial server - header
51 51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 52 """
53 53 http_accept = environ.get('HTTP_ACCEPT')
54 54 if http_accept and http_accept.startswith('application/mercurial'):
55 55 return True
56 56 return False
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 #authenticate this mercurial request using authfunc
64 64 self.authenticate = AuthBasicAuthenticator('', authfunc)
65 65 self.ipaddr = '0.0.0.0'
66 66 self.repo_name = None
67 67 self.username = None
68 68 self.action = None
69 69
70 70 def __call__(self, environ, start_response):
71 71 if not is_mercurial(environ):
72 72 return self.application(environ, start_response)
73 73
74 74 proxy_key = 'HTTP_X_REAL_IP'
75 75 def_key = 'REMOTE_ADDR'
76 76 self.ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
77 77 # skip passing error to error controller
78 78 environ['pylons.status_code_redirect'] = True
79 79
80 80 #======================================================================
81 81 # GET ACTION PULL or PUSH
82 82 #======================================================================
83 83 self.action = self.__get_action(environ)
84 84 try:
85 85 #==================================================================
86 86 # GET REPOSITORY NAME
87 87 #==================================================================
88 88 self.repo_name = self.__get_repository(environ)
89 89 except:
90 90 return HTTPInternalServerError()(environ, start_response)
91 91
92 92 #======================================================================
93 93 # CHECK ANONYMOUS PERMISSION
94 94 #======================================================================
95 95 if self.action in ['pull', 'push']:
96 96 anonymous_user = self.__get_user('default')
97 97 self.username = anonymous_user.username
98 98 anonymous_perm = self.__check_permission(self.action, anonymous_user ,
99 99 self.repo_name)
100 100
101 101 if anonymous_perm is not True or anonymous_user.active is False:
102 102 if anonymous_perm is not True:
103 103 log.debug('Not enough credentials to access this repository'
104 104 'as anonymous user')
105 105 if anonymous_user.active is False:
106 106 log.debug('Anonymous access is disabled, running '
107 107 'authentication')
108 #==================================================================
109 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE NEED
110 # TO AUTHENTICATE AND ASK FOR AUTHENTICATED USER PERMISSIONS
111 #==================================================================
108 #==============================================================
109 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
110 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
111 #==============================================================
112 112
113 113 if not REMOTE_USER(environ):
114 self.authenticate.realm = self.config['rhodecode_realm']
114 self.authenticate.realm = str(self.config['rhodecode_realm'])
115 115 result = self.authenticate(environ)
116 116 if isinstance(result, str):
117 117 AUTH_TYPE.update(environ, 'basic')
118 118 REMOTE_USER.update(environ, result)
119 119 else:
120 120 return result.wsgi_application(environ, start_response)
121 121
122 122
123 #==================================================================
123 #==============================================================
124 124 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
125 125 # BASIC AUTH
126 #==================================================================
126 #==============================================================
127 127
128 128 if self.action in ['pull', 'push']:
129 129 username = self.__get_environ_user(environ)
130 130 try:
131 131 user = self.__get_user(username)
132 132 self.username = user.username
133 133 except:
134 134 log.error(traceback.format_exc())
135 135 return HTTPInternalServerError()(environ, start_response)
136 136
137 137 #check permissions for this repository
138 138 perm = self.__check_permission(self.action, user, self.repo_name)
139 139 if perm is not True:
140 140 return HTTPForbidden()(environ, start_response)
141 141
142 142 self.extras = {'ip':self.ipaddr,
143 143 'username':self.username,
144 144 'action':self.action,
145 145 'repository':self.repo_name}
146 146
147 147 #===================================================================
148 148 # MERCURIAL REQUEST HANDLING
149 149 #===================================================================
150 150 environ['PATH_INFO'] = '/'#since we wrap into hgweb, reset the path
151 151 self.baseui = make_ui('db')
152 152 self.basepath = self.config['base_path']
153 153 self.repo_path = os.path.join(self.basepath, self.repo_name)
154 154
155 155 #quick check if that dir exists...
156 156 if check_repo_fast(self.repo_name, self.basepath):
157 157 return HTTPNotFound()(environ, start_response)
158 158 try:
159 159 app = wsgiapplication(self.__make_app)
160 160 except RepoError, e:
161 161 if str(e).find('not found') != -1:
162 162 return HTTPNotFound()(environ, start_response)
163 163 except Exception:
164 164 log.error(traceback.format_exc())
165 165 return HTTPInternalServerError()(environ, start_response)
166 166
167 167 #invalidate cache on push
168 168 if self.action == 'push':
169 169 self.__invalidate_cache(self.repo_name)
170 170
171 171 return app(environ, start_response)
172 172
173 173
174 174 def __make_app(self):
175 175 """Make an wsgi application using hgweb, and my generated baseui
176 176 instance
177 177 """
178 178
179 179 hgserve = hgweb(str(self.repo_path), baseui=self.baseui)
180 180 return self.__load_web_settings(hgserve, self.extras)
181 181
182 182
183 183 def __check_permission(self, action, user, repo_name):
184 184 """Checks permissions using action (push/pull) user and repository
185 185 name
186 186
187 187 :param action: push or pull action
188 188 :param user: user instance
189 189 :param repo_name: repository name
190 190 """
191 191 if action == 'push':
192 192 if not HasPermissionAnyMiddleware('repository.write',
193 193 'repository.admin')\
194 194 (user, repo_name):
195 195 return False
196 196
197 197 else:
198 198 #any other action need at least read permission
199 199 if not HasPermissionAnyMiddleware('repository.read',
200 200 'repository.write',
201 201 'repository.admin')\
202 202 (user, repo_name):
203 203 return False
204 204
205 205 return True
206 206
207 207
208 208 def __get_repository(self, environ):
209 209 """Get's repository name out of PATH_INFO header
210 210
211 211 :param environ: environ where PATH_INFO is stored
212 212 """
213 213 try:
214 214 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
215 215 if repo_name.endswith('/'):
216 216 repo_name = repo_name.rstrip('/')
217 217 except:
218 218 log.error(traceback.format_exc())
219 219 raise
220 220
221 221 return repo_name
222 222
223 223 def __get_environ_user(self, environ):
224 224 return environ.get('REMOTE_USER')
225 225
226 226 def __get_user(self, username):
227 227 return UserModel().get_by_username(username, cache=True)
228 228
229 229 def __get_action(self, environ):
230 230 """Maps mercurial request commands into a clone,pull or push command.
231 231 This should always return a valid command string
232 232
233 233 :param environ:
234 234 """
235 235 mapping = {'changegroup': 'pull',
236 236 'changegroupsubset': 'pull',
237 237 'stream_out': 'pull',
238 238 'listkeys': 'pull',
239 239 'unbundle': 'push',
240 240 'pushkey': 'push', }
241 241 for qry in environ['QUERY_STRING'].split('&'):
242 242 if qry.startswith('cmd'):
243 243 cmd = qry.split('=')[-1]
244 244 if mapping.has_key(cmd):
245 245 return mapping[cmd]
246 246 else:
247 247 return cmd
248 248
249 249 def __invalidate_cache(self, repo_name):
250 250 """we know that some change was made to repositories and we should
251 251 invalidate the cache to see the changes right away but only for
252 252 push requests"""
253 253 invalidate_cache('get_repo_cached_%s' % repo_name)
254 254
255 255
256 256 def __load_web_settings(self, hgserve, extras={}):
257 257 #set the global ui for hgserve instance passed
258 258 hgserve.repo.ui = self.baseui
259 259
260 260 hgrc = os.path.join(self.repo_path, '.hg', 'hgrc')
261 261
262 262 #inject some additional parameters that will be available in ui
263 263 #for hooks
264 264 for k, v in extras.items():
265 265 hgserve.repo.ui.setconfig('rhodecode_extras', k, v)
266 266
267 267 repoui = make_ui('file', hgrc, False)
268 268
269 269 if repoui:
270 270 #overwrite our ui instance with the section from hgrc file
271 271 for section in ui_sections:
272 272 for k, v in repoui.configitems(section):
273 273 hgserve.repo.ui.setconfig(section, k, v)
274 274
275 275 return hgserve
General Comments 0
You need to be logged in to leave comments. Login now