summaryrefslogtreecommitdiffstats
path: root/ipalib/rpc.py
diff options
context:
space:
mode:
Diffstat (limited to 'ipalib/rpc.py')
-rw-r--r--ipalib/rpc.py280
1 files changed, 222 insertions, 58 deletions
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index c555105f6..a5c5de557 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -37,6 +37,7 @@ import sys
import os
import errno
import locale
+import datetime
from xmlrpclib import (Binary, Fault, dumps, loads, ServerProxy, Transport,
ProtocolError, MININT, MAXINT)
import kerberos
@@ -48,8 +49,10 @@ from ipalib.errors import public_errors, PublicError, UnknownError, NetworkError
from ipalib import errors
from ipalib.request import context, Connection
from ipalib.util import get_current_principal
+from ipapython.ipa_log_manager import root_logger
from ipapython import ipautil
from ipapython import kernel_keyring
+from ipapython.cookie import Cookie
from ipalib.text import _
import httplib
@@ -61,7 +64,65 @@ from ipalib.krb_utils import KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN, KRB5KRB_AP_ERR_TKT
KRB5_FCC_PERM, KRB5_FCC_NOFILE, KRB5_CC_FORMAT, KRB5_REALM_CANT_RESOLVE
from ipapython.dn import DN
-COOKIE_NAME = 'ipa_session_cookie:%s'
+COOKIE_NAME = 'ipa_session'
+KEYRING_COOKIE_NAME = '%s_cookie:%%s' % COOKIE_NAME
+
+
+def client_session_keyring_keyname(principal):
+ '''
+ Return the key name used for storing the client session data for
+ the given principal.
+ '''
+
+ return KEYRING_COOKIE_NAME % principal
+
+def update_persistent_client_session_data(principal, data):
+ '''
+ Given a principal create or update the session data for that
+ principal in the persistent secure storage.
+
+ Raises ValueError if unable to perform the action for any reason.
+ '''
+
+ try:
+ keyname = client_session_keyring_keyname(principal)
+ except Exception, e:
+ raise ValueError(str(e))
+
+ # kernel_keyring only raises ValueError (why??)
+ kernel_keyring.update_key(keyname, data)
+
+def read_persistent_client_session_data(principal):
+ '''
+ Given a principal return the stored session data for that
+ principal from the persistent secure storage.
+
+ Raises ValueError if unable to perform the action for any reason.
+ '''
+
+ try:
+ keyname = client_session_keyring_keyname(principal)
+ except Exception, e:
+ raise ValueError(str(e))
+
+ # kernel_keyring only raises ValueError (why??)
+ return kernel_keyring.read_key(keyname)
+
+def delete_persistent_client_session_data(principal):
+ '''
+ Given a principal remove the session data for that
+ principal from the persistent secure storage.
+
+ Raises ValueError if unable to perform the action for any reason.
+ '''
+
+ try:
+ keyname = client_session_keyring_keyname(principal)
+ except Exception, e:
+ raise ValueError(str(e))
+
+ # kernel_keyring only raises ValueError (why??)
+ kernel_keyring.del_key(keyname)
def xml_wrap(value):
"""
@@ -310,9 +371,9 @@ class KerbTransport(SSLTransport):
if not isinstance(extra_headers, list):
extra_headers = []
- session_data = getattr(context, 'session_data', None)
- if session_data:
- extra_headers.append(('Cookie', session_data))
+ session_cookie = getattr(context, 'session_cookie', None)
+ if session_cookie:
+ extra_headers.append(('Cookie', session_cookie))
return (host, extra_headers, x509)
# Set the remote host principal
@@ -345,16 +406,55 @@ class KerbTransport(SSLTransport):
finally:
self.close()
+ def store_session_cookie(self, cookie_header):
+ '''
+ Given the contents of a Set-Cookie header scan the header and
+ extract each cookie contained within until the session cookie
+ is located. Examine the session cookie if the domain and path
+ are specified, if not update the cookie with those values from
+ the request URL. Then write the session cookie into the key
+ store for the principal. If the cookie header is None or the
+ session cookie is not present in the header no action is
+ taken.
+
+ Context Dependencies:
+
+ The per thread context is expected to contain:
+ principal
+ The current pricipal the HTTP request was issued for.
+ request_url
+ The URL of the HTTP request.
+
+ '''
+
+ if cookie_header is None:
+ return
+
+ principal = getattr(context, 'principal', None)
+ request_url = getattr(context, 'request_url', None)
+ root_logger.debug("received Set-Cookie '%s'", cookie_header)
+
+ # Search for the session cookie
+ try:
+ session_cookie = Cookie.get_named_cookie_from_string(cookie_header,
+ COOKIE_NAME, request_url)
+ except Exception, e:
+ root_logger.error("unable to parse cookie header '%s': %s", cookie_header, e)
+ return
+
+ if session_cookie is None:
+ return
+
+ cookie_string = str(session_cookie)
+ root_logger.debug("storing cookie '%s' for principal %s", cookie_string, principal)
+ try:
+ update_persistent_client_session_data(principal, cookie_string)
+ except Exception, e:
+ # Not fatal, we just can't use the session cookie we were sent.
+ pass
+
def parse_response(self, response):
- session_cookie = response.getheader('Set-Cookie')
- if session_cookie:
- principal = getattr(context, 'principal', None)
- try:
- kernel_keyring.update_key(COOKIE_NAME % principal, session_cookie)
- except ValueError, e:
- # Not fatal, we just can't use the session cookie we were
- # sent.
- pass
+ self.store_session_cookie(response.getheader('Set-Cookie'))
return SSLTransport.parse_response(self, response)
@@ -377,22 +477,6 @@ class xmlclient(Connectible):
super(xmlclient, self).__init__()
self.__errors = dict((e.errno, e) for e in public_errors)
- def reconstruct_url(self):
- """
- The URL directly isn't stored in the ServerProxy. We can't store
- it in the connection object itself but we can reconstruct it
- from the ServerProxy.
- """
- if not hasattr(self.conn, '_ServerProxy__transport'):
- return None
- if (isinstance(self.conn._ServerProxy__transport, KerbTransport) or
- isinstance(self.conn._ServerProxy__transport, DelegatedKerbTransport)):
- scheme = "https"
- else:
- scheme = "http"
- server = '%s://%s%s' % (scheme, ipautil.format_netloc(self.conn._ServerProxy__host), self.conn._ServerProxy__handler)
- return server
-
def get_url_list(self, xmlrpc_uri):
"""
Create a list of urls consisting of the available IPA servers.
@@ -425,40 +509,122 @@ class xmlclient(Connectible):
return servers
+ def get_session_cookie_from_persistent_storage(self, principal):
+ '''
+ Retrieves the session cookie for the given principal from the
+ persistent secure storage. Returns None if not found or unable
+ to retrieve the session cookie for any reason, otherwise
+ returns a Cookie object containing the session cookie.
+ '''
+
+ # Get the session data, it should contain a cookie string
+ # (possibly with more than one cookie).
+ try:
+ cookie_string = read_persistent_client_session_data(principal)
+ except Exception, e:
+ return None
+
+ # Search for the session cookie within the cookie string
+ try:
+ session_cookie = Cookie.get_named_cookie_from_string(cookie_string, COOKIE_NAME)
+ except Exception, e:
+ return None
+
+ return session_cookie
+
+ def apply_session_cookie(self, url):
+ '''
+ Attempt to load a session cookie for the current principal
+ from the persistent secure storage. If the cookie is
+ successfully loaded adjust the input url's to point to the
+ session path and insert the session cookie into the per thread
+ context for later insertion into the HTTP request. If the
+ cookie is not successfully loaded then the original url is
+ returned and the per thread context is not modified.
+
+ Context Dependencies:
+
+ The per thread context is expected to contain:
+ principal
+ The current pricipal the HTTP request was issued for.
+
+ The per thread context will be updated with:
+ session_cookie
+ A cookie string to be inserted into the Cookie header
+ of the HTPP request.
+
+ '''
+
+ original_url = url
+ principal = getattr(context, 'principal', None)
+
+ session_cookie = self.get_session_cookie_from_persistent_storage(principal)
+ if session_cookie is None:
+ self.log.debug("failed to find session_cookie in persistent storage for principal '%s'",
+ principal)
+ return original_url
+ else:
+ self.debug("found session_cookie in persistent storage for principal '%s', cookie: '%s'",
+ principal, session_cookie)
+
+ # Decide if we should send the cookie to the server
+ try:
+ session_cookie.http_return_ok(original_url)
+ except Cookie.Expired, e:
+ self.debug("deleting session data for principal '%s': %s", principal, e)
+ try:
+ delete_persistent_client_session_data(principal)
+ except Exception, e:
+ pass
+ return original_url
+ except Cookie.URLMismatch, e:
+ self.debug("not sending session cookie, URL mismatch: %s", e)
+ return original_url
+ except Exception, e:
+ self.error("not sending session cookie, unknown error: %s", e)
+ return original_url
+
+ # O.K. session_cookie is valid to be returned, stash it away where it will will
+ # get included in a HTTP Cookie headed sent to the server.
+ self.log.debug("setting session_cookie into context '%s'", session_cookie.http_cookie())
+ setattr(context, 'session_cookie', session_cookie.http_cookie())
+
+ # Form the session URL by substituting the session path into the original URL
+ scheme, netloc, path, params, query, fragment = urlparse.urlparse(original_url)
+ path = '/ipa/session/xml'
+ session_url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
+
+ return session_url
+
def create_connection(self, ccache=None, verbose=False, fallback=True,
delegate=False):
try:
- session = False
- session_data = None
xmlrpc_uri = self.env.xmlrpc_uri
principal = get_current_principal()
setattr(context, 'principal', principal)
# We have a session cookie, try using the session URI to see if it
# is still valid
if not delegate:
- session_data = kernel_keyring.read_key(COOKIE_NAME % principal)
- setattr(context, 'session_data', session_data)
- (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(self.env.xmlrpc_uri)
- xmlrpc_uri = urlparse.urlunparse((scheme, netloc, '/ipa/session/xml', params, query, fragment))
- session = True
+ xmlrpc_uri = self.apply_session_cookie(xmlrpc_uri)
except ValueError:
# No session key, do full Kerberos auth
pass
- servers = self.get_url_list(xmlrpc_uri)
+ urls = self.get_url_list(xmlrpc_uri)
serverproxy = None
- for server in servers:
+ for url in urls:
kw = dict(allow_none=True, encoding='UTF-8')
kw['verbose'] = verbose
- if server.startswith('https://'):
+ if url.startswith('https://'):
if delegate:
kw['transport'] = DelegatedKerbTransport()
else:
kw['transport'] = KerbTransport()
else:
kw['transport'] = LanguageAwareTransport()
- self.log.info('trying %s' % server)
- serverproxy = ServerProxy(server, **kw)
- if len(servers) == 1:
+ self.log.info('trying %s' % url)
+ setattr(context, 'request_url', url)
+ serverproxy = ServerProxy(url, **kw)
+ if len(urls) == 1:
# if we have only 1 server and then let the
# main requester handle any errors. This also means it
# must handle a 401 but we save a ping.
@@ -476,7 +642,7 @@ class xmlclient(Connectible):
raise UnknownError(
code=e.faultCode,
error=e.faultString,
- server=server,
+ server=url,
)
# We don't care about the response, just that we got one
break
@@ -484,14 +650,13 @@ class xmlclient(Connectible):
# kerberos error on one server is likely on all
raise errors.KerberosError(major=str(krberr), minor='')
except ProtocolError, e:
- if session_data and e.errcode == 401:
+ if hasattr(context, 'session_cookie') and e.errcode == 401:
# Unauthorized. Remove the session and try again.
- delattr(context, 'session_data')
+ delattr(context, 'session_cookie')
try:
- kernel_keyring.del_key(COOKIE_NAME % principal)
- except ValueError:
- # This shouldn't happen if we have a session but
- # it isn't fatal.
+ delete_persistent_client_session_data(principal)
+ except Exception, e:
+ # This shouldn't happen if we have a session but it isn't fatal.
pass
return self.create_connection(ccache, verbose, fallback, delegate)
if not fallback:
@@ -504,7 +669,7 @@ class xmlclient(Connectible):
if serverproxy is None:
raise NetworkError(uri=_('any of the configured servers'),
- error=', '.join(servers))
+ error=', '.join(urls))
return serverproxy
def destroy_connection(self):
@@ -529,7 +694,7 @@ class xmlclient(Connectible):
raise ValueError(
'%s.forward(): %r not in api.Command' % (self.name, name)
)
- server = self.reconstruct_url()
+ server = getattr(context, 'request_url', None)
self.info('Forwarding %r to server %r', name, server)
command = getattr(self.conn, name)
params = [args, kw]
@@ -554,16 +719,15 @@ class xmlclient(Connectible):
# By catching a 401 here we can detect the case where we have
# a single IPA server and the session is invalid. Otherwise
# we always have to do a ping().
- session_data = getattr(context, 'session_data', None)
- if session_data and e.errcode == 401:
+ session_cookie = getattr(context, 'session_cookie', None)
+ if session_cookie and e.errcode == 401:
# Unauthorized. Remove the session and try again.
- delattr(context, 'session_data')
+ delattr(context, 'session_cookie')
try:
principal = getattr(context, 'principal', None)
- kernel_keyring.del_key(COOKIE_NAME % principal)
- except ValueError:
- # This shouldn't happen if we have a session but
- # it isn't fatal.
+ delete_persistent_client_session_data(principal)
+ except Exception, e:
+ # This shouldn't happen if we have a session but it isn't fatal.
pass
# Create a new serverproxy with the non-session URI. If there