diff options
author | Martin Kosek <mkosek@redhat.com> | 2012-06-06 14:38:08 +0200 |
---|---|---|
committer | Rob Crittenden <rcritten@redhat.com> | 2012-06-11 23:07:03 -0400 |
commit | d1e695b5d0323167d37eee340718eb5e65138716 (patch) | |
tree | eca31d880051605493df2f6f09cba6730c8f33f0 /ipaserver | |
parent | 34a1dee93420805ba48fbe077b4e2a8cea351151 (diff) | |
download | freeipa-d1e695b5d0323167d37eee340718eb5e65138716.tar.gz freeipa-d1e695b5d0323167d37eee340718eb5e65138716.tar.xz freeipa-d1e695b5d0323167d37eee340718eb5e65138716.zip |
Password change capability for form-based auth
IPA server web form-based authentication allows logins for users
which for some reason cannot use Kerberos authentication. However,
when a password for such users expires, they are unable change the
password via web interface.
This patch adds a new WSGI script attached to URL
/ipa/session/change_password which can be accessed without
authentication and which provides password change capability
for web services.
The actual password change in the script is processed by LDAP
password change command.
Password result is passed both in the resulting HTML page, but
also in HTTP headers for easier parsing in web services:
X-IPA-Pwchange-Result: {ok, invalid-password, policy-error, error}
(optional) X-IPA-Pwchange-Policy-Error: $policy_error_text
https://fedorahosted.org/freeipa/ticket/2276
Diffstat (limited to 'ipaserver')
-rw-r--r-- | ipaserver/plugins/xmlserver.py | 3 | ||||
-rw-r--r-- | ipaserver/rpcserver.py | 108 |
2 files changed, 109 insertions, 2 deletions
diff --git a/ipaserver/plugins/xmlserver.py b/ipaserver/plugins/xmlserver.py index 4ae914950..bd9eb1fdf 100644 --- a/ipaserver/plugins/xmlserver.py +++ b/ipaserver/plugins/xmlserver.py @@ -25,10 +25,11 @@ 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, login_kerberos, login_password + from ipaserver.rpcserver import wsgi_dispatch, xmlserver, jsonserver_kerb, jsonserver_session, login_kerberos, login_password, change_password api.register(wsgi_dispatch) api.register(xmlserver) api.register(jsonserver_kerb) api.register(jsonserver_session) api.register(login_kerberos) api.register(login_password) + api.register(change_password) diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py index f9a549f4e..5abbaf1a6 100644 --- a/ipaserver/rpcserver.py +++ b/ipaserver/rpcserver.py @@ -28,7 +28,7 @@ from xml.sax.saxutils import escape from xmlrpclib import Fault from ipalib import plugable from ipalib.backend import Executioner -from ipalib.errors import PublicError, InternalError, CommandError, JSONError, ConversionError, CCacheError, RefererError, InvalidSessionPassword +from ipalib.errors import PublicError, InternalError, CommandError, JSONError, ConversionError, CCacheError, RefererError, InvalidSessionPassword, NotFound, ACIError, ExecutionError from ipalib.request import context, Connection, destroy_context from ipalib.rpc import xml_dumps, xml_loads from ipalib.util import parse_time_duration @@ -100,6 +100,18 @@ _unauthorized_template = """<html> </body> </html>""" +_pwchange_template = """<html> +<head> +<title>200 Success</title> +</head> +<body> +<h1>%(title)s</h1> +<p> +<strong>%(message)s</strong> +</p> +</body> +</html>""" + class HTTP_Status(plugable.Plugin): def not_found(self, environ, start_response, url, message): """ @@ -992,3 +1004,97 @@ class login_password(Backend, KerberosSession, HTTP_Status): if returncode != 0: raise InvalidSessionPassword(principal=principal, message=unicode(stderr)) +class change_password(Backend, HTTP_Status): + + content_type = 'text/plain' + key = '/session/change_password' + + def __init__(self): + super(change_password, self).__init__() + + def _on_finalize(self): + super(change_password, self)._on_finalize() + self.api.Backend.wsgi_dispatch.mount(self, self.key) + + def __call__(self, environ, start_response): + self.info('WSGI change_password.__call__:') + + # Get the user and password parameters from the request + content_type = environ.get('CONTENT_TYPE', '').lower() + if not content_type.startswith('application/x-www-form-urlencoded'): + return self.bad_request(environ, start_response, "Content-Type must be application/x-www-form-urlencoded") + + method = environ.get('REQUEST_METHOD', '').upper() + if method == 'POST': + query_string = read_input(environ) + else: + return self.bad_request(environ, start_response, "HTTP request method must be POST") + + try: + query_dict = urlparse.parse_qs(query_string) + except Exception, e: + return self.bad_request(environ, start_response, "cannot parse query data") + + data = {} + for field in ('user', 'old_password', 'new_password'): + value = query_dict.get(field, None) + if value is not None: + if len(value) == 1: + data[field] = value[0] + else: + return self.bad_request(environ, start_response, "more than one %s parameter" + % field) + else: + return self.bad_request(environ, start_response, "no %s specified" % field) + + # start building the response + self.info("WSGI change_password: start password change of user '%s'", data['user']) + status = HTTP_STATUS_SUCCESS + response_headers = [('Content-Type', 'text/html; charset=utf-8')] + title = 'Password change rejected' + result = 'error' + policy_error = None + + bind_dn = str(DN((self.api.Object.user.primary_key.name, data['user']), + self.api.env.container_user, self.api.env.basedn)) + + try: + conn = ldap2(shared_instance=False, + ldap_uri=self.api.env.ldap_uri) + conn.connect(bind_dn=bind_dn, bind_pw=data['old_password']) + except (NotFound, ACIError): + result = 'invalid-password' + message = 'The old password or username is not correct.' + except Exception, e: + message = "Could not connect to LDAP server." + self.error("change_password: cannot authenticate '%s' to LDAP server: %s", + data['user'], str(e)) + else: + try: + conn.modify_password(bind_dn, data['new_password'], data['old_password']) + except ExecutionError, e: + result = 'policy-error' + policy_error = escape(str(e)) + message = "Password change was rejected: %s" % escape(str(e)) + except Exception, e: + message = "Could not change the password" + self.error("change_password: cannot change password of '%s': %s", + data['user'], str(e)) + else: + result = 'ok' + title = "Password change successful" + message = "Password was changed." + finally: + if conn.isconnected(): + conn.destroy_connection() + + self.info('%s: %s', status, message) + + response_headers.append(('X-IPA-Pwchange-Result', result)) + if policy_error: + response_headers.append(('X-IPA-Pwchange-Policy-Error', policy_error)) + + start_response(status, response_headers) + output = _pwchange_template % dict(title=str(title), + message=str(message)) + return [output] |