summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--install/conf/ipa.conf8
-rw-r--r--ipaserver/plugins/ldap2.py14
-rw-r--r--ipaserver/plugins/xmlserver.py3
-rw-r--r--ipaserver/rpcserver.py110
4 files changed, 126 insertions, 9 deletions
diff --git a/install/conf/ipa.conf b/install/conf/ipa.conf
index f4dac9827..7eede73ef 100644
--- a/install/conf/ipa.conf
+++ b/install/conf/ipa.conf
@@ -1,5 +1,5 @@
#
-# VERSION 15 - DO NOT REMOVE THIS LINE
+# VERSION 16 - DO NOT REMOVE THIS LINE
#
# This file may be overwritten on upgrades.
#
@@ -103,6 +103,12 @@ KrbConstrainedDelegationLock ipa
Allow from all
</Location>
+<Location "/ipa/session/sync_token">
+ Satisfy Any
+ Order Deny,Allow
+ Allow from all
+</Location>
+
# This is where we redirect on failed auth
Alias /ipa/errors "/usr/share/ipa/html"
diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py
index 29bb20d41..9ecd0b87c 100644
--- a/ipaserver/plugins/ldap2.py
+++ b/ipaserver/plugins/ldap2.py
@@ -93,7 +93,7 @@ class ldap2(LDAPClient, CrudBackend):
def create_connection(self, ccache=None, bind_dn=None, bind_pw='',
tls_cacertfile=None, tls_certfile=None, tls_keyfile=None,
- debug_level=0, autobind=False):
+ debug_level=0, autobind=False, serverctrls=None, clientctrls=None):
"""
Connect to LDAP server.
@@ -151,16 +151,22 @@ class ldap2(LDAPClient, CrudBackend):
context=krbV.default_context()).principal().name
os.environ['KRB5CCNAME'] = ccache
- conn.sasl_interactive_bind_s(None, SASL_GSSAPI)
+ conn.sasl_interactive_bind_s(None, SASL_GSSAPI,
+ serverctrls=serverctrls,
+ clientctrls=clientctrls)
setattr(context, 'principal', principal)
else:
# no kerberos ccache, use simple bind or external sasl
if autobind:
pent = pwd.getpwuid(os.geteuid())
auth_tokens = _ldap.sasl.external(pent.pw_name)
- conn.sasl_interactive_bind_s(None, auth_tokens)
+ conn.sasl_interactive_bind_s(None, auth_tokens,
+ serverctrls=serverctrls,
+ clientctrls=clientctrls)
else:
- conn.simple_bind_s(bind_dn, bind_pw)
+ conn.simple_bind_s(bind_dn, bind_pw,
+ serverctrls=serverctrls,
+ clientctrls=clientctrls)
return conn
diff --git a/ipaserver/plugins/xmlserver.py b/ipaserver/plugins/xmlserver.py
index 8d96262cf..7460ead69 100644
--- a/ipaserver/plugins/xmlserver.py
+++ b/ipaserver/plugins/xmlserver.py
@@ -25,7 +25,7 @@ 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, change_password, xmlserver_session
+ from ipaserver.rpcserver import wsgi_dispatch, xmlserver, jsonserver_kerb, jsonserver_session, login_kerberos, login_password, change_password, sync_token, xmlserver_session
api.register(wsgi_dispatch)
api.register(xmlserver)
api.register(jsonserver_kerb)
@@ -33,4 +33,5 @@ if 'in_server' in api.env and api.env.in_server is True:
api.register(login_kerberos)
api.register(login_password)
api.register(change_password)
+ api.register(sync_token)
api.register(xmlserver_session)
diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py
index 0838ad143..454a53451 100644
--- a/ipaserver/rpcserver.py
+++ b/ipaserver/rpcserver.py
@@ -30,6 +30,10 @@ import datetime
import urlparse
import json
+import ldap.controls
+from pyasn1.type import univ, namedtype
+from pyasn1.codec.ber import encoder
+
from ipalib import plugable, errors
from ipalib.capabilities import VERSION_WITHOUT_CAPABILITIES
from ipalib.backend import Executioner
@@ -106,7 +110,7 @@ _unauthorized_template = """<html>
</body>
</html>"""
-_pwchange_template = """<html>
+_success_template = """<html>
<head>
<title>200 Success</title>
</head>
@@ -1105,10 +1109,110 @@ class change_password(Backend, HTTP_Status):
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))
+ output = _success_template % dict(title=str(title),
+ message=str(message))
return [output]
+class sync_token(Backend, HTTP_Status):
+ content_type = 'text/plain'
+ key = '/session/sync_token'
+
+ class OTPSyncRequest(univ.Sequence):
+ OID = "2.16.840.1.113730.3.8.10.6"
+
+ componentType = namedtype.NamedTypes(
+ namedtype.NamedType('firstCode', univ.OctetString()),
+ namedtype.NamedType('secondCode', univ.OctetString()),
+ namedtype.OptionalNamedType('tokenDN', univ.OctetString())
+ )
+
+ def __init__(self):
+ super(sync_token, self).__init__()
+
+ def _on_finalize(self):
+ super(sync_token, self)._on_finalize()
+ self.api.Backend.wsgi_dispatch.mount(self, self.key)
+
+ def __call__(self, environ, start_response):
+ # Make sure this is a form 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")
+
+ # Make sure this is a POST request.
+ 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")
+
+ # Parse the query string to a dictionary.
+ 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', 'password', 'first_code', 'second_code', 'token'):
+ 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)
+ elif field != 'token':
+ return self.bad_request(environ, start_response, "no %s specified" % field)
+
+ # Create the request control.
+ sr = self.OTPSyncRequest()
+ sr.setComponentByName('firstCode', data['first_code'])
+ sr.setComponentByName('secondCode', data['second_code'])
+ if 'token' in data:
+ try:
+ token_dn = DN(data['token'])
+ except ValueError:
+ token_dn = DN((self.api.Object.otptoken.primary_key.name, data['token']),
+ self.api.env.container_otp, self.api.env.basedn)
+
+ sr.setComponentByName('tokenDN', str(token_dn))
+ rc = ldap.controls.RequestControl(sr.OID, True, encoder.encode(sr))
+
+ # Resolve the user DN
+ bind_dn = DN((self.api.Object.user.primary_key.name, data['user']),
+ self.api.env.container_user, self.api.env.basedn)
+
+ # Start building the response.
+ status = HTTP_STATUS_SUCCESS
+ response_headers = [('Content-Type', 'text/html; charset=utf-8')]
+ title = 'Token sync rejected'
+
+ # Perform the synchronization.
+ conn = ldap2(shared_instance=False, ldap_uri=self.api.env.ldap_uri)
+ try:
+ conn.connect(bind_dn=bind_dn,
+ bind_pw=data['password'],
+ serverctrls=[rc,])
+ result = 'ok'
+ title = "Token sync successful"
+ message = "Token was synchronized."
+ except (NotFound, ACIError):
+ result = 'invalid-credentials'
+ message = 'The username, password or token codes are not correct.'
+ except Exception, e:
+ result = 'error'
+ message = "Could not connect to LDAP server."
+ self.error("token_sync: cannot authenticate '%s' to LDAP server: %s",
+ data['user'], str(e))
+ finally:
+ if conn.isconnected():
+ conn.destroy_connection()
+
+ # Report status and return.
+ response_headers.append(('X-IPA-TokenSync-Result', result))
+ start_response(status, response_headers)
+ output = _success_template % dict(title=str(title),
+ message=str(message))
+ return [output]
class xmlserver_session(xmlserver, KerberosSession):
"""