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