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