summaryrefslogtreecommitdiffstats
path: root/ipaserver
diff options
context:
space:
mode:
authorNathaniel McCallum <npmccallum@redhat.com>2014-05-28 11:38:40 -0400
committerMartin Kosek <mkosek@redhat.com>2014-06-26 15:55:24 +0200
commit14b38b7704778b4000a7b1b31d78fbb6b45e647b (patch)
tree13408e8d9f6204e7f311592bd4c1ab7f284a6744 /ipaserver
parent1c94edd3a09711d85ba099bd815c0bdd8f0210c1 (diff)
downloadfreeipa-14b38b7704778b4000a7b1b31d78fbb6b45e647b.tar.gz
freeipa-14b38b7704778b4000a7b1b31d78fbb6b45e647b.tar.xz
freeipa-14b38b7704778b4000a7b1b31d78fbb6b45e647b.zip
Add /session/token_sync POST support
This HTTP call takes the following parameters: * user * password * first_code * second_code * token (optional) Using this information, the server will perform token synchronization. If the token is not specified, all tokens will be searched for synchronization. Otherwise, only the token specified will be searched. https://fedorahosted.org/freeipa/ticket/4218 Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Diffstat (limited to 'ipaserver')
-rw-r--r--ipaserver/plugins/ldap2.py14
-rw-r--r--ipaserver/plugins/xmlserver.py3
-rw-r--r--ipaserver/rpcserver.py110
3 files changed, 119 insertions, 8 deletions
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):
"""