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