summaryrefslogtreecommitdiffstats
path: root/ipaserver/rpcserver.py
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/rpcserver.py
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/rpcserver.py')
-rw-r--r--ipaserver/rpcserver.py110
1 files changed, 107 insertions, 3 deletions
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):
"""