##// END OF EJS Templates
Making RhodeCode ready for dulwich 0.8.4
Tony Bussieres -
r2137:462a845c beta
parent child Browse files
Show More
@@ -1,17 +1,17 b''
1 Pylons==1.0.0
1 Pylons==1.0.0
2 Beaker==1.6.3
2 Beaker==1.6.3
3 WebHelpers==1.3
3 WebHelpers==1.3
4 formencode==1.2.4
4 formencode==1.2.4
5 SQLAlchemy==0.7.5
5 SQLAlchemy==0.7.5
6 Mako==0.5.0
6 Mako==0.5.0
7 pygments>=1.4
7 pygments>=1.4
8 whoosh>=2.3.0,<2.4
8 whoosh>=2.3.0,<2.4
9 celery>=2.2.5,<2.3
9 celery>=2.2.5,<2.3
10 babel
10 babel
11 python-dateutil>=1.5.0,<2.0.0
11 python-dateutil>=1.5.0,<2.0.0
12 dulwich>=0.8.0,<0.9.0
12 dulwich>=0.8.4,<0.9.0
13 webob==1.0.8
13 webob==1.0.8
14 markdown==2.1.1
14 markdown==2.1.1
15 docutils==0.8.1
15 docutils==0.8.1
16 py-bcrypt
16 py-bcrypt
17 mercurial>=2.1,<2.2 No newline at end of file
17 mercurial>=2.1,<2.2
@@ -1,251 +1,251 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 # Do they want any objects?
47 # Do they want any objects?
48 if objects_iter is None or len(objects_iter) == 0:
48 if objects_iter is None or len(objects_iter) == 0:
49 return
49 return
50
50
51 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 self.progress("counting objects: %d, done.\n" % len(objects_iter))
52 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
52 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
53 objects_iter, len(objects_iter))
53 objects_iter, len(objects_iter))
54 messages = []
54 messages = []
55 messages.append('thank you for using rhodecode')
55 messages.append('thank you for using rhodecode')
56
56
57 for msg in messages:
57 for msg in messages:
58 self.progress(msg + "\n")
58 self.progress(msg + "\n")
59 # we are done
59 # we are done
60 self.proto.write("0000")
60 self.proto.write("0000")
61
61
62 dulserver.DEFAULT_HANDLERS = {
62 dulserver.DEFAULT_HANDLERS = {
63 'git-upload-pack': SimpleGitUploadPackHandler,
63 'git-upload-pack': SimpleGitUploadPackHandler,
64 'git-receive-pack': dulserver.ReceivePackHandler,
64 'git-receive-pack': dulserver.ReceivePackHandler,
65 }
65 }
66
66
67 from dulwich.repo import Repo
67 from dulwich.repo import Repo
68 from dulwich.web import HTTPGitApplication
68 from dulwich.web import make_wsgi_chain
69
69
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71
71
72 from rhodecode.lib.utils2 import safe_str
72 from rhodecode.lib.utils2 import safe_str
73 from rhodecode.lib.base import BaseVCSController
73 from rhodecode.lib.base import BaseVCSController
74 from rhodecode.lib.auth import get_container_username
74 from rhodecode.lib.auth import get_container_username
75 from rhodecode.lib.utils import is_valid_repo
75 from rhodecode.lib.utils import is_valid_repo
76 from rhodecode.model.db import User
76 from rhodecode.model.db import User
77
77
78 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
78 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
79
79
80 log = logging.getLogger(__name__)
80 log = logging.getLogger(__name__)
81
81
82
82
83 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
83 GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
84
84
85
85
86 def is_git(environ):
86 def is_git(environ):
87 path_info = environ['PATH_INFO']
87 path_info = environ['PATH_INFO']
88 isgit_path = GIT_PROTO_PAT.match(path_info)
88 isgit_path = GIT_PROTO_PAT.match(path_info)
89 log.debug('pathinfo: %s detected as GIT %s' % (
89 log.debug('pathinfo: %s detected as GIT %s' % (
90 path_info, isgit_path != None)
90 path_info, isgit_path != None)
91 )
91 )
92 return isgit_path
92 return isgit_path
93
93
94
94
95 class SimpleGit(BaseVCSController):
95 class SimpleGit(BaseVCSController):
96
96
97 def _handle_request(self, environ, start_response):
97 def _handle_request(self, environ, start_response):
98
98
99 if not is_git(environ):
99 if not is_git(environ):
100 return self.application(environ, start_response)
100 return self.application(environ, start_response)
101
101
102 proxy_key = 'HTTP_X_REAL_IP'
102 proxy_key = 'HTTP_X_REAL_IP'
103 def_key = 'REMOTE_ADDR'
103 def_key = 'REMOTE_ADDR'
104 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
104 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
105 username = None
105 username = None
106 # skip passing error to error controller
106 # skip passing error to error controller
107 environ['pylons.status_code_redirect'] = True
107 environ['pylons.status_code_redirect'] = True
108
108
109 #======================================================================
109 #======================================================================
110 # EXTRACT REPOSITORY NAME FROM ENV
110 # EXTRACT REPOSITORY NAME FROM ENV
111 #======================================================================
111 #======================================================================
112 try:
112 try:
113 repo_name = self.__get_repository(environ)
113 repo_name = self.__get_repository(environ)
114 log.debug('Extracted repo name is %s' % repo_name)
114 log.debug('Extracted repo name is %s' % repo_name)
115 except:
115 except:
116 return HTTPInternalServerError()(environ, start_response)
116 return HTTPInternalServerError()(environ, start_response)
117
117
118 #======================================================================
118 #======================================================================
119 # GET ACTION PULL or PUSH
119 # GET ACTION PULL or PUSH
120 #======================================================================
120 #======================================================================
121 action = self.__get_action(environ)
121 action = self.__get_action(environ)
122
122
123 #======================================================================
123 #======================================================================
124 # CHECK ANONYMOUS PERMISSION
124 # CHECK ANONYMOUS PERMISSION
125 #======================================================================
125 #======================================================================
126 if action in ['pull', 'push']:
126 if action in ['pull', 'push']:
127 anonymous_user = self.__get_user('default')
127 anonymous_user = self.__get_user('default')
128 username = anonymous_user.username
128 username = anonymous_user.username
129 anonymous_perm = self._check_permission(action, anonymous_user,
129 anonymous_perm = self._check_permission(action, anonymous_user,
130 repo_name)
130 repo_name)
131
131
132 if anonymous_perm is not True or anonymous_user.active is False:
132 if anonymous_perm is not True or anonymous_user.active is False:
133 if anonymous_perm is not True:
133 if anonymous_perm is not True:
134 log.debug('Not enough credentials to access this '
134 log.debug('Not enough credentials to access this '
135 'repository as anonymous user')
135 'repository as anonymous user')
136 if anonymous_user.active is False:
136 if anonymous_user.active is False:
137 log.debug('Anonymous access is disabled, running '
137 log.debug('Anonymous access is disabled, running '
138 'authentication')
138 'authentication')
139 #==============================================================
139 #==============================================================
140 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
140 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
141 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
141 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
142 #==============================================================
142 #==============================================================
143
143
144 # Attempting to retrieve username from the container
144 # Attempting to retrieve username from the container
145 username = get_container_username(environ, self.config)
145 username = get_container_username(environ, self.config)
146
146
147 # If not authenticated by the container, running basic auth
147 # If not authenticated by the container, running basic auth
148 if not username:
148 if not username:
149 self.authenticate.realm = \
149 self.authenticate.realm = \
150 safe_str(self.config['rhodecode_realm'])
150 safe_str(self.config['rhodecode_realm'])
151 result = self.authenticate(environ)
151 result = self.authenticate(environ)
152 if isinstance(result, str):
152 if isinstance(result, str):
153 AUTH_TYPE.update(environ, 'basic')
153 AUTH_TYPE.update(environ, 'basic')
154 REMOTE_USER.update(environ, result)
154 REMOTE_USER.update(environ, result)
155 username = result
155 username = result
156 else:
156 else:
157 return result.wsgi_application(environ, start_response)
157 return result.wsgi_application(environ, start_response)
158
158
159 #==============================================================
159 #==============================================================
160 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
160 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
161 #==============================================================
161 #==============================================================
162 if action in ['pull', 'push']:
162 if action in ['pull', 'push']:
163 try:
163 try:
164 user = self.__get_user(username)
164 user = self.__get_user(username)
165 if user is None or not user.active:
165 if user is None or not user.active:
166 return HTTPForbidden()(environ, start_response)
166 return HTTPForbidden()(environ, start_response)
167 username = user.username
167 username = user.username
168 except:
168 except:
169 log.error(traceback.format_exc())
169 log.error(traceback.format_exc())
170 return HTTPInternalServerError()(environ,
170 return HTTPInternalServerError()(environ,
171 start_response)
171 start_response)
172
172
173 #check permissions for this repository
173 #check permissions for this repository
174 perm = self._check_permission(action, user, repo_name)
174 perm = self._check_permission(action, user, repo_name)
175 if perm is not True:
175 if perm is not True:
176 return HTTPForbidden()(environ, start_response)
176 return HTTPForbidden()(environ, start_response)
177
177
178 #===================================================================
178 #===================================================================
179 # GIT REQUEST HANDLING
179 # GIT REQUEST HANDLING
180 #===================================================================
180 #===================================================================
181 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
181 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
182 log.debug('Repository path is %s' % repo_path)
182 log.debug('Repository path is %s' % repo_path)
183
183
184 # quick check if that dir exists...
184 # quick check if that dir exists...
185 if is_valid_repo(repo_name, self.basepath) is False:
185 if is_valid_repo(repo_name, self.basepath) is False:
186 return HTTPNotFound()(environ, start_response)
186 return HTTPNotFound()(environ, start_response)
187
187
188 try:
188 try:
189 #invalidate cache on push
189 #invalidate cache on push
190 if action == 'push':
190 if action == 'push':
191 self._invalidate_cache(repo_name)
191 self._invalidate_cache(repo_name)
192 log.info('%s action on GIT repo "%s"' % (action, repo_name))
192 log.info('%s action on GIT repo "%s"' % (action, repo_name))
193 app = self.__make_app(repo_name, repo_path)
193 app = self.__make_app(repo_name, repo_path)
194 return app(environ, start_response)
194 return app(environ, start_response)
195 except Exception:
195 except Exception:
196 log.error(traceback.format_exc())
196 log.error(traceback.format_exc())
197 return HTTPInternalServerError()(environ, start_response)
197 return HTTPInternalServerError()(environ, start_response)
198
198
199 def __make_app(self, repo_name, repo_path):
199 def __make_app(self, repo_name, repo_path):
200 """
200 """
201 Make an wsgi application using dulserver
201 Make an wsgi application using dulserver
202
202
203 :param repo_name: name of the repository
203 :param repo_name: name of the repository
204 :param repo_path: full path to the repository
204 :param repo_path: full path to the repository
205 """
205 """
206 _d = {'/' + repo_name: Repo(repo_path)}
206 _d = {'/' + repo_name: Repo(repo_path)}
207 backend = dulserver.DictBackend(_d)
207 backend = dulserver.DictBackend(_d)
208 gitserve = HTTPGitApplication(backend)
208 gitserve = make_wsgi_chain(backend)
209
209
210 return gitserve
210 return gitserve
211
211
212 def __get_repository(self, environ):
212 def __get_repository(self, environ):
213 """
213 """
214 Get's repository name out of PATH_INFO header
214 Get's repository name out of PATH_INFO header
215
215
216 :param environ: environ where PATH_INFO is stored
216 :param environ: environ where PATH_INFO is stored
217 """
217 """
218 try:
218 try:
219 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
219 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
220 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
220 repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
221 except:
221 except:
222 log.error(traceback.format_exc())
222 log.error(traceback.format_exc())
223 raise
223 raise
224
224
225 return repo_name
225 return repo_name
226
226
227 def __get_user(self, username):
227 def __get_user(self, username):
228 return User.get_by_username(username)
228 return User.get_by_username(username)
229
229
230 def __get_action(self, environ):
230 def __get_action(self, environ):
231 """
231 """
232 Maps git request commands into a pull or push command.
232 Maps git request commands into a pull or push command.
233
233
234 :param environ:
234 :param environ:
235 """
235 """
236 service = environ['QUERY_STRING'].split('=')
236 service = environ['QUERY_STRING'].split('=')
237
237
238 if len(service) > 1:
238 if len(service) > 1:
239 service_cmd = service[1]
239 service_cmd = service[1]
240 mapping = {
240 mapping = {
241 'git-receive-pack': 'push',
241 'git-receive-pack': 'push',
242 'git-upload-pack': 'pull',
242 'git-upload-pack': 'pull',
243 }
243 }
244 op = mapping[service_cmd]
244 op = mapping[service_cmd]
245 self._git_stored_op = op
245 self._git_stored_op = op
246 return op
246 return op
247 else:
247 else:
248 # try to fallback to stored variable as we don't know if the last
248 # try to fallback to stored variable as we don't know if the last
249 # operation is pull/push
249 # operation is pull/push
250 op = getattr(self, '_git_stored_op', 'pull')
250 op = getattr(self, '_git_stored_op', 'pull')
251 return op
251 return op
General Comments 0
You need to be logged in to leave comments. Login now