##// END OF EJS Templates
merge with beta
marcink -
r2474:6474e138 merge codereview
parent child Browse files
Show More
@@ -1,304 +1,304 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplegit
3 rhodecode.lib.middleware.simplegit
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 It's implemented with basic auth function
7 It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import re
28 import re
29 import logging
29 import logging
30 import traceback
30 import traceback
31
31
32 from dulwich import server as dulserver
32 from dulwich import server as dulserver
33
33
34
34
35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
36
36
37 def handle(self):
37 def handle(self):
38 write = lambda x: self.proto.write_sideband(1, x)
38 write = lambda x: self.proto.write_sideband(1, x)
39
39
40 graph_walker = dulserver.ProtocolGraphWalker(self,
40 graph_walker = dulserver.ProtocolGraphWalker(self,
41 self.repo.object_store,
41 self.repo.object_store,
42 self.repo.get_peeled)
42 self.repo.get_peeled)
43 objects_iter = self.repo.fetch_objects(
43 objects_iter = self.repo.fetch_objects(
44 graph_walker.determine_wants, graph_walker, self.progress,
44 graph_walker.determine_wants, graph_walker, self.progress,
45 get_tagged=self.get_tagged)
45 get_tagged=self.get_tagged)
46
46
47 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
47 # Did the process short-circuit (e.g. in a stateless RPC call)? Note
48 # that the client still expects a 0-object pack in most cases.
48 # that the client still expects a 0-object pack in most cases.
49 if objects_iter is None:
49 if objects_iter is None:
50 return
50 return
51
51
52 self.progress("counting objects: %d, done.\n" % len(objects_iter))
52 self.progress("counting objects: %d, done.\n" % len(objects_iter))
53 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
53 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
54 objects_iter)
54 objects_iter)
55 messages = []
55 messages = []
56 messages.append('thank you for using rhodecode')
56 messages.append('thank you for using rhodecode')
57
57
58 for msg in messages:
58 for msg in messages:
59 self.progress(msg + "\n")
59 self.progress(msg + "\n")
60 # we are done
60 # we are done
61 self.proto.write("0000")
61 self.proto.write("0000")
62
62
63
63
64 dulserver.DEFAULT_HANDLERS = {
64 dulserver.DEFAULT_HANDLERS = {
65 #git-ls-remote, git-clone, git-fetch and git-pull
65 #git-ls-remote, git-clone, git-fetch and git-pull
66 'git-upload-pack': SimpleGitUploadPackHandler,
66 'git-upload-pack': SimpleGitUploadPackHandler,
67 #git-push
67 #git-push
68 'git-receive-pack': dulserver.ReceivePackHandler,
68 'git-receive-pack': dulserver.ReceivePackHandler,
69 }
69 }
70
70
71 # not used for now until dulwich get's fixed
71 # not used for now until dulwich get's fixed
72 #from dulwich.repo import Repo
72 #from dulwich.repo import Repo
73 #from dulwich.web import make_wsgi_chain
73 #from dulwich.web import make_wsgi_chain
74
74
75 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
75 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
76
76
77 from rhodecode.lib.utils2 import safe_str
77 from rhodecode.lib.utils2 import safe_str
78 from rhodecode.lib.base import BaseVCSController
78 from rhodecode.lib.base import BaseVCSController
79 from rhodecode.lib.auth import get_container_username
79 from rhodecode.lib.auth import get_container_username
80 from rhodecode.lib.utils import is_valid_repo, make_ui
80 from rhodecode.lib.utils import is_valid_repo, make_ui
81 from rhodecode.model.db import User, RhodeCodeUi
81 from rhodecode.model.db import User, RhodeCodeUi
82
82
83 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
83 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
84
84
85 log = logging.getLogger(__name__)
85 log = logging.getLogger(__name__)
86
86
87
87
88 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
88 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
89
89
90
90
91 def is_git(environ):
91 def is_git(environ):
92 path_info = environ['PATH_INFO']
92 path_info = environ['PATH_INFO']
93 isgit_path = GIT_PROTO_PAT.match(path_info)
93 isgit_path = GIT_PROTO_PAT.match(path_info)
94 log.debug('pathinfo: %s detected as GIT %s' % (
94 log.debug('pathinfo: %s detected as GIT %s' % (
95 path_info, isgit_path != None)
95 path_info, isgit_path != None)
96 )
96 )
97 return isgit_path
97 return isgit_path
98
98
99
99
100 class SimpleGit(BaseVCSController):
100 class SimpleGit(BaseVCSController):
101
101
102 def _handle_request(self, environ, start_response):
102 def _handle_request(self, environ, start_response):
103
103
104 if not is_git(environ):
104 if not is_git(environ):
105 return self.application(environ, start_response)
105 return self.application(environ, start_response)
106
106
107 ipaddr = self._get_ip_addr(environ)
107 ipaddr = self._get_ip_addr(environ)
108 username = None
108 username = None
109 self._git_first_op = False
109 self._git_first_op = False
110 # skip passing error to error controller
110 # skip passing error to error controller
111 environ['pylons.status_code_redirect'] = True
111 environ['pylons.status_code_redirect'] = True
112
112
113 #======================================================================
113 #======================================================================
114 # EXTRACT REPOSITORY NAME FROM ENV
114 # EXTRACT REPOSITORY NAME FROM ENV
115 #======================================================================
115 #======================================================================
116 try:
116 try:
117 repo_name = self.__get_repository(environ)
117 repo_name = self.__get_repository(environ)
118 log.debug('Extracted repo name is %s' % repo_name)
118 log.debug('Extracted repo name is %s' % repo_name)
119 except:
119 except:
120 return HTTPInternalServerError()(environ, start_response)
120 return HTTPInternalServerError()(environ, start_response)
121
121
122 # quick check if that dir exists...
122 # quick check if that dir exists...
123 if is_valid_repo(repo_name, self.basepath) is False:
123 if is_valid_repo(repo_name, self.basepath) is False:
124 return HTTPNotFound()(environ, start_response)
124 return HTTPNotFound()(environ, start_response)
125
125
126 #======================================================================
126 #======================================================================
127 # GET ACTION PULL or PUSH
127 # GET ACTION PULL or PUSH
128 #======================================================================
128 #======================================================================
129 action = self.__get_action(environ)
129 action = self.__get_action(environ)
130
130
131 #======================================================================
131 #======================================================================
132 # CHECK ANONYMOUS PERMISSION
132 # CHECK ANONYMOUS PERMISSION
133 #======================================================================
133 #======================================================================
134 if action in ['pull', 'push']:
134 if action in ['pull', 'push']:
135 anonymous_user = self.__get_user('default')
135 anonymous_user = self.__get_user('default')
136 username = anonymous_user.username
136 username = anonymous_user.username
137 anonymous_perm = self._check_permission(action, anonymous_user,
137 anonymous_perm = self._check_permission(action, anonymous_user,
138 repo_name)
138 repo_name)
139
139
140 if anonymous_perm is not True or anonymous_user.active is False:
140 if anonymous_perm is not True or anonymous_user.active is False:
141 if anonymous_perm is not True:
141 if anonymous_perm is not True:
142 log.debug('Not enough credentials to access this '
142 log.debug('Not enough credentials to access this '
143 'repository as anonymous user')
143 'repository as anonymous user')
144 if anonymous_user.active is False:
144 if anonymous_user.active is False:
145 log.debug('Anonymous access is disabled, running '
145 log.debug('Anonymous access is disabled, running '
146 'authentication')
146 'authentication')
147 #==============================================================
147 #==============================================================
148 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
148 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
149 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
149 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
150 #==============================================================
150 #==============================================================
151
151
152 # Attempting to retrieve username from the container
152 # Attempting to retrieve username from the container
153 username = get_container_username(environ, self.config)
153 username = get_container_username(environ, self.config)
154
154
155 # If not authenticated by the container, running basic auth
155 # If not authenticated by the container, running basic auth
156 if not username:
156 if not username:
157 self.authenticate.realm = \
157 self.authenticate.realm = \
158 safe_str(self.config['rhodecode_realm'])
158 safe_str(self.config['rhodecode_realm'])
159 result = self.authenticate(environ)
159 result = self.authenticate(environ)
160 if isinstance(result, str):
160 if isinstance(result, str):
161 AUTH_TYPE.update(environ, 'basic')
161 AUTH_TYPE.update(environ, 'basic')
162 REMOTE_USER.update(environ, result)
162 REMOTE_USER.update(environ, result)
163 username = result
163 username = result
164 else:
164 else:
165 return result.wsgi_application(environ, start_response)
165 return result.wsgi_application(environ, start_response)
166
166
167 #==============================================================
167 #==============================================================
168 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
168 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
169 #==============================================================
169 #==============================================================
170 if action in ['pull', 'push']:
170 if action in ['pull', 'push']:
171 try:
171 try:
172 user = self.__get_user(username)
172 user = self.__get_user(username)
173 if user is None or not user.active:
173 if user is None or not user.active:
174 return HTTPForbidden()(environ, start_response)
174 return HTTPForbidden()(environ, start_response)
175 username = user.username
175 username = user.username
176 except:
176 except:
177 log.error(traceback.format_exc())
177 log.error(traceback.format_exc())
178 return HTTPInternalServerError()(environ,
178 return HTTPInternalServerError()(environ,
179 start_response)
179 start_response)
180
180
181 #check permissions for this repository
181 #check permissions for this repository
182 perm = self._check_permission(action, user, repo_name)
182 perm = self._check_permission(action, user, repo_name)
183 if perm is not True:
183 if perm is not True:
184 return HTTPForbidden()(environ, start_response)
184 return HTTPForbidden()(environ, start_response)
185 extras = {
185 extras = {
186 'ip': ipaddr,
186 'ip': ipaddr,
187 'username': username,
187 'username': username,
188 'action': action,
188 'action': action,
189 'repository': repo_name,
189 'repository': repo_name,
190 'scm': 'git',
190 'scm': 'git',
191 }
191 }
192
192
193 #===================================================================
193 #===================================================================
194 # GIT REQUEST HANDLING
194 # GIT REQUEST HANDLING
195 #===================================================================
195 #===================================================================
196 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
196 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
197 log.debug('Repository path is %s' % repo_path)
197 log.debug('Repository path is %s' % repo_path)
198
198
199 baseui = make_ui('db')
199 baseui = make_ui('db')
200 self.__inject_extras(repo_path, baseui, extras)
200 self.__inject_extras(repo_path, baseui, extras)
201
201
202 try:
202 try:
203 # invalidate cache on push
203 # invalidate cache on push
204 if action == 'push':
204 if action == 'push':
205 self._invalidate_cache(repo_name)
205 self._invalidate_cache(repo_name)
206 self._handle_githooks(repo_name, action, baseui, environ)
206 self._handle_githooks(repo_name, action, baseui, environ)
207
207
208 log.info('%s action on GIT repo "%s"' % (action, repo_name))
208 log.info('%s action on GIT repo "%s"' % (action, repo_name))
209 app = self.__make_app(repo_name, repo_path, username)
209 app = self.__make_app(repo_name, repo_path, username)
210 return app(environ, start_response)
210 return app(environ, start_response)
211 except Exception:
211 except Exception:
212 log.error(traceback.format_exc())
212 log.error(traceback.format_exc())
213 return HTTPInternalServerError()(environ, start_response)
213 return HTTPInternalServerError()(environ, start_response)
214
214
215 def __make_app(self, repo_name, repo_path, username):
215 def __make_app(self, repo_name, repo_path, username):
216 """
216 """
217 Make an wsgi application using dulserver
217 Make an wsgi application using dulserver
218
218
219 :param repo_name: name of the repository
219 :param repo_name: name of the repository
220 :param repo_path: full path to the repository
220 :param repo_path: full path to the repository
221 """
221 """
222
222
223 from rhodecode.lib.middleware.pygrack import make_wsgi_app
223 from rhodecode.lib.middleware.pygrack import make_wsgi_app
224 app = make_wsgi_app(
224 app = make_wsgi_app(
225 repo_root=os.path.dirname(repo_path),
225 repo_root=safe_str(self.basepath),
226 repo_name=repo_name,
226 repo_name=repo_name,
227 username=username,
227 username=username,
228 )
228 )
229 return app
229 return app
230
230
231 def __get_repository(self, environ):
231 def __get_repository(self, environ):
232 """
232 """
233 Get's repository name out of PATH_INFO header
233 Get's repository name out of PATH_INFO header
234
234
235 :param environ: environ where PATH_INFO is stored
235 :param environ: environ where PATH_INFO is stored
236 """
236 """
237 try:
237 try:
238 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
238 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
239 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
239 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
240 except:
240 except:
241 log.error(traceback.format_exc())
241 log.error(traceback.format_exc())
242 raise
242 raise
243
243
244 return repo_name
244 return repo_name
245
245
246 def __get_user(self, username):
246 def __get_user(self, username):
247 return User.get_by_username(username)
247 return User.get_by_username(username)
248
248
249 def __get_action(self, environ):
249 def __get_action(self, environ):
250 """
250 """
251 Maps git request commands into a pull or push command.
251 Maps git request commands into a pull or push command.
252
252
253 :param environ:
253 :param environ:
254 """
254 """
255 service = environ['QUERY_STRING'].split('=')
255 service = environ['QUERY_STRING'].split('=')
256
256
257 if len(service) > 1:
257 if len(service) > 1:
258 service_cmd = service[1]
258 service_cmd = service[1]
259 mapping = {
259 mapping = {
260 'git-receive-pack': 'push',
260 'git-receive-pack': 'push',
261 'git-upload-pack': 'pull',
261 'git-upload-pack': 'pull',
262 }
262 }
263 op = mapping[service_cmd]
263 op = mapping[service_cmd]
264 self._git_stored_op = op
264 self._git_stored_op = op
265 return op
265 return op
266 else:
266 else:
267 # try to fallback to stored variable as we don't know if the last
267 # try to fallback to stored variable as we don't know if the last
268 # operation is pull/push
268 # operation is pull/push
269 op = getattr(self, '_git_stored_op', 'pull')
269 op = getattr(self, '_git_stored_op', 'pull')
270 return op
270 return op
271
271
272 def _handle_githooks(self, repo_name, action, baseui, environ):
272 def _handle_githooks(self, repo_name, action, baseui, environ):
273 """
273 """
274 Handles pull action, push is handled by post-receive hook
274 Handles pull action, push is handled by post-receive hook
275 """
275 """
276 from rhodecode.lib.hooks import log_pull_action
276 from rhodecode.lib.hooks import log_pull_action
277 service = environ['QUERY_STRING'].split('=')
277 service = environ['QUERY_STRING'].split('=')
278 if len(service) < 2:
278 if len(service) < 2:
279 return
279 return
280
280
281 from rhodecode.model.db import Repository
281 from rhodecode.model.db import Repository
282 _repo = Repository.get_by_repo_name(repo_name)
282 _repo = Repository.get_by_repo_name(repo_name)
283 _repo = _repo.scm_instance
283 _repo = _repo.scm_instance
284 _repo._repo.ui = baseui
284 _repo._repo.ui = baseui
285
285
286 _hooks = dict(baseui.configitems('hooks')) or {}
286 _hooks = dict(baseui.configitems('hooks')) or {}
287 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
287 if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL):
288 log_pull_action(ui=baseui, repo=_repo._repo)
288 log_pull_action(ui=baseui, repo=_repo._repo)
289
289
290 def __inject_extras(self, repo_path, baseui, extras={}):
290 def __inject_extras(self, repo_path, baseui, extras={}):
291 """
291 """
292 Injects some extra params into baseui instance
292 Injects some extra params into baseui instance
293
293
294 :param baseui: baseui instance
294 :param baseui: baseui instance
295 :param extras: dict with extra params to put into baseui
295 :param extras: dict with extra params to put into baseui
296 """
296 """
297
297
298 # make our hgweb quiet so it doesn't print output
298 # make our hgweb quiet so it doesn't print output
299 baseui.setconfig('ui', 'quiet', 'true')
299 baseui.setconfig('ui', 'quiet', 'true')
300
300
301 #inject some additional parameters that will be available in ui
301 #inject some additional parameters that will be available in ui
302 #for hooks
302 #for hooks
303 for k, v in extras.items():
303 for k, v in extras.items():
304 baseui.setconfig('rhodecode_extras', k, v)
304 baseui.setconfig('rhodecode_extras', k, v)
General Comments 0
You need to be logged in to leave comments. Login now