On 02/27/2012 05:53 PM, Rob Crittenden wrote:
John Dennis wrote:
On 02/27/2012 01:50 PM, Rob Crittenden wrote:
John Dennis wrote:
Attached is a revised patch, it addresses the following concerns raised
during review:

* The version in ipa.conf has been bumped.

* Rob reported duplicate session cookies being returned. As far as I can
tell this was due to a Python bug where it reused the value of a default
keyword parameter from a previous invocation rather than re-initializing
it. Workaround is to change the default value from [] to the value to
None and create an empty list if the arg is None.

* Rob reported two test failures, one for ERRNO (e.g. **1234**) not
being present in the docstring for an error I added and the other was
for a change in the wsgi dispatch route() method that showed up in
test_rpcserver.py

The Requires on krb5-workstation is not required. The server requires
the client which requires it.

Yeah, I wasn't sure about that, now I know.

OK, fixed

I think you need a more unique way of generating the ccache name when
doing the kinit (I'd use tempfile.mkstemp).

Why? The session code is already set up to use a "temporary" ccache file
for each request it processes (your suggestion). The file is created
when the request comes, exists for the duration of the request, and is
deleted when the request completes. Is there a compelling reason to
treat initializing a ccache in a request from using a ccache in a request?

Ok, I think this is fine. I thought you were storing the name of the
file and since there is no way to predict which Apache subprocess would
handle a given request there would have been a chance of collision.
Since you always use the current pid as the name it is fine (as long as
we never use the worker model).


There is an incorrect comment in internal_error()

Good catch, OK fixed.

You want to return 401 Unauthorized and not 403 Forbidden on password
failures.

That wasn't an accident, I read the RFC for 401 and 403. The RFC for 401
states "The response MUST include a WWW-Authenticate header field
(section 14.47) containing a challenge applicable to the requested
resource." But we're not doing Basic Auth here and we're not returning
challenges as spec'ed out in the other RFC's, this is something a bit
different so I concluded 401 was not appropriate. But I also see your
point about 403 not being quite right either.

I'm happy to change it to 401 though, your point is well taken, it's
probably closer to being correct

OK, fixed

Hmm, yeah. I think we'll need to see what the browsers do when they get
a 401 and no WWW-authenticate back.


We shouldn't support the GET method as the password will appear in the
logs:

192.168.0.1 - - [27/Feb/2012:13:46:31 -0500] "GET
/ipa/session/login_password?user=admin&password=password HTTP/1.1" 200 -

Good point, OK, fixed

revised patch coming soon ...

revised patch has arrived ... see attachement


--
John Dennis <jden...@redhat.com>

Looking to carve out IT costs?
www.redhat.com/carveoutcosts/
>From 8166749325a388d9237c668582fd6e80594517fe Mon Sep 17 00:00:00 2001
From: John Dennis <jden...@redhat.com>
Date: Sat, 25 Feb 2012 13:39:19 -0500
Subject: [PATCH 64-2] Implement password based session login
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: 8bit

* Adjust URL's
  - rename /ipa/login -> /ipa/session/login_kerberos
  - add /ipa/session/login_password

* Adjust Kerberos protection on URL's in ipa.conf

* Bump VERSION in httpd ipa.conf to pick up session changes.

* Adjust login URL in ipa.js

* Add InvalidSessionPassword to errors.py

* Rename krblogin class to login_kerberos for consistency with
  new login_password class

* Implement login_password.kinit() method which invokes
  /usr/bin/kinit as a subprocess

* Add login_password class for WSGI dispatch, accepts POST
  application/x-www-form-urlencoded user & password
  parameters. We form the Kerberos principal from the server's
  realm.

* Add function  krb5_unparse_ccache()

* Refactor code to share common code

* Clean up use of ccache names, be consistent

* Replace read_krbccache_file(), store_krbccache_file(), delete_krbccache_file()
  with load_ccache_data(), bind_ipa_ccache(), release_ipa_ccache().
  bind_ipa_ccache() now sets environment KRB5CCNAME variable.
  release_ipa_ccache() now clears environment KRB5CCNAME variable.

* ccache names should now support any ccache storage scheme,
  not just FILE based ccaches

* Add utilies to return HTTP status from wsgi handlers,
  use constants for HTTP status code for consistency.
  Use utilies for returning from wsgi handlers rather than
  duplicated code.

* Add KerberosSession.finalize_kerberos_acquisition() method
  so different login handlers can share common code.

* add Requires: krb5-workstation to server (server now calls kinit)

* Fix test_rpcserver.py to use new dispatch inside route() method
---
 install/conf/ipa.conf                  |   10 +-
 install/ui/ipa.js                      |    2 +-
 ipalib/errors.py                       |    7 +
 ipalib/krb_utils.py                    |   13 +-
 ipalib/session.py                      |   89 ++++++++----
 ipaserver/plugins/xmlserver.py         |    5 +-
 ipaserver/rpcserver.py                 |  258 +++++++++++++++++++++++++-------
 tests/test_ipaserver/test_rpcserver.py |    8 +-
 8 files changed, 294 insertions(+), 98 deletions(-)

diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf
index cd806be..89c9849 100644
--- a/install/conf/ipa.conf
+++ b/install/conf/ipa.conf
@@ -1,5 +1,5 @@
 #
-# VERSION 3 - DO NOT REMOVE THIS LINE
+# VERSION 4 - DO NOT REMOVE THIS LINE
 #
 # LoadModule auth_kerb_module modules/mod_auth_kerb.so
 
@@ -60,7 +60,13 @@ KrbConstrainedDelegationLock ipa
 </Location>
 
 # Turn off Apache authentication for sessions
-<Location "/ipa/session">
+<Location "/ipa/session/json">
+  Satisfy Any
+  Order Deny,Allow
+  Allow from all
+</Location>
+
+<Location "/ipa/session/login_password">
   Satisfy Any
   Order Deny,Allow
   Allow from all
diff --git a/install/ui/ipa.js b/install/ui/ipa.js
index a599f6a..433d7fe 100644
--- a/install/ui/ipa.js
+++ b/install/ui/ipa.js
@@ -60,7 +60,7 @@ var IPA = function() {
         // if current path matches live server path, use live data
         if (that.url && window.location.pathname.substring(0, that.url.length) === that.url) {
             that.json_url = params.url || '/ipa/session/json';
-            that.login_url = params.url || '/ipa/login';
+            that.login_url = params.url || '/ipa/session/login_kerberos';
 
         } else { // otherwise use fixtures
             that.json_path = params.url || "test/data";
diff --git a/ipalib/errors.py b/ipalib/errors.py
index bdc4a5e..df4ab41 100644
--- a/ipalib/errors.py
+++ b/ipalib/errors.py
@@ -612,6 +612,13 @@ class SessionError(AuthenticationError):
     format= _('Session error')
 
 
+class InvalidSessionPassword(SessionError):
+    """
+    **1201** Raised when we cannot obtain a TGT for a principal.
+    """
+    errno = 1201
+    format= _('Principal %(principal)s cannot be authenticated: %(message)s')
+
 ##############################################################################
 # 2000 - 2999: Authorization errors
 class AuthorizationError(PublicError):
diff --git a/ipalib/krb_utils.py b/ipalib/krb_utils.py
index 7e68bf6..a8f7751 100644
--- a/ipalib/krb_utils.py
+++ b/ipalib/krb_utils.py
@@ -42,7 +42,7 @@ ccache_name_re = re.compile(r'^((\w+):)?(.+)')
 
 #-------------------------------------------------------------------------------
 
-def krb5_parse_ccache(name):
+def krb5_parse_ccache(ccache_name):
     '''
     Given a Kerberos ccache name parse it into it's scheme and
     location components. Currently valid values for the scheme
@@ -55,12 +55,12 @@ def krb5_parse_ccache(name):
     does not exist it defaults to FILE.
 
     :parameters:
-      name
+      ccache_name
         The name of the Kerberos ccache.
     :returns:
       A two-tuple of (scheme, ccache)
     '''
-    match = ccache_name_re.search(name)
+    match = ccache_name_re.search(ccache_name)
     if match:
         scheme = match.group(2)
         location = match.group(3)
@@ -71,7 +71,10 @@ def krb5_parse_ccache(name):
 
         return scheme, location
     else:
-        raise ValueError('Invalid ccache name = "%s"' % name)
+        raise ValueError('Invalid ccache name = "%s"' % ccache_name)
+
+def krb5_unparse_ccache(scheme, name):
+    return '%s:%s' % (scheme.upper(), name)
 
 def krb5_format_principal_name(user, realm):
     '''
@@ -388,5 +391,5 @@ class KRB5_CCache(object):
         except KeyError:
             pass
 
-        self.debug('"%s" ccache endtime=%s (%s)', self.ccache_str(), result, krb5_format_time(result))
+        self.debug('KRB5_CCache %s endtime=%s (%s)', self.ccache_str(), result, krb5_format_time(result))
         return result
diff --git a/ipalib/session.py b/ipalib/session.py
index 5c71a92..4b783bb 100644
--- a/ipalib/session.py
+++ b/ipalib/session.py
@@ -1197,34 +1197,71 @@ class MemcacheSessionManager(SessionManager):
 krbccache_dir ='/var/run/ipa_memcached'
 krbccache_prefix = 'krbcc_'
 
-def get_krbccache_pathname():
+def _get_krbccache_pathname():
     return os.path.join(krbccache_dir, '%s%s' % (krbccache_prefix, os.getpid()))
 
-def read_krbccache_file(krbccache_pathname):
-    root_logger.debug('reading krbccache data from "%s"', krbccache_pathname)
-    src = open(krbccache_pathname)
-    ccache_data = src.read()
-    src.close()
-    return ccache_data
-
-def store_krbccache_file(ccache_data):
-    krbccache_pathname = get_krbccache_pathname()
-    root_logger.debug('storing krbccache data into "%s"', krbccache_pathname)
-    dst = open(krbccache_pathname, 'w')
-    dst.write(ccache_data)
-    dst.close()
-
-    return krbccache_pathname
-
-def delete_krbccache_file(krbccache_pathname=None):
-    if krbccache_pathname is None:
-        krbccache_pathname = get_krbccache_pathname()
-
-    try:
-        os.unlink(krbccache_pathname)
-    except Exception, e:
-        root_logger.error('unable to delete session krbccache file "%s", %s',
-                          krbccache_pathname, e)
+def get_ipa_ccache_name(scheme='FILE'):
+    if scheme == 'FILE':
+        name = os.path.join(krbccache_dir, '%s%s' % (krbccache_prefix, os.getpid()))
+    else:
+        raise ValueError('ccache scheme "%s" unsupported', scheme)
+
+    ccache_name = krb5_unparse_ccache(scheme, name)
+    return ccache_name
+
+
+def load_ccache_data(ccache_name):
+    scheme, name = krb5_parse_ccache(ccache_name)
+    if scheme == 'FILE':
+        root_logger.debug('reading ccache data from file "%s"', name)
+        src = open(name)
+        ccache_data = src.read()
+        src.close()
+        return ccache_data
+    else:
+        raise ValueError('ccache scheme "%s" unsupported (%s)', scheme, ccache_name)
+
+def bind_ipa_ccache(ccache_data, scheme='FILE'):
+    if scheme == 'FILE':
+        name = _get_krbccache_pathname()
+        root_logger.debug('storing ccache data into file "%s"', name)
+        dst = open(name, 'w')
+        dst.write(ccache_data)
+        dst.close()
+    else:
+        raise ValueError('ccache scheme "%s" unsupported', scheme)
+
+    ccache_name = krb5_unparse_ccache(scheme, name)
+    os.environ['KRB5CCNAME'] = ccache_name
+    return ccache_name
+
+def release_ipa_ccache(ccache_name):
+    '''
+    Stop using the current request's ccache.
+      * Remove KRB5CCNAME from the enviroment
+      * Remove the ccache file from the file system
+
+    Note, we do not demand any of these elements exist, but if they
+    do we'll remove them.
+    '''
+
+    if os.environ.has_key('KRB5CCNAME'):
+        if ccache_name != os.environ['KRB5CCNAME']:
+            root_logger.error('release_ipa_ccache: ccache_name (%s) != KRB5CCNAME environment variable (%s)',
+                              ccache_name, os.environ['KRB5CCNAME'])
+        del os.environ['KRB5CCNAME']
+    else:
+        root_logger.debug('release_ipa_ccache: KRB5CCNAME environment variable not set')
+
+    scheme, name = krb5_parse_ccache(ccache_name)
+    if scheme == 'FILE':
+        if os.path.exists(name):
+            try:
+                os.unlink(name)
+            except Exception, e:
+                root_logger.error('unable to delete session ccache file "%s", %s', name, e)
+    else:
+        raise ValueError('ccache scheme "%s" unsupported (%s)', scheme, ccache_name)
 
 
 #-------------------------------------------------------------------------------
diff --git a/ipaserver/plugins/xmlserver.py b/ipaserver/plugins/xmlserver.py
index d2a28ec..4ae9149 100644
--- a/ipaserver/plugins/xmlserver.py
+++ b/ipaserver/plugins/xmlserver.py
@@ -25,9 +25,10 @@ Loads WSGI server plugins.
 from ipalib import api
 
 if 'in_server' in api.env and api.env.in_server is True:
-    from ipaserver.rpcserver import wsgi_dispatch, xmlserver, jsonserver_kerb, jsonserver_session, krblogin
+    from ipaserver.rpcserver import wsgi_dispatch, xmlserver, jsonserver_kerb, jsonserver_session, login_kerberos, login_password
     api.register(wsgi_dispatch)
     api.register(xmlserver)
     api.register(jsonserver_kerb)
     api.register(jsonserver_session)
-    api.register(krblogin)
+    api.register(login_kerberos)
+    api.register(login_password)
diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index 0b8aa40..5f2e068 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -27,14 +27,15 @@ from cgi import parse_qs
 from xml.sax.saxutils import escape
 from xmlrpclib import Fault
 from ipalib.backend import Executioner
-from ipalib.errors import PublicError, InternalError, CommandError, JSONError, ConversionError, CCacheError, RefererError
+from ipalib.errors import PublicError, InternalError, CommandError, JSONError, ConversionError, CCacheError, RefererError, InvalidSessionPassword
 from ipalib.request import context, Connection, destroy_context
 from ipalib.rpc import xml_dumps, xml_loads
 from ipalib.util import make_repr, parse_time_duration
 from ipapython.compat import json
-from ipalib.session import session_mgr, AuthManager, read_krbccache_file, store_krbccache_file, delete_krbccache_file, fmt_time, default_max_session_duration
+from ipalib.session import session_mgr, AuthManager, get_ipa_ccache_name, load_ccache_data, bind_ipa_ccache, release_ipa_ccache, fmt_time, default_max_session_duration
 from ipalib.backend import Backend
-from ipalib.krb_utils import krb5_parse_ccache, KRB5_CCache, krb_ticket_expiration_threshold
+from ipalib.krb_utils import krb5_parse_ccache, KRB5_CCache, krb_ticket_expiration_threshold, krb5_format_principal_name
+from ipapython import ipautil
 from wsgiref.util import shift_path_info
 from ipapython.version import VERSION
 import base64
@@ -42,6 +43,11 @@ import os
 import string
 import datetime
 from decimal import Decimal
+import urlparse
+
+HTTP_STATUS_SUCCESS = '200 Success'
+HTTP_STATUS_SERVER_ERROR = '500 Internal Server Error'
+
 _not_found_template = """<html>
 <head>
 <title>404 Not Found</title>
@@ -54,19 +60,84 @@ The requested URL <strong>%(url)s</strong> was not found on this server.
 </body>
 </html>"""
 
+_bad_request_template = """<html>
+<head>
+<title>400 Bad Request</title>
+</head>
+<body>
+<h1>Bad Request</h1>
+<p>
+<strong>%(message)s</strong>
+</p>
+</body>
+</html>"""
+
+_internal_error_template = """<html>
+<head>
+<title>500 Internal Server Error</title>
+</head>
+<body>
+<h1>Internal Server Error</h1>
+<p>
+<strong>%(message)s</strong>
+</p>
+</body>
+</html>"""
+
+_unauthorized_template = """<html>
+<head>
+<title>401 Unauthorized</title>
+</head>
+<body>
+<h1>Invalid Authentication</h1>
+<p>
+<strong>%(message)s</strong>
+</p>
+</body>
+</html>"""
 
 def not_found(environ, start_response):
     """
     Return a 404 Not Found error.
     """
     status = '404 Not Found'
-    response_headers = [('Content-Type', 'text/html')]
+    response_headers = [('Content-Type', 'text/html; charset=utf-8')]
     start_response(status, response_headers)
     output = _not_found_template % dict(
         url=escape(environ['SCRIPT_NAME'] + environ['PATH_INFO'])
     )
     return [output]
 
+def bad_request(environ, start_response, message):
+    """
+    Return a 400 Bad Request error.
+    """
+    status = '400 Bad Request'
+    response_headers = [('Content-Type', 'text/html; charset=utf-8')]
+    start_response(status, response_headers)
+    output = _bad_request_template % dict(message=escape(message))
+    return [output]
+
+def internal_error(environ, start_response, message):
+    """
+    Return a 500 Internal Server Error.
+    """
+    status = HTTP_STATUS_SERVER_ERROR
+    response_headers = [('Content-Type', 'text/html; charset=utf-8')]
+    start_response(status, response_headers)
+    output = _internal_error_template % dict(message=escape(message))
+    return [output]
+
+def unauthorized(environ, start_response, message):
+    """
+    Return a 401 Unauthorized error.
+    """
+    status = '401 Unauthorized'
+    response_headers = [('Content-Type', 'text/html; charset=utf-8')]
+    start_response(status, response_headers)
+    output = _unauthorized_template % dict(message=escape(message))
+    return [output]
+
 def read_input(environ):
     """
     Read the request body from environ['wsgi.input'].
@@ -267,14 +338,14 @@ class WSGIExecutioner(Executioner):
 
         self.debug('WSGI WSGIExecutioner.__call__:')
         try:
-            status = '200 OK'
+            status = HTTP_STATUS_SUCCESS
             response = self.wsgi_execute(environ)
             headers = [('Content-Type', self.content_type + '; charset=utf-8')]
         except StandardError, e:
             self.exception('WSGI %s.__call__():', self.name)
-            status = '500 Internal Server Error'
+            status = HTTP_STATUS_SERVER_ERROR
             response = status
-            headers = [('Content-Type', 'text/plain')]
+            headers = [('Content-Type', 'text/plain; charset=utf-8')]
 
         session_data = getattr(context, 'session_data', None)
         if session_data is not None:
@@ -316,17 +387,16 @@ class xmlserver(WSGIExecutioner):
         '''
 
         self.debug('WSGI xmlserver.__call__:')
-        ccache=environ.get('KRB5CCNAME')
-        if ccache is None:
+        user_ccache=environ.get('KRB5CCNAME')
+        if user_ccache is None:
             return self.marshal(None, CCacheError())
-        self.create_context(ccache=ccache)
         try:
-            self.create_context(ccache=environ.get('KRB5CCNAME'))
+            self.create_context(ccache=user_ccache)
             response = super(xmlserver, self).__call__(environ, start_response)
         except PublicError, e:
-            status = '200 OK'
+            status = HTTP_STATUS_SUCCESS
             response = status
-            headers = [('Content-Type', 'text/plain')]
+            headers = [('Content-Type', 'text/plain; charset=utf-8')]
             start_response(status, headers)
             return self.marshal(None, e)
         finally:
@@ -619,6 +689,40 @@ class KerberosSession(object):
                                                 max_age=krb_expiration,
                                                 duration_type=self.api.env.session_duration_type)
 
+
+    def finalize_kerberos_acquisition(self, who, ccache_name, environ, start_response, headers=None):
+        if headers is None:
+            headers = []
+
+        # Retrieve the session data (or newly create)
+        session_data = session_mgr.load_session_data(environ.get('HTTP_COOKIE'))
+        session_id = session_data['session_id']
+
+        self.debug('finalize_kerberos_acquisition: %s ccache_name="%s" session_id="%s"',
+                   who, ccache_name, session_id)
+
+        # Copy the ccache file contents into the session data
+        session_data['ccache_data'] = load_ccache_data(ccache_name)
+
+        # Set when the session will expire
+        cc = KRB5_CCache(ccache_name)
+        endtime = cc.endtime(self.api.env.host, self.api.env.realm)
+        self.update_session_expiration(session_data, endtime)
+
+        # Store the session data now that it's been updated with the ccache
+        session_mgr.store_session_data(session_data)
+
+        # The request is finished with the ccache, destroy it.
+        release_ipa_ccache(ccache_name)
+
+        # Return success and set session cookie
+        session_cookie = session_mgr.generate_cookie('/ipa', session_id)
+        headers.append(('Set-Cookie', session_cookie))
+
+        start_response(HTTP_STATUS_SUCCESS, headers)
+        return ['']
+
+
 class jsonserver_session(jsonserver, KerberosSession):
     """
     JSON RPC server protected with session auth.
@@ -668,13 +772,14 @@ class jsonserver_session(jsonserver, KerberosSession):
             self.debug('no ccache, need login')
             return self.need_login(start_response)
 
-        krbccache_pathname = store_krbccache_file(ccache_data)
+        ipa_ccache_name = bind_ipa_ccache(ccache_data)
 
         # Redirect to login if Kerberos credentials are expired
-        cc = KRB5_CCache(krbccache_pathname)
+        cc = KRB5_CCache(ipa_ccache_name)
         if not cc.valid(self.api.env.host, self.api.env.realm):
             self.debug('ccache expired, deleting session, need login')
-            delete_krbccache_file(krbccache_pathname)
+            # The request is finished with the ccache, destroy it.
+            release_ipa_ccache(ipa_ccache_name)
             return self.need_login(start_response)
 
         # Update the session expiration based on the Kerberos expiration
@@ -684,7 +789,7 @@ class jsonserver_session(jsonserver, KerberosSession):
         # Store the session data in the per-thread context
         setattr(context, 'session_data', session_data)
 
-        self.create_context(ccache=krbccache_pathname)
+        self.create_context(ccache=ipa_ccache_name)
 
         try:
             response = super(jsonserver_session, self).__call__(environ, start_response)
@@ -701,10 +806,10 @@ class jsonserver_session(jsonserver, KerberosSession):
             # data to invalidate the session credentials.
 
             if session_data.has_key('ccache_data'):
-                session_data['ccache_data'] = read_krbccache_file(krbccache_pathname)
+                session_data['ccache_data'] = load_ccache_data(ipa_ccache_name)
 
-            # Delete the temporary ccache file we used
-            delete_krbccache_file(krbccache_pathname)
+            # The request is finished with the ccache, destroy it.
+            release_ipa_ccache(ipa_ccache_name)
             # Store the session data.
             session_mgr.store_session_data(session_data)
             destroy_context()
@@ -724,10 +829,10 @@ class jsonserver_kerb(jsonserver):
 
         self.debug('WSGI jsonserver_kerb.__call__:')
 
-        ccache=environ.get('KRB5CCNAME')
-        if ccache is None:
+        user_ccache=environ.get('KRB5CCNAME')
+        if user_ccache is None:
             return self.marshal(None, CCacheError())
-        self.create_context(ccache=ccache)
+        self.create_context(ccache=user_ccache)
 
         try:
             response = super(jsonserver_kerb, self).__call__(environ, start_response)
@@ -737,59 +842,96 @@ class jsonserver_kerb(jsonserver):
         return response
 
 
-class krblogin(Backend, KerberosSession):
-    key = '/login'
+class login_kerberos(Backend, KerberosSession):
+    key = '/session/login_kerberos'
 
     def __init__(self):
-        super(krblogin, self).__init__()
+        super(login_kerberos, self).__init__()
 
     def _on_finalize(self):
-        super(krblogin, self)._on_finalize()
+        super(login_kerberos, self)._on_finalize()
         self.api.Backend.wsgi_dispatch.mount(self, self.key)
         self.kerb_session_on_finalize()
 
     def __call__(self, environ, start_response):
-        headers = []
-
-        self.debug('WSGI krblogin.__call__:')
+        self.debug('WSGI login_kerberos.__call__:')
 
         # Get the ccache created by mod_auth_kerb
-        ccache=environ.get('KRB5CCNAME')
-        if ccache is None:
-            status = '500 Internal Error'
-            response = 'KRB5CCNAME not defined'
-            start_response(status, headers)
-            return [response]
+        user_ccache_name=environ.get('KRB5CCNAME')
+        if user_ccache_name is None:
+            return internal_error(environ, start_response, 'KRB5CCNAME not defined')
 
-        ccache_scheme, ccache_location = krb5_parse_ccache(ccache)
-        assert ccache_scheme == 'FILE'
+        return self.finalize_kerberos_acquisition('login_kerberos', user_ccache_name, environ, start_response)
 
-        # Retrieve the session data (or newly create)
-        session_data = session_mgr.load_session_data(environ.get('HTTP_COOKIE'))
-        session_id = session_data['session_id']
+class login_password(Backend, KerberosSession):
 
-        # Copy the ccache file contents into the session data
-        session_data['ccache_data'] = read_krbccache_file(ccache_location)
+    content_type = 'text/plain'
+    key = '/session/login_password'
 
-        # Set when the session will expire
-        cc = KRB5_CCache(ccache)
-        endtime = cc.endtime(self.api.env.host, self.api.env.realm)
-        self.update_session_expiration(session_data, endtime)
+    def __init__(self):
+        super(login_password, self).__init__()
 
-        # Store the session data now that it's been updated with the ccache
-        session_mgr.store_session_data(session_data)
+    def _on_finalize(self):
+        super(login_password, self)._on_finalize()
+        self.api.Backend.wsgi_dispatch.mount(self, self.key)
+        self.kerb_session_on_finalize()
 
-        self.debug('krblogin: ccache="%s" session_id="%s" ccache="%s"',
-                   ccache, session_id, ccache)
+    def __call__(self, environ, start_response):
+        self.debug('WSGI login_password.__call__:')
 
-        # Return success and set session cookie
-        status = '200 Success'
-        response = ''
+        # Get the user and password parameters from the request
+        content_type = environ.get('CONTENT_TYPE', '').lower()
+        if content_type != 'application/x-www-form-urlencoded':
+            return bad_request(environ, start_response, "Content-Type must be application/x-www-form-urlencoded")
 
-        session_cookie = session_mgr.generate_cookie('/ipa', session_id)
-        headers.append(('Set-Cookie', session_cookie))
+        method = environ.get('REQUEST_METHOD', '').upper()
+        if method == 'POST':
+            query_string = read_input(environ)
+        else:
+            return bad_request(environ, start_response, "HTTP request method must be POST")
 
-        start_response(status, headers)
-        return [response]
+        try:
+            query_dict = urlparse.parse_qs(query_string)
+        except Exception, e:
+            return bad_request(environ, start_response, "cannot parse query data")
+
+        user = query_dict.get('user', None)
+        if user is not None:
+            if len(user) == 1:
+                user = user[0]
+            else:
+                return bad_request(environ, start_response, "more than one user parameter")
+        else:
+            return bad_request(environ, start_response, "no user specified")
+
+        password = query_dict.get('password', None)
+        if password is not None:
+            if len(password) == 1:
+                password = password[0]
+            else:
+                return bad_request(environ, start_response, "more than one password parameter")
+        else:
+            return bad_request(environ, start_response, "no password specified")
+
+        # Get the ccache we'll use and attempt to get credentials in it with user,password
+        ipa_ccache_name = get_ipa_ccache_name()
+        try:
+            self.kinit(user, self.api.env.realm, password, ipa_ccache_name)
+        except InvalidSessionPassword, e:
+            return unauthorized(environ, start_response, str(e))
+
+        return self.finalize_kerberos_acquisition('login_password', ipa_ccache_name, environ, start_response)
+
+    def kinit(self, user, realm, password, ccache_name):
+        # Format the user as a kerberos principal
+        principal = krb5_format_principal_name(user, realm)
+
+        (stdout, stderr, returncode) = ipautil.run(['/usr/bin/kinit', principal],
+                                                   env={'KRB5CCNAME':ccache_name},
+                                                   stdin=password, raiseonerr=False)
+        self.debug('kinit: principal=%s returncode=%s, stderr="%s"',
+                   principal, returncode, stderr)
 
+        if returncode != 0:
+            raise InvalidSessionPassword(principal=principal, message=unicode(stderr))
 
diff --git a/tests/test_ipaserver/test_rpcserver.py b/tests/test_ipaserver/test_rpcserver.py
index e712078..15ca9dc 100644
--- a/tests/test_ipaserver/test_rpcserver.py
+++ b/tests/test_ipaserver/test_rpcserver.py
@@ -100,14 +100,14 @@ class test_session(object):
             )
 
         inst = self.klass()
-        inst.mount(app1, 'foo')
-        inst.mount(app2, 'bar')
+        inst.mount(app1, '/foo/stuff')
+        inst.mount(app2, '/bar')
 
         d = dict(SCRIPT_NAME='/ipa', PATH_INFO='/foo/stuff')
-        assert inst.route(d, None) == ('from 1', ['/ipa/foo', '/stuff'])
+        assert inst.route(d, None) == ('from 1', ['/ipa', '/foo/stuff'])
 
         d = dict(SCRIPT_NAME='/ipa', PATH_INFO='/bar')
-        assert inst.route(d, None) == ('from 2', ['/ipa/bar', ''])
+        assert inst.route(d, None) == ('from 2', ['/ipa', '/bar'])
 
     def test_mount(self):
         def app1(environ, start_response):
-- 
1.7.7.6

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to