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