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