mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Implement password based session login
* 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 https://fedorahosted.org/freeipa/ticket/2095
This commit is contained in:
parent
059a90702e
commit
ee780df13c
@ -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
|
# LoadModule auth_kerb_module modules/mod_auth_kerb.so
|
||||||
|
|
||||||
@ -60,7 +60,13 @@ KrbConstrainedDelegationLock ipa
|
|||||||
</Location>
|
</Location>
|
||||||
|
|
||||||
# Turn off Apache authentication for sessions
|
# 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
|
Satisfy Any
|
||||||
Order Deny,Allow
|
Order Deny,Allow
|
||||||
Allow from all
|
Allow from all
|
||||||
|
@ -60,7 +60,7 @@ var IPA = function() {
|
|||||||
// if current path matches live server path, use live data
|
// if current path matches live server path, use live data
|
||||||
if (that.url && window.location.pathname.substring(0, that.url.length) === that.url) {
|
if (that.url && window.location.pathname.substring(0, that.url.length) === that.url) {
|
||||||
that.json_url = params.url || '/ipa/session/json';
|
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
|
} else { // otherwise use fixtures
|
||||||
that.json_path = params.url || "test/data";
|
that.json_path = params.url || "test/data";
|
||||||
|
@ -612,6 +612,13 @@ class SessionError(AuthenticationError):
|
|||||||
format= _('Session error')
|
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
|
# 2000 - 2999: Authorization errors
|
||||||
class AuthorizationError(PublicError):
|
class AuthorizationError(PublicError):
|
||||||
|
@ -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
|
Given a Kerberos ccache name parse it into it's scheme and
|
||||||
location components. Currently valid values for the scheme
|
location components. Currently valid values for the scheme
|
||||||
@ -55,12 +55,12 @@ def krb5_parse_ccache(name):
|
|||||||
does not exist it defaults to FILE.
|
does not exist it defaults to FILE.
|
||||||
|
|
||||||
:parameters:
|
:parameters:
|
||||||
name
|
ccache_name
|
||||||
The name of the Kerberos ccache.
|
The name of the Kerberos ccache.
|
||||||
:returns:
|
:returns:
|
||||||
A two-tuple of (scheme, ccache)
|
A two-tuple of (scheme, ccache)
|
||||||
'''
|
'''
|
||||||
match = ccache_name_re.search(name)
|
match = ccache_name_re.search(ccache_name)
|
||||||
if match:
|
if match:
|
||||||
scheme = match.group(2)
|
scheme = match.group(2)
|
||||||
location = match.group(3)
|
location = match.group(3)
|
||||||
@ -71,7 +71,10 @@ def krb5_parse_ccache(name):
|
|||||||
|
|
||||||
return scheme, location
|
return scheme, location
|
||||||
else:
|
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):
|
def krb5_format_principal_name(user, realm):
|
||||||
'''
|
'''
|
||||||
@ -388,5 +391,5 @@ class KRB5_CCache(object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
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
|
return result
|
||||||
|
@ -1197,34 +1197,71 @@ class MemcacheSessionManager(SessionManager):
|
|||||||
krbccache_dir ='/var/run/ipa_memcached'
|
krbccache_dir ='/var/run/ipa_memcached'
|
||||||
krbccache_prefix = 'krbcc_'
|
krbccache_prefix = 'krbcc_'
|
||||||
|
|
||||||
def get_krbccache_pathname():
|
def _get_krbccache_pathname():
|
||||||
return os.path.join(krbccache_dir, '%s%s' % (krbccache_prefix, os.getpid()))
|
return os.path.join(krbccache_dir, '%s%s' % (krbccache_prefix, os.getpid()))
|
||||||
|
|
||||||
def read_krbccache_file(krbccache_pathname):
|
def get_ipa_ccache_name(scheme='FILE'):
|
||||||
root_logger.debug('reading krbccache data from "%s"', krbccache_pathname)
|
if scheme == 'FILE':
|
||||||
src = open(krbccache_pathname)
|
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()
|
ccache_data = src.read()
|
||||||
src.close()
|
src.close()
|
||||||
return ccache_data
|
return ccache_data
|
||||||
|
else:
|
||||||
|
raise ValueError('ccache scheme "%s" unsupported (%s)', scheme, ccache_name)
|
||||||
|
|
||||||
def store_krbccache_file(ccache_data):
|
def bind_ipa_ccache(ccache_data, scheme='FILE'):
|
||||||
krbccache_pathname = get_krbccache_pathname()
|
if scheme == 'FILE':
|
||||||
root_logger.debug('storing krbccache data into "%s"', krbccache_pathname)
|
name = _get_krbccache_pathname()
|
||||||
dst = open(krbccache_pathname, 'w')
|
root_logger.debug('storing ccache data into file "%s"', name)
|
||||||
|
dst = open(name, 'w')
|
||||||
dst.write(ccache_data)
|
dst.write(ccache_data)
|
||||||
dst.close()
|
dst.close()
|
||||||
|
else:
|
||||||
|
raise ValueError('ccache scheme "%s" unsupported', scheme)
|
||||||
|
|
||||||
return krbccache_pathname
|
ccache_name = krb5_unparse_ccache(scheme, name)
|
||||||
|
os.environ['KRB5CCNAME'] = ccache_name
|
||||||
|
return ccache_name
|
||||||
|
|
||||||
def delete_krbccache_file(krbccache_pathname=None):
|
def release_ipa_ccache(ccache_name):
|
||||||
if krbccache_pathname is None:
|
'''
|
||||||
krbccache_pathname = get_krbccache_pathname()
|
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:
|
try:
|
||||||
os.unlink(krbccache_pathname)
|
os.unlink(name)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
root_logger.error('unable to delete session krbccache file "%s", %s',
|
root_logger.error('unable to delete session ccache file "%s", %s', name, e)
|
||||||
krbccache_pathname, e)
|
else:
|
||||||
|
raise ValueError('ccache scheme "%s" unsupported (%s)', scheme, ccache_name)
|
||||||
|
|
||||||
|
|
||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
|
@ -25,9 +25,10 @@ Loads WSGI server plugins.
|
|||||||
from ipalib import api
|
from ipalib import api
|
||||||
|
|
||||||
if 'in_server' in api.env and api.env.in_server is True:
|
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(wsgi_dispatch)
|
||||||
api.register(xmlserver)
|
api.register(xmlserver)
|
||||||
api.register(jsonserver_kerb)
|
api.register(jsonserver_kerb)
|
||||||
api.register(jsonserver_session)
|
api.register(jsonserver_session)
|
||||||
api.register(krblogin)
|
api.register(login_kerberos)
|
||||||
|
api.register(login_password)
|
||||||
|
@ -27,14 +27,15 @@ from cgi import parse_qs
|
|||||||
from xml.sax.saxutils import escape
|
from xml.sax.saxutils import escape
|
||||||
from xmlrpclib import Fault
|
from xmlrpclib import Fault
|
||||||
from ipalib.backend import Executioner
|
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.request import context, Connection, destroy_context
|
||||||
from ipalib.rpc import xml_dumps, xml_loads
|
from ipalib.rpc import xml_dumps, xml_loads
|
||||||
from ipalib.util import make_repr, parse_time_duration
|
from ipalib.util import make_repr, parse_time_duration
|
||||||
from ipapython.compat import json
|
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.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 wsgiref.util import shift_path_info
|
||||||
from ipapython.version import VERSION
|
from ipapython.version import VERSION
|
||||||
import base64
|
import base64
|
||||||
@ -42,6 +43,11 @@ import os
|
|||||||
import string
|
import string
|
||||||
import datetime
|
import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
import urlparse
|
||||||
|
|
||||||
|
HTTP_STATUS_SUCCESS = '200 Success'
|
||||||
|
HTTP_STATUS_SERVER_ERROR = '500 Internal Server Error'
|
||||||
|
|
||||||
_not_found_template = """<html>
|
_not_found_template = """<html>
|
||||||
<head>
|
<head>
|
||||||
<title>404 Not Found</title>
|
<title>404 Not Found</title>
|
||||||
@ -54,19 +60,84 @@ The requested URL <strong>%(url)s</strong> was not found on this server.
|
|||||||
</body>
|
</body>
|
||||||
</html>"""
|
</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):
|
def not_found(environ, start_response):
|
||||||
"""
|
"""
|
||||||
Return a 404 Not Found error.
|
Return a 404 Not Found error.
|
||||||
"""
|
"""
|
||||||
status = '404 Not Found'
|
status = '404 Not Found'
|
||||||
response_headers = [('Content-Type', 'text/html')]
|
response_headers = [('Content-Type', 'text/html; charset=utf-8')]
|
||||||
start_response(status, response_headers)
|
start_response(status, response_headers)
|
||||||
output = _not_found_template % dict(
|
output = _not_found_template % dict(
|
||||||
url=escape(environ['SCRIPT_NAME'] + environ['PATH_INFO'])
|
url=escape(environ['SCRIPT_NAME'] + environ['PATH_INFO'])
|
||||||
)
|
)
|
||||||
return [output]
|
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):
|
def read_input(environ):
|
||||||
"""
|
"""
|
||||||
Read the request body from environ['wsgi.input'].
|
Read the request body from environ['wsgi.input'].
|
||||||
@ -267,14 +338,14 @@ class WSGIExecutioner(Executioner):
|
|||||||
|
|
||||||
self.debug('WSGI WSGIExecutioner.__call__:')
|
self.debug('WSGI WSGIExecutioner.__call__:')
|
||||||
try:
|
try:
|
||||||
status = '200 OK'
|
status = HTTP_STATUS_SUCCESS
|
||||||
response = self.wsgi_execute(environ)
|
response = self.wsgi_execute(environ)
|
||||||
headers = [('Content-Type', self.content_type + '; charset=utf-8')]
|
headers = [('Content-Type', self.content_type + '; charset=utf-8')]
|
||||||
except StandardError, e:
|
except StandardError, e:
|
||||||
self.exception('WSGI %s.__call__():', self.name)
|
self.exception('WSGI %s.__call__():', self.name)
|
||||||
status = '500 Internal Server Error'
|
status = HTTP_STATUS_SERVER_ERROR
|
||||||
response = status
|
response = status
|
||||||
headers = [('Content-Type', 'text/plain')]
|
headers = [('Content-Type', 'text/plain; charset=utf-8')]
|
||||||
|
|
||||||
session_data = getattr(context, 'session_data', None)
|
session_data = getattr(context, 'session_data', None)
|
||||||
if session_data is not None:
|
if session_data is not None:
|
||||||
@ -316,17 +387,16 @@ class xmlserver(WSGIExecutioner):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
self.debug('WSGI xmlserver.__call__:')
|
self.debug('WSGI xmlserver.__call__:')
|
||||||
ccache=environ.get('KRB5CCNAME')
|
user_ccache=environ.get('KRB5CCNAME')
|
||||||
if ccache is None:
|
if user_ccache is None:
|
||||||
return self.marshal(None, CCacheError())
|
return self.marshal(None, CCacheError())
|
||||||
self.create_context(ccache=ccache)
|
|
||||||
try:
|
try:
|
||||||
self.create_context(ccache=environ.get('KRB5CCNAME'))
|
self.create_context(ccache=user_ccache)
|
||||||
response = super(xmlserver, self).__call__(environ, start_response)
|
response = super(xmlserver, self).__call__(environ, start_response)
|
||||||
except PublicError, e:
|
except PublicError, e:
|
||||||
status = '200 OK'
|
status = HTTP_STATUS_SUCCESS
|
||||||
response = status
|
response = status
|
||||||
headers = [('Content-Type', 'text/plain')]
|
headers = [('Content-Type', 'text/plain; charset=utf-8')]
|
||||||
start_response(status, headers)
|
start_response(status, headers)
|
||||||
return self.marshal(None, e)
|
return self.marshal(None, e)
|
||||||
finally:
|
finally:
|
||||||
@ -619,6 +689,40 @@ class KerberosSession(object):
|
|||||||
max_age=krb_expiration,
|
max_age=krb_expiration,
|
||||||
duration_type=self.api.env.session_duration_type)
|
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):
|
class jsonserver_session(jsonserver, KerberosSession):
|
||||||
"""
|
"""
|
||||||
JSON RPC server protected with session auth.
|
JSON RPC server protected with session auth.
|
||||||
@ -668,13 +772,14 @@ class jsonserver_session(jsonserver, KerberosSession):
|
|||||||
self.debug('no ccache, need login')
|
self.debug('no ccache, need login')
|
||||||
return self.need_login(start_response)
|
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
|
# 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):
|
if not cc.valid(self.api.env.host, self.api.env.realm):
|
||||||
self.debug('ccache expired, deleting session, need login')
|
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)
|
return self.need_login(start_response)
|
||||||
|
|
||||||
# Update the session expiration based on the Kerberos expiration
|
# 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
|
# Store the session data in the per-thread context
|
||||||
setattr(context, 'session_data', session_data)
|
setattr(context, 'session_data', session_data)
|
||||||
|
|
||||||
self.create_context(ccache=krbccache_pathname)
|
self.create_context(ccache=ipa_ccache_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = super(jsonserver_session, self).__call__(environ, start_response)
|
response = super(jsonserver_session, self).__call__(environ, start_response)
|
||||||
@ -701,10 +806,10 @@ class jsonserver_session(jsonserver, KerberosSession):
|
|||||||
# data to invalidate the session credentials.
|
# data to invalidate the session credentials.
|
||||||
|
|
||||||
if session_data.has_key('ccache_data'):
|
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
|
# The request is finished with the ccache, destroy it.
|
||||||
delete_krbccache_file(krbccache_pathname)
|
release_ipa_ccache(ipa_ccache_name)
|
||||||
# Store the session data.
|
# Store the session data.
|
||||||
session_mgr.store_session_data(session_data)
|
session_mgr.store_session_data(session_data)
|
||||||
destroy_context()
|
destroy_context()
|
||||||
@ -724,10 +829,10 @@ class jsonserver_kerb(jsonserver):
|
|||||||
|
|
||||||
self.debug('WSGI jsonserver_kerb.__call__:')
|
self.debug('WSGI jsonserver_kerb.__call__:')
|
||||||
|
|
||||||
ccache=environ.get('KRB5CCNAME')
|
user_ccache=environ.get('KRB5CCNAME')
|
||||||
if ccache is None:
|
if user_ccache is None:
|
||||||
return self.marshal(None, CCacheError())
|
return self.marshal(None, CCacheError())
|
||||||
self.create_context(ccache=ccache)
|
self.create_context(ccache=user_ccache)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = super(jsonserver_kerb, self).__call__(environ, start_response)
|
response = super(jsonserver_kerb, self).__call__(environ, start_response)
|
||||||
@ -737,59 +842,96 @@ class jsonserver_kerb(jsonserver):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class krblogin(Backend, KerberosSession):
|
class login_kerberos(Backend, KerberosSession):
|
||||||
key = '/login'
|
key = '/session/login_kerberos'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(krblogin, self).__init__()
|
super(login_kerberos, self).__init__()
|
||||||
|
|
||||||
def _on_finalize(self):
|
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.api.Backend.wsgi_dispatch.mount(self, self.key)
|
||||||
self.kerb_session_on_finalize()
|
self.kerb_session_on_finalize()
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
def __call__(self, environ, start_response):
|
||||||
headers = []
|
self.debug('WSGI login_kerberos.__call__:')
|
||||||
|
|
||||||
self.debug('WSGI krblogin.__call__:')
|
|
||||||
|
|
||||||
# Get the ccache created by mod_auth_kerb
|
# Get the ccache created by mod_auth_kerb
|
||||||
ccache=environ.get('KRB5CCNAME')
|
user_ccache_name=environ.get('KRB5CCNAME')
|
||||||
if ccache is None:
|
if user_ccache_name is None:
|
||||||
status = '500 Internal Error'
|
return internal_error(environ, start_response, 'KRB5CCNAME not defined')
|
||||||
response = 'KRB5CCNAME not defined'
|
|
||||||
start_response(status, headers)
|
|
||||||
return [response]
|
|
||||||
|
|
||||||
ccache_scheme, ccache_location = krb5_parse_ccache(ccache)
|
return self.finalize_kerberos_acquisition('login_kerberos', user_ccache_name, environ, start_response)
|
||||||
assert ccache_scheme == 'FILE'
|
|
||||||
|
|
||||||
# Retrieve the session data (or newly create)
|
class login_password(Backend, KerberosSession):
|
||||||
session_data = session_mgr.load_session_data(environ.get('HTTP_COOKIE'))
|
|
||||||
session_id = session_data['session_id']
|
|
||||||
|
|
||||||
# Copy the ccache file contents into the session data
|
content_type = 'text/plain'
|
||||||
session_data['ccache_data'] = read_krbccache_file(ccache_location)
|
key = '/session/login_password'
|
||||||
|
|
||||||
# Set when the session will expire
|
def __init__(self):
|
||||||
cc = KRB5_CCache(ccache)
|
super(login_password, self).__init__()
|
||||||
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
|
def _on_finalize(self):
|
||||||
session_mgr.store_session_data(session_data)
|
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"',
|
def __call__(self, environ, start_response):
|
||||||
ccache, session_id, ccache)
|
self.debug('WSGI login_password.__call__:')
|
||||||
|
|
||||||
# Return success and set session cookie
|
# Get the user and password parameters from the request
|
||||||
status = '200 Success'
|
content_type = environ.get('CONTENT_TYPE', '').lower()
|
||||||
response = ''
|
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)
|
method = environ.get('REQUEST_METHOD', '').upper()
|
||||||
headers.append(('Set-Cookie', session_cookie))
|
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)
|
try:
|
||||||
return [response]
|
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))
|
||||||
|
|
||||||
|
@ -100,14 +100,14 @@ class test_session(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
inst = self.klass()
|
inst = self.klass()
|
||||||
inst.mount(app1, 'foo')
|
inst.mount(app1, '/foo/stuff')
|
||||||
inst.mount(app2, 'bar')
|
inst.mount(app2, '/bar')
|
||||||
|
|
||||||
d = dict(SCRIPT_NAME='/ipa', PATH_INFO='/foo/stuff')
|
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')
|
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 test_mount(self):
|
||||||
def app1(environ, start_response):
|
def app1(environ, start_response):
|
||||||
|
Loading…
Reference in New Issue
Block a user