##// END OF EJS Templates
make get_action always return action
marcink -
r2501:044c31d6 beta
parent child Browse files
Show More
@@ -1,255 +1,255
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). 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 logging
28 import logging
29 import traceback
29 import traceback
30 import urllib
30 import urllib
31
31
32 from mercurial.error import RepoError
32 from mercurial.error import RepoError
33 from mercurial.hgweb import hgweb_mod
33 from mercurial.hgweb import hgweb_mod
34
34
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36
36
37 from rhodecode.lib.utils2 import safe_str
37 from rhodecode.lib.utils2 import safe_str
38 from rhodecode.lib.base import BaseVCSController
38 from rhodecode.lib.base import BaseVCSController
39 from rhodecode.lib.auth import get_container_username
39 from rhodecode.lib.auth import get_container_username
40 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
40 from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
41 from rhodecode.model.db import User
41 from rhodecode.model.db import User
42
42
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 def is_mercurial(environ):
48 def is_mercurial(environ):
49 """
49 """
50 Returns True if request's target is mercurial server - header
50 Returns True if request's target is mercurial server - header
51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
52 """
52 """
53 http_accept = environ.get('HTTP_ACCEPT')
53 http_accept = environ.get('HTTP_ACCEPT')
54 path_info = environ['PATH_INFO']
54 path_info = environ['PATH_INFO']
55 if http_accept and http_accept.startswith('application/mercurial'):
55 if http_accept and http_accept.startswith('application/mercurial'):
56 ishg_path = True
56 ishg_path = True
57 else:
57 else:
58 ishg_path = False
58 ishg_path = False
59
59
60 log.debug('pathinfo: %s detected as HG %s' % (
60 log.debug('pathinfo: %s detected as HG %s' % (
61 path_info, ishg_path)
61 path_info, ishg_path)
62 )
62 )
63 return ishg_path
63 return ishg_path
64
64
65
65
66 class SimpleHg(BaseVCSController):
66 class SimpleHg(BaseVCSController):
67
67
68 def _handle_request(self, environ, start_response):
68 def _handle_request(self, environ, start_response):
69 if not is_mercurial(environ):
69 if not is_mercurial(environ):
70 return self.application(environ, start_response)
70 return self.application(environ, start_response)
71
71
72 ipaddr = self._get_ip_addr(environ)
72 ipaddr = self._get_ip_addr(environ)
73 username = None
73 username = None
74 # skip passing error to error controller
74 # skip passing error to error controller
75 environ['pylons.status_code_redirect'] = True
75 environ['pylons.status_code_redirect'] = True
76
76
77 #======================================================================
77 #======================================================================
78 # EXTRACT REPOSITORY NAME FROM ENV
78 # EXTRACT REPOSITORY NAME FROM ENV
79 #======================================================================
79 #======================================================================
80 try:
80 try:
81 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
81 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
82 log.debug('Extracted repo name is %s' % repo_name)
82 log.debug('Extracted repo name is %s' % repo_name)
83 except:
83 except:
84 return HTTPInternalServerError()(environ, start_response)
84 return HTTPInternalServerError()(environ, start_response)
85
85
86 # quick check if that dir exists...
86 # quick check if that dir exists...
87 if is_valid_repo(repo_name, self.basepath) is False:
87 if is_valid_repo(repo_name, self.basepath) is False:
88 return HTTPNotFound()(environ, start_response)
88 return HTTPNotFound()(environ, start_response)
89
89
90 #======================================================================
90 #======================================================================
91 # GET ACTION PULL or PUSH
91 # GET ACTION PULL or PUSH
92 #======================================================================
92 #======================================================================
93 action = self.__get_action(environ)
93 action = self.__get_action(environ)
94
94
95 #======================================================================
95 #======================================================================
96 # CHECK ANONYMOUS PERMISSION
96 # CHECK ANONYMOUS PERMISSION
97 #======================================================================
97 #======================================================================
98 if action in ['pull', 'push']:
98 if action in ['pull', 'push']:
99 anonymous_user = self.__get_user('default')
99 anonymous_user = self.__get_user('default')
100 username = anonymous_user.username
100 username = anonymous_user.username
101 anonymous_perm = self._check_permission(action, anonymous_user,
101 anonymous_perm = self._check_permission(action, anonymous_user,
102 repo_name)
102 repo_name)
103
103
104 if anonymous_perm is not True or anonymous_user.active is False:
104 if anonymous_perm is not True or anonymous_user.active is False:
105 if anonymous_perm is not True:
105 if anonymous_perm is not True:
106 log.debug('Not enough credentials to access this '
106 log.debug('Not enough credentials to access this '
107 'repository as anonymous user')
107 'repository as anonymous user')
108 if anonymous_user.active is False:
108 if anonymous_user.active is False:
109 log.debug('Anonymous access is disabled, running '
109 log.debug('Anonymous access is disabled, running '
110 'authentication')
110 'authentication')
111 #==============================================================
111 #==============================================================
112 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
112 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
113 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
113 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
114 #==============================================================
114 #==============================================================
115
115
116 # Attempting to retrieve username from the container
116 # Attempting to retrieve username from the container
117 username = get_container_username(environ, self.config)
117 username = get_container_username(environ, self.config)
118
118
119 # If not authenticated by the container, running basic auth
119 # If not authenticated by the container, running basic auth
120 if not username:
120 if not username:
121 self.authenticate.realm = \
121 self.authenticate.realm = \
122 safe_str(self.config['rhodecode_realm'])
122 safe_str(self.config['rhodecode_realm'])
123 result = self.authenticate(environ)
123 result = self.authenticate(environ)
124 if isinstance(result, str):
124 if isinstance(result, str):
125 AUTH_TYPE.update(environ, 'basic')
125 AUTH_TYPE.update(environ, 'basic')
126 REMOTE_USER.update(environ, result)
126 REMOTE_USER.update(environ, result)
127 username = result
127 username = result
128 else:
128 else:
129 return result.wsgi_application(environ, start_response)
129 return result.wsgi_application(environ, start_response)
130
130
131 #==============================================================
131 #==============================================================
132 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
132 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
133 #==============================================================
133 #==============================================================
134 try:
134 try:
135 user = self.__get_user(username)
135 user = self.__get_user(username)
136 if user is None or not user.active:
136 if user is None or not user.active:
137 return HTTPForbidden()(environ, start_response)
137 return HTTPForbidden()(environ, start_response)
138 username = user.username
138 username = user.username
139 except:
139 except:
140 log.error(traceback.format_exc())
140 log.error(traceback.format_exc())
141 return HTTPInternalServerError()(environ, start_response)
141 return HTTPInternalServerError()(environ, start_response)
142
142
143 #check permissions for this repository
143 #check permissions for this repository
144 perm = self._check_permission(action, user, repo_name)
144 perm = self._check_permission(action, user, repo_name)
145 if perm is not True:
145 if perm is not True:
146 return HTTPForbidden()(environ, start_response)
146 return HTTPForbidden()(environ, start_response)
147
147
148 # extras are injected into mercurial UI object and later available
148 # extras are injected into mercurial UI object and later available
149 # in hg hooks executed by rhodecode
149 # in hg hooks executed by rhodecode
150 extras = {
150 extras = {
151 'ip': ipaddr,
151 'ip': ipaddr,
152 'username': username,
152 'username': username,
153 'action': action,
153 'action': action,
154 'repository': repo_name,
154 'repository': repo_name,
155 'scm': 'hg',
155 'scm': 'hg',
156 }
156 }
157
157
158 #======================================================================
158 #======================================================================
159 # MERCURIAL REQUEST HANDLING
159 # MERCURIAL REQUEST HANDLING
160 #======================================================================
160 #======================================================================
161 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
161 repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
162 log.debug('Repository path is %s' % repo_path)
162 log.debug('Repository path is %s' % repo_path)
163
163
164 baseui = make_ui('db')
164 baseui = make_ui('db')
165 self.__inject_extras(repo_path, baseui, extras)
165 self.__inject_extras(repo_path, baseui, extras)
166
166
167 try:
167 try:
168 # invalidate cache on push
168 # invalidate cache on push
169 if action == 'push':
169 if action == 'push':
170 self._invalidate_cache(repo_name)
170 self._invalidate_cache(repo_name)
171 log.info('%s action on HG repo "%s"' % (action, repo_name))
171 log.info('%s action on HG repo "%s"' % (action, repo_name))
172 app = self.__make_app(repo_path, baseui, extras)
172 app = self.__make_app(repo_path, baseui, extras)
173 return app(environ, start_response)
173 return app(environ, start_response)
174 except RepoError, e:
174 except RepoError, e:
175 if str(e).find('not found') != -1:
175 if str(e).find('not found') != -1:
176 return HTTPNotFound()(environ, start_response)
176 return HTTPNotFound()(environ, start_response)
177 except Exception:
177 except Exception:
178 log.error(traceback.format_exc())
178 log.error(traceback.format_exc())
179 return HTTPInternalServerError()(environ, start_response)
179 return HTTPInternalServerError()(environ, start_response)
180
180
181 def __make_app(self, repo_name, baseui, extras):
181 def __make_app(self, repo_name, baseui, extras):
182 """
182 """
183 Make an wsgi application using hgweb, and inject generated baseui
183 Make an wsgi application using hgweb, and inject generated baseui
184 instance, additionally inject some extras into ui object
184 instance, additionally inject some extras into ui object
185 """
185 """
186 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
186 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
187
187
188 def __get_repository(self, environ):
188 def __get_repository(self, environ):
189 """
189 """
190 Get's repository name out of PATH_INFO header
190 Get's repository name out of PATH_INFO header
191
191
192 :param environ: environ where PATH_INFO is stored
192 :param environ: environ where PATH_INFO is stored
193 """
193 """
194 try:
194 try:
195 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
195 environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
196 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
196 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
197 if repo_name.endswith('/'):
197 if repo_name.endswith('/'):
198 repo_name = repo_name.rstrip('/')
198 repo_name = repo_name.rstrip('/')
199 except:
199 except:
200 log.error(traceback.format_exc())
200 log.error(traceback.format_exc())
201 raise
201 raise
202
202
203 return repo_name
203 return repo_name
204
204
205 def __get_user(self, username):
205 def __get_user(self, username):
206 return User.get_by_username(username)
206 return User.get_by_username(username)
207
207
208 def __get_action(self, environ):
208 def __get_action(self, environ):
209 """
209 """
210 Maps mercurial request commands into a clone,pull or push command.
210 Maps mercurial request commands into a clone,pull or push command.
211 This should always return a valid command string
211 This should always return a valid command string
212
212
213 :param environ:
213 :param environ:
214 """
214 """
215 mapping = {'changegroup': 'pull',
215 mapping = {'changegroup': 'pull',
216 'changegroupsubset': 'pull',
216 'changegroupsubset': 'pull',
217 'stream_out': 'pull',
217 'stream_out': 'pull',
218 'listkeys': 'pull',
218 'listkeys': 'pull',
219 'unbundle': 'push',
219 'unbundle': 'push',
220 'pushkey': 'push', }
220 'pushkey': 'push', }
221 for qry in environ['QUERY_STRING'].split('&'):
221 for qry in environ['QUERY_STRING'].split('&'):
222 if qry.startswith('cmd'):
222 if qry.startswith('cmd'):
223 cmd = qry.split('=')[-1]
223 cmd = qry.split('=')[-1]
224 if cmd in mapping:
224 if cmd in mapping:
225 return mapping[cmd]
225 return mapping[cmd]
226 else:
226
227 return 'pull'
227 return 'pull'
228
228
229 def __inject_extras(self, repo_path, baseui, extras={}):
229 def __inject_extras(self, repo_path, baseui, extras={}):
230 """
230 """
231 Injects some extra params into baseui instance
231 Injects some extra params into baseui instance
232
232
233 also overwrites global settings with those takes from local hgrc file
233 also overwrites global settings with those takes from local hgrc file
234
234
235 :param baseui: baseui instance
235 :param baseui: baseui instance
236 :param extras: dict with extra params to put into baseui
236 :param extras: dict with extra params to put into baseui
237 """
237 """
238
238
239 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
239 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
240
240
241 # make our hgweb quiet so it doesn't print output
241 # make our hgweb quiet so it doesn't print output
242 baseui.setconfig('ui', 'quiet', 'true')
242 baseui.setconfig('ui', 'quiet', 'true')
243
243
244 #inject some additional parameters that will be available in ui
244 #inject some additional parameters that will be available in ui
245 #for hooks
245 #for hooks
246 for k, v in extras.items():
246 for k, v in extras.items():
247 baseui.setconfig('rhodecode_extras', k, v)
247 baseui.setconfig('rhodecode_extras', k, v)
248
248
249 repoui = make_ui('file', hgrc, False)
249 repoui = make_ui('file', hgrc, False)
250
250
251 if repoui:
251 if repoui:
252 #overwrite our ui instance with the section from hgrc file
252 #overwrite our ui instance with the section from hgrc file
253 for section in ui_sections:
253 for section in ui_sections:
254 for k, v in repoui.configitems(section):
254 for k, v in repoui.configitems(section):
255 baseui.setconfig(section, k, v)
255 baseui.setconfig(section, k, v)
General Comments 0
You need to be logged in to leave comments. Login now