Package proxy :: Package broker :: Module rhnBroker
[hide private]
[frames] | no frames]

Source Code for Module proxy.broker.rhnBroker

  1  # Spacewalk Proxy Server Broker handler code. 
  2  # 
  3  # Copyright (c) 2008--2020 Red Hat, Inc. 
  4  # 
  5  # This software is licensed to you under the GNU General Public License, 
  6  # version 2 (GPLv2). There is NO WARRANTY for this software, express or 
  7  # implied, including the implied warranties of MERCHANTABILITY or FITNESS 
  8  # FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 
  9  # along with this software; if not, see 
 10  # http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. 
 11  # 
 12  # Red Hat trademarks are not licensed under GPLv2. No permission is 
 13  # granted to use or replicate Red Hat trademarks that are incorporated 
 14  # in this software or its documentation. 
 15  # 
 16   
 17  # system module imports 
 18  import time 
 19  import socket 
 20  import re 
 21  from urlparse import urlparse, urlunparse 
 22   
 23  # common module imports 
 24  from rhn.UserDictCase import UserDictCase 
 25  from spacewalk.common.rhnLib import parseUrl 
 26  from spacewalk.common.rhnConfig import CFG 
 27  from spacewalk.common.rhnLog import log_debug, log_error 
 28  from spacewalk.common.rhnException import rhnFault 
 29  from spacewalk.common import rhnFlags, apache 
 30  from spacewalk.common.rhnTranslate import _ 
 31   
 32  # local module imports 
 33  from proxy.rhnShared import SharedHandler 
 34  from proxy.rhnConstants import URI_PREFIX_KS_CHECKSUM 
 35  import rhnRepository 
 36  import proxy.rhnProxyAuth 
 37   
 38   
 39  # the version should not be never decreased, never mind that spacewalk has different versioning 
 40  _PROXY_VERSION = '5.5.0' 
41 # HISTORY: '0.9.7', '3.2.0', '3.5.0', '3.6.0', '4.1.0', 42 # '4.2.0', '5.0.0', '5.1.0', '5.2.0', '0.1', 43 # '5.3.0', '5.3.1', '5.4.0', '5.5.0' 44 45 46 -class BrokerHandler(SharedHandler):
47 48 """ Spacewalk Proxy broker specific handler code called by rhnApache. 49 50 Workflow is: 51 Client -> Apache:Broker -> Squid -> Apache:Redirect -> Satellite 52 53 Broker handler get request from clients from outside. Some request 54 (POST and HEAD) bypass cache so, it is passed directly to parent. 55 For everything else we transform destination to localhost:80 (which 56 is handled by Redirect handler) and set proxy as local squid. 57 This way we got all request cached localy by squid. 58 """ 59 60 # pylint: disable=R0902
61 - def __init__(self, req):
62 SharedHandler.__init__(self, req) 63 64 # Initialize variables 65 self.componentType = 'proxy.broker' 66 self.cachedClientInfo = None # headers - session token 67 self.authChannels = None 68 self.clientServerId = None 69 self.rhnParentXMLRPC = None 70 hostname = '' 71 # should *always* exist and be my ip address 72 my_ip_addr = req.headers_in['SERVER_ADDR'] 73 if req.headers_in.has_key('Host'): 74 # the client has provided a host header 75 try: 76 # When a client with python 2.4 (RHEL 5) uses SSL 77 # the host header is in the 'hostname:port' form 78 # (In python 2.6 RFE #1472176 changed this and 'hostname' 79 # is used). We need to use the 'hostname' part in any case 80 # or we create bogus 'hostname:port' DNS queries 81 host_header = req.headers_in['Host'].split(':')[0] 82 if socket.gethostbyname(host_header) == my_ip_addr: 83 # if host header is valid (i.e. not just an /etc/hosts 84 # entry on the client or the hostname of some other 85 # machine (say a load balancer)) then use it 86 hostname = host_header 87 except (socket.gaierror, socket.error, 88 socket.herror, socket.timeout): 89 # hostname probably didn't exist, fine 90 pass 91 if not hostname: 92 # okay, that didn't work, let's do a reverse dns lookup on my 93 # ip address 94 try: 95 hostname = socket.gethostbyaddr(my_ip_addr)[0] 96 except (socket.gaierror, socket.error, 97 socket.herror, socket.timeout): 98 # unknown host, we don't have a hostname? 99 pass 100 if not hostname: 101 # this shouldn't happen 102 # socket.gethostname is a punt. Shouldn't need to do it. 103 hostname = socket.gethostname() 104 log_debug(-1, 'WARNING: no hostname in the incoming headers; ' 105 'punting: %s' % hostname) 106 hostname = parseUrl(hostname)[1].split(':')[0] 107 self.proxyAuth = proxy.rhnProxyAuth.get_proxy_auth(hostname) 108 109 self._initConnectionVariables(req)
110
111 - def _initConnectionVariables(self, req):
112 """ set connection variables 113 NOTE: self.{caChain,rhnParent,httpProxy*} are initialized 114 in SharedHandler 115 116 rules: 117 - GET requests: 118 . are non-SSLed (potentially SSLed by the redirect) 119 . use the local cache 120 . CFG.HTTP_PROXY or CFG.USE_SSL: 121 . use the SSL Redirect 122 (i.e., parent is now 127.0.0.1) 123 . NOTE: the reason we use the SSL Redirect if we 124 are going through an outside HTTP_PROXY: 125 o CFG.HTTP_PROXY is ONLY used by an SSL 126 redirect - maybe should rethink that. 127 . not CFG.USE_SSL and not CFG.HTTP_PROXY: 128 . bypass the SSL Redirect (performance) 129 - POST and HEAD requests (not GET) bypass both the local cache 130 and SSL redirect (we SSL it directly) 131 """ 132 133 scheme = 'http' 134 # self.{caChain,httpProxy*,rhnParent} initialized in rhnShared.py 135 effectiveURI = self._getEffectiveURI() 136 effectiveURI_parts = urlparse(effectiveURI) 137 # Fixup effectiveURI_parts, if effectiveURI is dirty. 138 # We are doing this because the ubuntu clients request uris like 139 # 'http://hostname//XMLRPC...'. See bug 1220399 for details. 140 if not effectiveURI_parts.scheme and effectiveURI_parts.netloc and effectiveURI_parts.netloc == 'XMLRPC': 141 effectiveURI_parts = urlparse(urlunparse([ 142 '', 143 '', 144 '/' + effectiveURI_parts.netloc + effectiveURI_parts.path, 145 effectiveURI_parts.params, 146 effectiveURI_parts.query, 147 effectiveURI_parts.fragment])) 148 149 if req.method == 'GET': 150 scheme = 'http' 151 self.httpProxy = CFG.SQUID 152 self.caChain = self.httpProxyUsername = self.httpProxyPassword = '' 153 if CFG.HTTP_PROXY or CFG.USE_SSL or re.search('^' + URI_PREFIX_KS_CHECKSUM, effectiveURI_parts[2]): 154 # o if we need to go through an outside HTTP proxy, use the 155 # redirect 156 # o if an SSL request, use the redirect 157 # o otherwise (non-ssl and not going through an outside HTTP 158 # proxy) bypass that redirect for performance 159 self.rhnParent = self.proxyAuth.hostname 160 else: 161 # !GET: bypass cache, bypass redirect 162 if CFG.USE_SSL: 163 scheme = 'https' 164 else: 165 scheme = 'http' 166 self.caChain = '' 167 168 self.rhnParentXMLRPC = urlunparse((scheme, self.rhnParent, '/XMLRPC', '', '', '')) 169 self.rhnParent = urlunparse((scheme, self.rhnParent) + effectiveURI_parts[2:]) 170 171 log_debug(2, 'set self.rhnParent: %s' % self.rhnParent) 172 log_debug(2, 'set self.rhnParentXMLRPC: %s' % self.rhnParentXMLRPC) 173 if self.httpProxy: 174 if self.httpProxyUsername and self.httpProxyPassword: 175 log_debug(2, 'using self.httpProxy: %s (authenticating)' % self.httpProxy) 176 else: 177 log_debug(2, 'using self.httpProxy: %s (non-authenticating)' % self.httpProxy) 178 else: 179 log_debug(2, '*not* using an http proxy')
180
181 - def handler(self):
182 """ Main handler to handle all requests pumped through this server. """ 183 184 # pylint: disable=R0915 185 log_debug(1) 186 self._prepHandler() 187 188 _oto = rhnFlags.get('outputTransportOptions') 189 190 # tell parent that we can follow redirects, even if client is not able to 191 _oto['X-RHN-Transport-Capability'] = "follow-redirects=3" 192 193 # No reason to put Host: in the header, the connection object will 194 # do that for us 195 196 # Add/modify the X-RHN-IP-Path header. 197 ip_path = None 198 if 'X-RHN-IP-Path' in _oto: 199 ip_path = _oto['X-RHN-IP-Path'] 200 log_debug(4, "X-RHN-IP-Path is: %s" % repr(ip_path)) 201 client_ip = self.req.connection.remote_ip 202 if ip_path is None: 203 ip_path = client_ip 204 else: 205 ip_path += ',' + client_ip 206 _oto['X-RHN-IP-Path'] = ip_path 207 208 # NOTE: X-RHN-Proxy-Auth described in broker/rhnProxyAuth.py 209 if 'X-RHN-Proxy-Auth' in _oto: 210 log_debug(5, 'X-RHN-Proxy-Auth currently set to: %s' % repr(_oto['X-RHN-Proxy-Auth'])) 211 else: 212 log_debug(5, 'X-RHN-Proxy-Auth is not set') 213 214 if self.req.headers_in.has_key('X-RHN-Proxy-Auth'): 215 tokens = [] 216 if 'X-RHN-Proxy-Auth' in _oto: 217 tokens = _oto['X-RHN-Proxy-Auth'].split(',') 218 log_debug(5, 'Tokens: %s' % tokens) 219 220 # GETs: authenticate user, and service local GETs. 221 getResult = self.__local_GET_handler(self.req) 222 if getResult is not None: 223 # it's a GET request 224 return getResult 225 226 # 1. check cached version of the proxy login, 227 # snag token if there... 228 # if not... login... 229 # if good token, cache it. 230 # 2. push into headers. 231 authToken = self.proxyAuth.check_cached_token() 232 log_debug(5, 'Auth token for this machine only! %s' % authToken) 233 tokens = [] 234 235 _oto = rhnFlags.get('outputTransportOptions') 236 if _oto.has_key('X-RHN-Proxy-Auth'): 237 log_debug(5, ' (auth token prior): %s' % repr(_oto['X-RHN-Proxy-Auth'])) 238 tokens = _oto['X-RHN-Proxy-Auth'].split(',') 239 240 # list of tokens to be pushed into the headers. 241 tokens.append(authToken) 242 tokens = [t for t in tokens if t] 243 244 _oto['X-RHN-Proxy-Auth'] = ','.join(tokens) 245 log_debug(5, ' (auth token after): %s' 246 % repr(_oto['X-RHN-Proxy-Auth'])) 247 248 log_debug(3, 'Trying to connect to parent') 249 250 # Loops twice? Here's why: 251 # o If no errors, the loop is broken and we move on. 252 # o If an error, either we get a new token and try again, 253 # or we get a critical error and we fault. 254 for _i in range(2): 255 self._connectToParent() # part 1 256 257 log_debug(4, 'after _connectToParent') 258 # Add the proxy version 259 rhnFlags.get('outputTransportOptions')['X-RHN-Proxy-Version'] = str(_PROXY_VERSION) 260 261 status = self._serverCommo() # part 2 262 263 # check for proxy authentication blowup. 264 respHeaders = self.responseContext.getHeaders() 265 if not respHeaders or \ 266 not respHeaders.has_key('X-RHN-Proxy-Auth-Error'): 267 # No proxy auth errors 268 # XXX: need to verify that with respHeaders == 269 # None that is is correct logic. It should be -taw 270 break 271 272 error = str(respHeaders['X-RHN-Proxy-Auth-Error']).split(':')[0] 273 274 # If a proxy other than this one needs to update its auth token 275 # pass the error on up to it 276 if (respHeaders.has_key('X-RHN-Proxy-Auth-Origin') and 277 respHeaders['X-RHN-Proxy-Auth-Origin'] != self.proxyAuth.hostname): 278 break 279 280 # Expired/invalid auth token; go through the loop once again 281 if error == '1003': # invalid token 282 msg = "Spacewalk Proxy Session Token INVALID -- bad!" 283 log_error(msg) 284 log_debug(0, msg) 285 elif error == '1004': 286 log_debug(1, 287 "Spacewalk Proxy Session Token expired, acquiring new one.") 288 else: # this should never happen. 289 msg = "Spacewalk Proxy login failed, error code is %s" % error 290 log_error(msg) 291 log_debug(0, msg) 292 raise rhnFault(1000, 293 _("Spacewalk Proxy error (issues with proxy login). " 294 "Please contact your system administrator.")) 295 296 # Forced refresh of the proxy token 297 rhnFlags.get('outputTransportOptions')['X-RHN-Proxy-Auth'] = self.proxyAuth.check_cached_token(1) 298 else: # for 299 # The token could not be aquired 300 log_debug(0, "Unable to acquire proxy authentication token") 301 raise rhnFault(1000, 302 _("Spacewalk Proxy error (unable to acquire proxy auth token). " 303 "Please contact your system administrator.")) 304 305 # Support for yum byte-range 306 if (status != apache.OK) and (status != apache.HTTP_PARTIAL_CONTENT): 307 log_debug(1, "Leaving handler with status code %s" % status) 308 return status 309 310 self.__handleAction(self.responseContext.getHeaders()) 311 312 return self._clientCommo()
313
314 - def _prepHandler(self):
315 """ prep handler and check PROXY_AUTH's expiration. """ 316 SharedHandler._prepHandler(self)
317 318 @staticmethod
319 - def _split_ks_url(req):
320 """ read kickstart options from incoming url 321 URIs we care about look something like: 322 /ks/dist/session/2xfe7113bc89f359001280dee1f4a020bc/ 323 ks-rhel-x86_64-server-6-6.5/Packages/rhnsd-4.9.3-2.el6.x86_64.rpm 324 /ks/dist/ks-rhel-x86_64-server-6-6.5/Packages/ 325 rhnsd-4.9.3-2.el6.x86_64.rpm 326 /ks/dist/org/1/ks-rhel-x86_64-server-6-6.5/Packages/ 327 rhnsd-4.9.3-2.el6.x86_64.rpm 328 /ks/dist/ks-rhel-x86_64-server-6-6.5/child/sherr-child-1/Packages/ 329 rhnsd-4.9.3-2.el6.x86_64.rpm 330 """ 331 args = req.path_info.split('/') 332 params = {'child': None, 'session': None, 'orgId': None, 333 'file': args[-1]} 334 action = None 335 if args[2] == 'org': 336 params['orgId'] = args[3] 337 kickstart = args[4] 338 if args[5] == 'Packages': 339 action = 'getPackage' 340 elif args[2] == 'session': 341 params['session'] = args[3] 342 kickstart = args[4] 343 if args[5] == 'Packages': 344 action = 'getPackage' 345 elif args[3] == 'child': 346 params['child'] = args[4] 347 kickstart = args[2] 348 if args[5] == 'Packages': 349 action = 'getPackage' 350 else: 351 kickstart = args[2] 352 if args[3] == 'Packages': 353 action = 'getPackage' 354 return kickstart, action, params
355 356 @staticmethod
357 - def _split_url(req):
358 """ read url from incoming url and return (req_type, channel, action, params) 359 URI should look something like: 360 /GET-REQ/rhel-i386-server-5/getPackage/autofs-5.0.1-0.rc2.143.el5_5.6.i386.rpm 361 """ 362 args = req.path_info.split('/') 363 if len(args) < 5: 364 return (None, None, None, None) 365 366 return (args[1], args[2], args[3], args[4:])
367 368 # --- PRIVATE METHODS --- 369
370 - def __handleAction(self, headers):
371 log_debug(1) 372 # Check if proxy is interested in this action, and execute any 373 # action required: 374 if not headers.has_key('X-RHN-Action'): 375 # Don't know what to do 376 return 377 378 log_debug(2, "Action is %s" % headers['X-RHN-Action']) 379 # Now, is it a login? If so, cache the session token. 380 if headers['X-RHN-Action'] != 'login': 381 # Don't care 382 return 383 384 # A login. Cache the session token 385 self.__cacheClientSessionToken(headers)
386
387 - def __local_GET_handler(self, req):
388 """ GETs: authenticate user, and service local GETs. 389 if not a local fetch, return None 390 """ 391 392 log_debug(2, 'request method: %s' % req.method) 393 # Early test to check if this is a request the proxy can handle 394 # Can we serve this request? 395 if req.method != "GET" or not CFG.PKG_DIR: 396 # Don't know how to handle this 397 return None 398 399 # Tiny-url kickstart requests (for server kickstarts, aka not profiles) 400 # have been name munged and we've already sent a HEAD request to the 401 # Satellite to get a checksum for the rpm so we can find it in the 402 # squid cache. 403 # Original url looks like /ty/bSWE7qIq/Packages/policycoreutils-2.0.83 404 # -19.39.el6.x86_64.rpm which gets munged to be /ty-cksm/ddb43838ad58 405 # d74dc95badef543cd96459b8bb37ff559339de58ec8dbbd1f18b/Packages/polic 406 # ycoreutils-2.0.83-19.39.el6.x86_64.rpm 407 args = req.path_info.split('/') 408 # urlparse returns a ParseResult, index 2 is the path 409 if re.search('^' + URI_PREFIX_KS_CHECKSUM, urlparse(self.rhnParent)[2]): 410 # We *ONLY* locally cache RPMs for kickstarts 411 if len(args) < 3 or args[2] != 'Packages': 412 return None 413 req_type = 'tinyurl' 414 reqident = args[1] 415 reqaction = 'getPackage' 416 reqparams = [args[-1]] 417 self.cachedClientInfo = UserDictCase() 418 elif (len(args) > 3 and args[1] == 'dist'): 419 # This is a kickstart request 420 req_type = 'ks-dist' 421 reqident, reqaction, reqparams = self._split_ks_url(req) 422 self.cachedClientInfo = UserDictCase() 423 else: 424 # Some other type of request 425 (req_type, reqident, reqaction, reqparams) = self._split_url(req) 426 427 if req_type is None or (req_type not in 428 ['$RHN', 'GET-REQ', 'tinyurl', 'ks-dist']): 429 # not a traditional RHN GET (i.e., it is an arbitrary get) 430 # XXX: there has to be a more elegant way to do this 431 return None 432 433 # kickstarts don't auth... 434 if req_type in ['$RHN', 'GET-REQ']: 435 # --- AUTH. CHECK: 436 # Check client authentication. If not authenticated, throw 437 # an exception. 438 token = self.__getSessionToken() 439 self.__checkAuthSessionTokenCache(token, reqident) 440 441 # Is this channel local? 442 for ch in self.authChannels: 443 channel, _version, _isBaseChannel, isLocalChannel = ch[:4] 444 if channel == reqident and str(isLocalChannel) == '1': 445 # Local channel 446 break 447 else: 448 # Not a local channel 449 return None 450 451 # --- LOCAL GET: 452 localFlist = CFG.PROXY_LOCAL_FLIST or [] 453 454 if reqaction not in localFlist: 455 # Not an action we know how to handle 456 return None 457 458 # We have a match; we'll try to serve packages from the local 459 # repository 460 log_debug(3, "Retrieve from local repository.") 461 log_debug(3, req_type, reqident, reqaction, reqparams) 462 result = self.__callLocalRepository(req_type, reqident, reqaction, 463 reqparams) 464 if result is None: 465 log_debug(3, "Not available locally; will try higher up the chain.") 466 else: 467 # Signal that we have to XMLRPC encode the response in apacheHandler 468 rhnFlags.set("NeedEncoding", 1) 469 470 return result
471 472 @staticmethod
473 - def __getSessionToken():
474 """ Get/test-for session token in headers (rhnFlags) """ 475 log_debug(1) 476 if not rhnFlags.test("AUTH_SESSION_TOKEN"): 477 raise rhnFault(33, "Missing session token") 478 return rhnFlags.get("AUTH_SESSION_TOKEN")
479
480 - def __cacheClientSessionToken(self, headers):
481 """pull session token from headers and push to caching daemon. """ 482 483 log_debug(1) 484 # Get the server ID 485 if not headers.has_key('X-RHN-Server-ID'): 486 log_debug(3, "Client server ID not found in headers") 487 # XXX: no client server ID in headers, should we care? 488 #raise rhnFault(1000, _("Client Server ID not found in headers!")) 489 return None 490 serverId = 'X-RHN-Server-ID' 491 492 self.clientServerId = headers[serverId] 493 token = UserDictCase() 494 495 # The session token contains everything that begins with 496 # "x-rhn-auth" 497 prefix = "x-rhn-auth" 498 l = len(prefix) 499 tokenKeys = [x for x in headers.keys() if x[:l].lower() == prefix] 500 for k in tokenKeys: 501 if k.lower() == 'x-rhn-auth-channels': 502 # Multivalued header 503 #values = headers.getHeaderValues(k) 504 values = self._get_header(k) 505 token[k] = [x.split(':') for x in values] 506 else: 507 # Single-valued header 508 token[k] = headers[k] 509 510 # Dump the proxy's clock skew in the dict 511 serverTime = float(token['X-RHN-Auth-Server-Time']) 512 token["X-RHN-Auth-Proxy-Clock-Skew"] = time.time() - serverTime 513 514 # Save the token 515 self.proxyAuth.set_client_token(self.clientServerId, token) 516 return token
517
518 - def __callLocalRepository(self, req_type, identifier, funct, params):
519 """ Contacts the local repository and retrieves files""" 520 521 log_debug(2, req_type, identifier, funct, params) 522 523 # NOTE: X-RHN-Proxy-Auth described in broker/rhnProxyAuth.py 524 if rhnFlags.get('outputTransportOptions').has_key('X-RHN-Proxy-Auth'): 525 self.cachedClientInfo['X-RHN-Proxy-Auth'] = rhnFlags.get('outputTransportOptions')['X-RHN-Proxy-Auth'] 526 if rhnFlags.get('outputTransportOptions').has_key('Host'): 527 self.cachedClientInfo['Host'] = rhnFlags.get('outputTransportOptions')['Host'] 528 529 if req_type == 'tinyurl': 530 try: 531 rep = rhnRepository.TinyUrlRepository(identifier, 532 self.cachedClientInfo, rhnParent=self.rhnParent, 533 rhnParentXMLRPC=self.rhnParentXMLRPC, 534 httpProxy=self.httpProxy, 535 httpProxyUsername=self.httpProxyUsername, 536 httpProxyPassword=self.httpProxyPassword, 537 caChain=self.caChain, 538 systemId=self.proxyAuth.get_system_id()) 539 except rhnRepository.NotLocalError: 540 return None 541 elif req_type == 'ks-dist': 542 try: 543 rep = rhnRepository.KickstartRepository(identifier, 544 self.cachedClientInfo, rhnParent=self.rhnParent, 545 rhnParentXMLRPC=self.rhnParentXMLRPC, 546 httpProxy=self.httpProxy, 547 httpProxyUsername=self.httpProxyUsername, 548 httpProxyPassword=self.httpProxyPassword, 549 caChain=self.caChain, orgId=params['orgId'], 550 child=params['child'], session=params['session'], 551 systemId=self.proxyAuth.get_system_id()) 552 except rhnRepository.NotLocalError: 553 return None 554 params = [params['file']] 555 else: 556 # Find the channel version 557 version = None 558 for c in self.authChannels: 559 ch, ver = c[:2] 560 if ch == identifier: 561 version = ver 562 break 563 564 # We already know he's subscribed to this channel 565 # channel, so the version is non-null 566 rep = rhnRepository.Repository(identifier, version, 567 self.cachedClientInfo, rhnParent=self.rhnParent, 568 rhnParentXMLRPC=self.rhnParentXMLRPC, 569 httpProxy=self.httpProxy, 570 httpProxyUsername=self.httpProxyUsername, 571 httpProxyPassword=self.httpProxyPassword, 572 caChain=self.caChain) 573 574 f = rep.get_function(funct) 575 if not f: 576 raise rhnFault(1000, 577 _("Spacewalk Proxy configuration error: invalid function %s") % funct) 578 579 log_debug(3, "Calling %s(%s)" % (funct, params)) 580 if params is None: 581 params = () 582 try: 583 ret = f(*params) 584 except rhnRepository.NotLocalError: 585 # The package is not local 586 return None 587 588 return ret
589
590 - def __checkAuthSessionTokenCache(self, token, channel):
591 """ Authentication / authorize the channel """ 592 593 log_debug(2, token, channel) 594 # make sure server-id does not contain path 595 self.clientServerId = token['X-RHN-Server-ID'].split("/")[-1] 596 597 cachedToken = self.proxyAuth.get_client_token(self.clientServerId) 598 if not cachedToken: 599 # maybe client logged in through different load-balanced proxy 600 # try to update the cache an try again 601 cachedToken = self.proxyAuth.update_client_token_if_valid( 602 self.clientServerId, token) 603 604 if not cachedToken: 605 msg = _("Invalid session key - server ID not found in cache: %s") \ 606 % self.clientServerId 607 log_error(msg) 608 raise rhnFault(33, msg) 609 610 self.cachedClientInfo = UserDictCase(cachedToken) 611 612 clockSkew = self.cachedClientInfo["X-RHN-Auth-Proxy-Clock-Skew"] 613 del self.cachedClientInfo["X-RHN-Auth-Proxy-Clock-Skew"] 614 615 # Add the server id 616 self.authChannels = self.cachedClientInfo['X-RHN-Auth-Channels'] 617 del self.cachedClientInfo['X-RHN-Auth-Channels'] 618 self.cachedClientInfo['X-RHN-Server-ID'] = self.clientServerId 619 log_debug(4, 'Retrieved token from cache: %s' % self.cachedClientInfo) 620 621 # Compare the two things 622 if not _dictEquals(token, self.cachedClientInfo, 623 ['X-RHN-Auth-Channels']): 624 # Maybe the client logged in through a different load-balanced 625 # proxy? Check validity of the token the client passed us. 626 updatedToken = self.proxyAuth.update_client_token_if_valid( 627 self.clientServerId, token) 628 # fix up the updated token the same way we did above 629 if updatedToken: 630 self.cachedClientInfo = UserDictCase(updatedToken) 631 clockSkew = self.cachedClientInfo[ 632 "X-RHN-Auth-Proxy-Clock-Skew"] 633 del self.cachedClientInfo["X-RHN-Auth-Proxy-Clock-Skew"] 634 self.authChannels = self.cachedClientInfo[ 635 'X-RHN-Auth-Channels'] 636 del self.cachedClientInfo['X-RHN-Auth-Channels'] 637 self.cachedClientInfo['X-RHN-Server-ID'] = \ 638 self.clientServerId 639 log_debug(4, 'Retrieved token from cache: %s' % 640 self.cachedClientInfo) 641 642 if not updatedToken or not _dictEquals( 643 token, self.cachedClientInfo, ['X-RHN-Auth-Channels']): 644 log_debug(3, "Session tokens different") 645 raise rhnFault(33) # Invalid session key 646 647 # Check the expiration 648 serverTime = float(token['X-RHN-Auth-Server-Time']) 649 offset = float(token['X-RHN-Auth-Expire-Offset']) 650 if time.time() > serverTime + offset + clockSkew: 651 log_debug(3, "Session token has expired") 652 raise rhnFault(34) # Session key has expired 653 654 # Only autherized channels are the ones stored in the cache. 655 authChannels = [x[0] for x in self.authChannels] 656 log_debug(4, "Auth channels: '%s'" % authChannels) 657 # Check the authorization 658 if channel not in authChannels: 659 log_debug(4, "Not subscribed to channel %s; unauthorized" % 660 channel) 661 raise rhnFault(35, _('Unauthorized channel access requested.'))
662
663 664 -def _dictEquals(d1, d2, exceptions=None):
665 """ Function that compare two dictionaries, ignoring certain keys """ 666 exceptions = [x.lower() for x in (exceptions or [])] 667 for k, v in d1.items(): 668 if k.lower() in exceptions: 669 continue 670 if not d2.has_key(k) or d2[k] != v: 671 return 0 672 for k, v in d2.items(): 673 if k.lower() in exceptions: 674 continue 675 if not d1.has_key(k) or d1[k] != v: 676 return 0 677 return 1
678 679 680 #=============================================================================== 681