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