##// END OF EJS Templates
Use space after , in lists
Mads Kiilerich -
r3987:b58ed6d6 default
parent child Browse files
Show More
@@ -1,348 +1,348
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 import logging
6 6 import time
7 7 import traceback
8 8
9 9 from paste.auth.basic import AuthBasicAuthenticator
10 10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
12 12
13 13 from pylons import config, tmpl_context as c, request, session, url
14 14 from pylons.controllers import WSGIController
15 15 from pylons.controllers.util import redirect
16 16 from pylons.templating import render_mako as render
17 17
18 18 from rhodecode import __version__, BACKENDS
19 19
20 20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
21 21 safe_str, safe_int
22 22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
23 23 HasPermissionAnyMiddleware, CookieStoreWrapper
24 24 from rhodecode.lib.utils import get_repo_slug
25 25 from rhodecode.model import meta
26 26
27 27 from rhodecode.model.db import Repository, RhodeCodeUi, User, RhodeCodeSetting
28 28 from rhodecode.model.notification import NotificationModel
29 29 from rhodecode.model.scm import ScmModel
30 30 from rhodecode.model.meta import Session
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 def _filter_proxy(ip):
36 36 """
37 HEADERS can have mutliple ips inside the left-most being the original
37 HEADERS can have multiple ips inside the left-most being the original
38 38 client, and each successive proxy that passed the request adding the IP
39 39 address where it received the request from.
40 40
41 41 :param ip:
42 42 """
43 43 if ',' in ip:
44 44 _ips = ip.split(',')
45 45 _first_ip = _ips[0].strip()
46 46 log.debug('Got multiple IPs %s, using %s' % (','.join(_ips), _first_ip))
47 47 return _first_ip
48 48 return ip
49 49
50 50
51 51 def _get_ip_addr(environ):
52 52 proxy_key = 'HTTP_X_REAL_IP'
53 53 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
54 54 def_key = 'REMOTE_ADDR'
55 55
56 56 ip = environ.get(proxy_key)
57 57 if ip:
58 58 return _filter_proxy(ip)
59 59
60 60 ip = environ.get(proxy_key2)
61 61 if ip:
62 62 return _filter_proxy(ip)
63 63
64 64 ip = environ.get(def_key, '0.0.0.0')
65 65 return _filter_proxy(ip)
66 66
67 67
68 68 def _get_access_path(environ):
69 69 path = environ.get('PATH_INFO')
70 70 org_req = environ.get('pylons.original_request')
71 71 if org_req:
72 72 path = org_req.environ.get('PATH_INFO')
73 73 return path
74 74
75 75
76 76 class BasicAuth(AuthBasicAuthenticator):
77 77
78 78 def __init__(self, realm, authfunc, auth_http_code=None):
79 79 self.realm = realm
80 80 self.authfunc = authfunc
81 81 self._rc_auth_http_code = auth_http_code
82 82
83 83 def build_authentication(self):
84 84 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
85 85 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
86 86 # return 403 if alternative http return code is specified in
87 87 # RhodeCode config
88 88 return HTTPForbidden(headers=head)
89 89 return HTTPUnauthorized(headers=head)
90 90
91 91 def authenticate(self, environ):
92 92 authorization = AUTHORIZATION(environ)
93 93 if not authorization:
94 94 return self.build_authentication()
95 95 (authmeth, auth) = authorization.split(' ', 1)
96 96 if 'basic' != authmeth.lower():
97 97 return self.build_authentication()
98 98 auth = auth.strip().decode('base64')
99 99 _parts = auth.split(':', 1)
100 100 if len(_parts) == 2:
101 101 username, password = _parts
102 102 if self.authfunc(environ, username, password):
103 103 return username
104 104 return self.build_authentication()
105 105
106 106 __call__ = authenticate
107 107
108 108
109 109 class BaseVCSController(object):
110 110
111 111 def __init__(self, application, config):
112 112 self.application = application
113 113 self.config = config
114 114 # base path of repo locations
115 115 self.basepath = self.config['base_path']
116 116 #authenticate this mercurial request using authfunc
117 117 self.authenticate = BasicAuth('', authfunc,
118 118 config.get('auth_ret_code'))
119 119 self.ip_addr = '0.0.0.0'
120 120
121 121 def _handle_request(self, environ, start_response):
122 122 raise NotImplementedError()
123 123
124 124 def _get_by_id(self, repo_name):
125 125 """
126 126 Get's a special pattern _<ID> from clone url and tries to replace it
127 127 with a repository_name for support of _<ID> non changable urls
128 128
129 129 :param repo_name:
130 130 """
131 131 try:
132 132 data = repo_name.split('/')
133 133 if len(data) >= 2:
134 134 by_id = data[1].split('_')
135 135 if len(by_id) == 2 and by_id[1].isdigit():
136 136 _repo_name = Repository.get(by_id[1]).repo_name
137 137 data[1] = _repo_name
138 138 except Exception:
139 139 log.debug('Failed to extract repo_name from id %s' % (
140 140 traceback.format_exc()
141 141 )
142 142 )
143 143
144 144 return '/'.join(data)
145 145
146 146 def _invalidate_cache(self, repo_name):
147 147 """
148 148 Set's cache for this repository for invalidation on next access
149 149
150 150 :param repo_name: full repo name, also a cache key
151 151 """
152 152 ScmModel().mark_for_invalidation(repo_name)
153 153
154 154 def _check_permission(self, action, user, repo_name, ip_addr=None):
155 155 """
156 156 Checks permissions using action (push/pull) user and repository
157 157 name
158 158
159 159 :param action: push or pull action
160 160 :param user: user instance
161 161 :param repo_name: repository name
162 162 """
163 163 #check IP
164 164 authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr)
165 165 if not authuser.ip_allowed:
166 166 return False
167 167 else:
168 168 log.info('Access for IP:%s allowed' % (ip_addr))
169 169 if action == 'push':
170 170 if not HasPermissionAnyMiddleware('repository.write',
171 171 'repository.admin')(user,
172 172 repo_name):
173 173 return False
174 174
175 175 else:
176 176 #any other action need at least read permission
177 177 if not HasPermissionAnyMiddleware('repository.read',
178 178 'repository.write',
179 179 'repository.admin')(user,
180 180 repo_name):
181 181 return False
182 182
183 183 return True
184 184
185 185 def _get_ip_addr(self, environ):
186 186 return _get_ip_addr(environ)
187 187
188 188 def _check_ssl(self, environ, start_response):
189 189 """
190 190 Checks the SSL check flag and returns False if SSL is not present
191 191 and required True otherwise
192 192 """
193 193 org_proto = environ['wsgi._org_proto']
194 194 #check if we have SSL required ! if not it's a bad request !
195 195 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
196 196 if require_ssl and org_proto == 'http':
197 197 log.debug('proto is %s and SSL is required BAD REQUEST !'
198 198 % org_proto)
199 199 return False
200 200 return True
201 201
202 202 def _check_locking_state(self, environ, action, repo, user_id):
203 203 """
204 204 Checks locking on this repository, if locking is enabled and lock is
205 205 present returns a tuple of make_lock, locked, locked_by.
206 206 make_lock can have 3 states None (do nothing) True, make lock
207 207 False release lock, This value is later propagated to hooks, which
208 208 do the locking. Think about this as signals passed to hooks what to do.
209 209
210 210 """
211 211 locked = False # defines that locked error should be thrown to user
212 212 make_lock = None
213 213 repo = Repository.get_by_repo_name(repo)
214 214 user = User.get(user_id)
215 215
216 216 # this is kind of hacky, but due to how mercurial handles client-server
217 217 # server see all operation on changeset; bookmarks, phases and
218 218 # obsolescence marker in different transaction, we don't want to check
219 219 # locking on those
220 220 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
221 221 locked_by = repo.locked
222 222 if repo and repo.enable_locking and not obsolete_call:
223 223 if action == 'push':
224 224 #check if it's already locked !, if it is compare users
225 225 user_id, _date = repo.locked
226 226 if user.user_id == user_id:
227 227 log.debug('Got push from user %s, now unlocking' % (user))
228 228 # unlock if we have push from user who locked
229 229 make_lock = False
230 230 else:
231 231 # we're not the same user who locked, ban with 423 !
232 232 locked = True
233 233 if action == 'pull':
234 234 if repo.locked[0] and repo.locked[1]:
235 235 locked = True
236 236 else:
237 237 log.debug('Setting lock on repo %s by %s' % (repo, user))
238 238 make_lock = True
239 239
240 240 else:
241 241 log.debug('Repository %s do not have locking enabled' % (repo))
242 242 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
243 243 % (make_lock, locked, locked_by))
244 244 return make_lock, locked, locked_by
245 245
246 246 def __call__(self, environ, start_response):
247 247 start = time.time()
248 248 try:
249 249 return self._handle_request(environ, start_response)
250 250 finally:
251 251 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
252 252 log.debug('Request time: %.3fs' % (time.time() - start))
253 253 meta.Session.remove()
254 254
255 255
256 256 class BaseController(WSGIController):
257 257
258 258 def __before__(self):
259 259 """
260 260 __before__ is called before controller methods and after __call__
261 261 """
262 262 c.rhodecode_version = __version__
263 263 c.rhodecode_instanceid = config.get('instance_id')
264 264 c.rhodecode_name = config.get('rhodecode_title')
265 265 c.use_gravatar = str2bool(config.get('use_gravatar'))
266 266 c.ga_code = config.get('rhodecode_ga_code')
267 267 # Visual options
268 268 c.visual = AttributeDict({})
269 269 rc_config = RhodeCodeSetting.get_app_settings()
270 270 ## DB stored
271 271 c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon'))
272 272 c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon'))
273 273 c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags'))
274 274 c.visual.dashboard_items = safe_int(rc_config.get('rhodecode_dashboard_items', 100))
275 275 c.visual.repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
276 276 c.visual.show_version = str2bool(rc_config.get('rhodecode_show_version'))
277 277
278 278 ## INI stored
279 279 self.cut_off_limit = int(config.get('cut_off_limit'))
280 280 c.visual.allow_repo_location_change = str2bool(config.get('allow_repo_location_change', True))
281 281
282 282 c.repo_name = get_repo_slug(request) # can be empty
283 283 c.backends = BACKENDS.keys()
284 284 c.unread_notifications = NotificationModel()\
285 285 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
286 286 self.sa = meta.Session
287 287 self.scm_model = ScmModel(self.sa)
288 288
289 289 def __call__(self, environ, start_response):
290 290 """Invoke the Controller"""
291 291 # WSGIController.__call__ dispatches to the Controller method
292 292 # the request is routed to. This routing information is
293 293 # available in environ['pylons.routes_dict']
294 294 try:
295 295 self.ip_addr = _get_ip_addr(environ)
296 296 # make sure that we update permissions each time we call controller
297 297 api_key = request.GET.get('api_key')
298 298 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
299 299 user_id = cookie_store.get('user_id', None)
300 300 username = get_container_username(environ, config)
301 301 auth_user = AuthUser(user_id, api_key, username, self.ip_addr)
302 302 request.user = auth_user
303 303 self.rhodecode_user = c.rhodecode_user = auth_user
304 304 if not self.rhodecode_user.is_authenticated and \
305 305 self.rhodecode_user.user_id is not None:
306 306 self.rhodecode_user.set_authenticated(
307 307 cookie_store.get('is_authenticated')
308 308 )
309 309 log.info('IP: %s User: %s accessed %s' % (
310 310 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
311 311 )
312 312 return WSGIController.__call__(self, environ, start_response)
313 313 finally:
314 314 meta.Session.remove()
315 315
316 316
317 317 class BaseRepoController(BaseController):
318 318 """
319 319 Base class for controllers responsible for loading all needed data for
320 320 repository loaded items are
321 321
322 322 c.rhodecode_repo: instance of scm repository
323 323 c.rhodecode_db_repo: instance of db
324 324 c.repository_followers: number of followers
325 325 c.repository_forks: number of forks
326 326 c.repository_following: weather the current user is following the current repo
327 327 """
328 328
329 329 def __before__(self):
330 330 super(BaseRepoController, self).__before__()
331 331 if c.repo_name:
332 332
333 333 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
334 334 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
335 335 # update last change according to VCS data
336 336 dbr.update_changeset_cache(dbr.get_changeset())
337 337 if c.rhodecode_repo is None:
338 338 log.error('%s this repository is present in database but it '
339 339 'cannot be created as an scm instance', c.repo_name)
340 340
341 341 redirect(url('home'))
342 342
343 343 # some globals counter for menu
344 344 c.repository_followers = self.scm_model.get_followers(dbr)
345 345 c.repository_forks = self.scm_model.get_forks(dbr)
346 346 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
347 347 c.repository_following = self.scm_model.is_following_repo(c.repo_name,
348 348 self.rhodecode_user.user_id)
General Comments 0
You need to be logged in to leave comments. Login now