##// END OF EJS Templates
comments on git-operations mapp
marcink -
r2322:6ea36346 beta
parent child Browse files
Show More
@@ -1,300 +1,301 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-upload-pack': SimpleGitUploadPackHandler,
66 'git-upload-pack': SimpleGitUploadPackHandler,
67 #git-push
66 'git-receive-pack': dulserver.ReceivePackHandler,
68 'git-receive-pack': dulserver.ReceivePackHandler,
67 }
69 }
68
70
69 from dulwich.repo import Repo
71 from dulwich.repo import Repo
70 from dulwich.web import make_wsgi_chain
72 from dulwich.web import make_wsgi_chain
71
73
72 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
74 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
73
75
74 from rhodecode.lib.utils2 import safe_str
76 from rhodecode.lib.utils2 import safe_str
75 from rhodecode.lib.base import BaseVCSController
77 from rhodecode.lib.base import BaseVCSController
76 from rhodecode.lib.auth import get_container_username
78 from rhodecode.lib.auth import get_container_username
77 from rhodecode.lib.utils import is_valid_repo, make_ui
79 from rhodecode.lib.utils import is_valid_repo, make_ui
78 from rhodecode.model.db import User
80 from rhodecode.model.db import User
79
81
80 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
82 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
81
83
82 log = logging.getLogger(__name__)
84 log = logging.getLogger(__name__)
83
85
84
86
85 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
87 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
86
88
87
89
88 def is_git(environ):
90 def is_git(environ):
89 path_info = environ['PATH_INFO']
91 path_info = environ['PATH_INFO']
90 isgit_path = GIT_PROTO_PAT.match(path_info)
92 isgit_path = GIT_PROTO_PAT.match(path_info)
91 log.debug('pathinfo: %s detected as GIT %s' % (
93 log.debug('pathinfo: %s detected as GIT %s' % (
92 path_info, isgit_path != None)
94 path_info, isgit_path != None)
93 )
95 )
94 return isgit_path
96 return isgit_path
95
97
96
98
97 class SimpleGit(BaseVCSController):
99 class SimpleGit(BaseVCSController):
98
100
99 def _handle_request(self, environ, start_response):
101 def _handle_request(self, environ, start_response):
100
102
101 if not is_git(environ):
103 if not is_git(environ):
102 return self.application(environ, start_response)
104 return self.application(environ, start_response)
103
105
104 ipaddr = self._get_ip_addr(environ)
106 ipaddr = self._get_ip_addr(environ)
105 username = None
107 username = None
106 self._git_first_op = False
108 self._git_first_op = False
107 # skip passing error to error controller
109 # skip passing error to error controller
108 environ['pylons.status_code_redirect'] = True
110 environ['pylons.status_code_redirect'] = True
109
111
110 #======================================================================
112 #======================================================================
111 # EXTRACT REPOSITORY NAME FROM ENV
113 # EXTRACT REPOSITORY NAME FROM ENV
112 #======================================================================
114 #======================================================================
113 try:
115 try:
114 repo_name = self.__get_repository(environ)
116 repo_name = self.__get_repository(environ)
115 log.debug('Extracted repo name is %s' % repo_name)
117 log.debug('Extracted repo name is %s' % repo_name)
116 except:
118 except:
117 return HTTPInternalServerError()(environ, start_response)
119 return HTTPInternalServerError()(environ, start_response)
118
120
119 # quick check if that dir exists...
121 # quick check if that dir exists...
120 if is_valid_repo(repo_name, self.basepath) is False:
122 if is_valid_repo(repo_name, self.basepath) is False:
121 return HTTPNotFound()(environ, start_response)
123 return HTTPNotFound()(environ, start_response)
122
124
123 #======================================================================
125 #======================================================================
124 # GET ACTION PULL or PUSH
126 # GET ACTION PULL or PUSH
125 #======================================================================
127 #======================================================================
126 action = self.__get_action(environ)
128 action = self.__get_action(environ)
127
129
128 #======================================================================
130 #======================================================================
129 # CHECK ANONYMOUS PERMISSION
131 # CHECK ANONYMOUS PERMISSION
130 #======================================================================
132 #======================================================================
131 if action in ['pull', 'push']:
133 if action in ['pull', 'push']:
132 anonymous_user = self.__get_user('default')
134 anonymous_user = self.__get_user('default')
133 username = anonymous_user.username
135 username = anonymous_user.username
134 anonymous_perm = self._check_permission(action, anonymous_user,
136 anonymous_perm = self._check_permission(action, anonymous_user,
135 repo_name)
137 repo_name)
136
138
137 if anonymous_perm is not True or anonymous_user.active is False:
139 if anonymous_perm is not True or anonymous_user.active is False:
138 if anonymous_perm is not True:
140 if anonymous_perm is not True:
139 log.debug('Not enough credentials to access this '
141 log.debug('Not enough credentials to access this '
140 'repository as anonymous user')
142 'repository as anonymous user')
141 if anonymous_user.active is False:
143 if anonymous_user.active is False:
142 log.debug('Anonymous access is disabled, running '
144 log.debug('Anonymous access is disabled, running '
143 'authentication')
145 'authentication')
144 #==============================================================
146 #==============================================================
145 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
147 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
146 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
148 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
147 #==============================================================
149 #==============================================================
148
150
149 # Attempting to retrieve username from the container
151 # Attempting to retrieve username from the container
150 username = get_container_username(environ, self.config)
152 username = get_container_username(environ, self.config)
151
153
152 # If not authenticated by the container, running basic auth
154 # If not authenticated by the container, running basic auth
153 if not username:
155 if not username:
154 self.authenticate.realm = \
156 self.authenticate.realm = \
155 safe_str(self.config['rhodecode_realm'])
157 safe_str(self.config['rhodecode_realm'])
156 result = self.authenticate(environ)
158 result = self.authenticate(environ)
157 if isinstance(result, str):
159 if isinstance(result, str):
158 AUTH_TYPE.update(environ, 'basic')
160 AUTH_TYPE.update(environ, 'basic')
159 REMOTE_USER.update(environ, result)
161 REMOTE_USER.update(environ, result)
160 username = result
162 username = result
161 else:
163 else:
162 return result.wsgi_application(environ, start_response)
164 return result.wsgi_application(environ, start_response)
163
165
164 #==============================================================
166 #==============================================================
165 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
167 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
166 #==============================================================
168 #==============================================================
167 if action in ['pull', 'push']:
169 if action in ['pull', 'push']:
168 try:
170 try:
169 user = self.__get_user(username)
171 user = self.__get_user(username)
170 if user is None or not user.active:
172 if user is None or not user.active:
171 return HTTPForbidden()(environ, start_response)
173 return HTTPForbidden()(environ, start_response)
172 username = user.username
174 username = user.username
173 except:
175 except:
174 log.error(traceback.format_exc())
176 log.error(traceback.format_exc())
175 return HTTPInternalServerError()(environ,
177 return HTTPInternalServerError()(environ,
176 start_response)
178 start_response)
177
179
178 #check permissions for this repository
180 #check permissions for this repository
179 perm = self._check_permission(action, user, repo_name)
181 perm = self._check_permission(action, user, repo_name)
180 if perm is not True:
182 if perm is not True:
181 return HTTPForbidden()(environ, start_response)
183 return HTTPForbidden()(environ, start_response)
182 extras = {
184 extras = {
183 'ip': ipaddr,
185 'ip': ipaddr,
184 'username': username,
186 'username': username,
185 'action': action,
187 'action': action,
186 'repository': repo_name,
188 'repository': repo_name,
187 'scm': 'git',
189 'scm': 'git',
188 }
190 }
189
191
190 #===================================================================
192 #===================================================================
191 # GIT REQUEST HANDLING
193 # GIT REQUEST HANDLING
192 #===================================================================
194 #===================================================================
193 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
195 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
194 log.debug('Repository path is %s' % repo_path)
196 log.debug('Repository path is %s' % repo_path)
195
197
196 baseui = make_ui('db')
198 baseui = make_ui('db')
197 self.__inject_extras(repo_path, baseui, extras)
199 self.__inject_extras(repo_path, baseui, extras)
198
200
199
200 try:
201 try:
201 # invalidate cache on push
202 # invalidate cache on push
202 if action == 'push':
203 if action == 'push':
203 self._invalidate_cache(repo_name)
204 self._invalidate_cache(repo_name)
204 self._handle_githooks(repo_name, action, baseui, environ)
205 self._handle_githooks(repo_name, action, baseui, environ)
205
206
206 log.info('%s action on GIT repo "%s"' % (action, repo_name))
207 log.info('%s action on GIT repo "%s"' % (action, repo_name))
207 app = self.__make_app(repo_name, repo_path)
208 app = self.__make_app(repo_name, repo_path)
208 return app(environ, start_response)
209 return app(environ, start_response)
209 except Exception:
210 except Exception:
210 log.error(traceback.format_exc())
211 log.error(traceback.format_exc())
211 return HTTPInternalServerError()(environ, start_response)
212 return HTTPInternalServerError()(environ, start_response)
212
213
213 def __make_app(self, repo_name, repo_path):
214 def __make_app(self, repo_name, repo_path):
214 """
215 """
215 Make an wsgi application using dulserver
216 Make an wsgi application using dulserver
216
217
217 :param repo_name: name of the repository
218 :param repo_name: name of the repository
218 :param repo_path: full path to the repository
219 :param repo_path: full path to the repository
219 """
220 """
220 _d = {'/' + repo_name: Repo(repo_path)}
221 _d = {'/' + repo_name: Repo(repo_path)}
221 backend = dulserver.DictBackend(_d)
222 backend = dulserver.DictBackend(_d)
222 gitserve = make_wsgi_chain(backend)
223 gitserve = make_wsgi_chain(backend)
223
224
224 return gitserve
225 return gitserve
225
226
226 def __get_repository(self, environ):
227 def __get_repository(self, environ):
227 """
228 """
228 Get's repository name out of PATH_INFO header
229 Get's repository name out of PATH_INFO header
229
230
230 :param environ: environ where PATH_INFO is stored
231 :param environ: environ where PATH_INFO is stored
231 """
232 """
232 try:
233 try:
233 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
234 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
234 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
235 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
235 except:
236 except:
236 log.error(traceback.format_exc())
237 log.error(traceback.format_exc())
237 raise
238 raise
238
239
239 return repo_name
240 return repo_name
240
241
241 def __get_user(self, username):
242 def __get_user(self, username):
242 return User.get_by_username(username)
243 return User.get_by_username(username)
243
244
244 def __get_action(self, environ):
245 def __get_action(self, environ):
245 """
246 """
246 Maps git request commands into a pull or push command.
247 Maps git request commands into a pull or push command.
247
248
248 :param environ:
249 :param environ:
249 """
250 """
250 service = environ['QUERY_STRING'].split('=')
251 service = environ['QUERY_STRING'].split('=')
251
252
252 if len(service) > 1:
253 if len(service) > 1:
253 service_cmd = service[1]
254 service_cmd = service[1]
254 mapping = {
255 mapping = {
255 'git-receive-pack': 'push',
256 'git-receive-pack': 'push',
256 'git-upload-pack': 'pull',
257 'git-upload-pack': 'pull',
257 }
258 }
258 op = mapping[service_cmd]
259 op = mapping[service_cmd]
259 self._git_stored_op = op
260 self._git_stored_op = op
260 return op
261 return op
261 else:
262 else:
262 # try to fallback to stored variable as we don't know if the last
263 # try to fallback to stored variable as we don't know if the last
263 # operation is pull/push
264 # operation is pull/push
264 op = getattr(self, '_git_stored_op', 'pull')
265 op = getattr(self, '_git_stored_op', 'pull')
265 return op
266 return op
266
267
267 def _handle_githooks(self, repo_name, action, baseui, environ):
268 def _handle_githooks(self, repo_name, action, baseui, environ):
268 from rhodecode.lib.hooks import log_pull_action, log_push_action
269 from rhodecode.lib.hooks import log_pull_action, log_push_action
269 service = environ['QUERY_STRING'].split('=')
270 service = environ['QUERY_STRING'].split('=')
270 if len(service) < 2:
271 if len(service) < 2:
271 return
272 return
272
273
273 from rhodecode.model.db import Repository
274 from rhodecode.model.db import Repository
274 _repo = Repository.get_by_repo_name(repo_name)
275 _repo = Repository.get_by_repo_name(repo_name)
275 _repo = _repo.scm_instance
276 _repo = _repo.scm_instance
276 _repo._repo.ui = baseui
277 _repo._repo.ui = baseui
277
278
278 push_hook = 'pretxnchangegroup.push_logger'
279 push_hook = 'pretxnchangegroup.push_logger'
279 pull_hook = 'preoutgoing.pull_logger'
280 pull_hook = 'preoutgoing.pull_logger'
280 _hooks = dict(baseui.configitems('hooks')) or {}
281 _hooks = dict(baseui.configitems('hooks')) or {}
281 if action == 'push' and _hooks.get(push_hook):
282 if action == 'push' and _hooks.get(push_hook):
282 log_push_action(ui=baseui, repo=_repo._repo)
283 log_push_action(ui=baseui, repo=_repo._repo)
283 elif action == 'pull' and _hooks.get(pull_hook):
284 elif action == 'pull' and _hooks.get(pull_hook):
284 log_pull_action(ui=baseui, repo=_repo._repo)
285 log_pull_action(ui=baseui, repo=_repo._repo)
285
286
286 def __inject_extras(self, repo_path, baseui, extras={}):
287 def __inject_extras(self, repo_path, baseui, extras={}):
287 """
288 """
288 Injects some extra params into baseui instance
289 Injects some extra params into baseui instance
289
290
290 :param baseui: baseui instance
291 :param baseui: baseui instance
291 :param extras: dict with extra params to put into baseui
292 :param extras: dict with extra params to put into baseui
292 """
293 """
293
294
294 # make our hgweb quiet so it doesn't print output
295 # make our hgweb quiet so it doesn't print output
295 baseui.setconfig('ui', 'quiet', 'true')
296 baseui.setconfig('ui', 'quiet', 'true')
296
297
297 #inject some additional parameters that will be available in ui
298 #inject some additional parameters that will be available in ui
298 #for hooks
299 #for hooks
299 for k, v in extras.items():
300 for k, v in extras.items():
300 baseui.setconfig('rhodecode_extras', k, v)
301 baseui.setconfig('rhodecode_extras', k, v)
General Comments 0
You need to be logged in to leave comments. Login now