Imported Debian patch 4.0.5-6~numeezy
This commit is contained in:
committed by
Mario Fetka
parent
c44de33144
commit
10dfc9587b
Binary file not shown.
@@ -1,12 +1,10 @@
|
||||
# Authors:
|
||||
# Ade Lee <alee@redhat.com>
|
||||
# Andrew Wnuk <awnuk@redhat.com>
|
||||
# Jason Gerard DeRose <jderose@redhat.com>
|
||||
# Rob Crittenden <rcritten@@redhat.com>
|
||||
# John Dennis <jdennis@redhat.com>
|
||||
# Fraser Tweedale <ftweedal@redhat.com>
|
||||
#
|
||||
# Copyright (C) 2014, 2015 Red Hat
|
||||
# Copyright (C) 2009 Red Hat
|
||||
# see file 'COPYING' for use and warranty information
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
@@ -36,7 +34,7 @@ variety of names, the open source version is called "dogtag".
|
||||
|
||||
CMS consists of a number of servlets which in rough terms can be thought of as
|
||||
RPC commands. A servlet is invoked by making an HTTP request to a specific URL
|
||||
and passing URL arguments. Normally CMS responds with an HTTP response consisting
|
||||
and passing URL arguments. Normally CMS responds with an HTTP reponse consisting
|
||||
of HTML to be rendered by a web browser. This HTTP HTML response has both
|
||||
Javascript SCRIPT components and HTML rendering code. One of the Javascript
|
||||
SCRIPT blocks holds the data for the result. The rest of the response is derived
|
||||
@@ -44,13 +42,13 @@ from templates associated with the servlet which may be customized. The
|
||||
templates pull the result data from Javascript variables.
|
||||
|
||||
One way to get the result data is to parse the HTML looking for the Javascript
|
||||
variable initializations. Simple string searches are not a robust method. First of
|
||||
varible initializations. Simple string searchs are not a robust method. First of
|
||||
all one must be sure the string is only found in a Javascript SCRIPT block and
|
||||
not somewhere else in the HTML document. Some of the Javascript variable
|
||||
initializations are rather complex (e.g. lists of structures). It would be hard
|
||||
to correctly parse such complex and diverse Javascript. Existing Javascript
|
||||
parsers are not generally available. Finally, it's important to know the
|
||||
character encoding for strings. There is a somewhat complex set of precedent
|
||||
character encoding for strings. There is a somewhat complex set of precident
|
||||
rules for determining the current character encoding from the HTTP header,
|
||||
meta-equiv tags, mime Content-Type and charset attributes on HTML elements. All
|
||||
of this means trying to read the result data from a CMS HTML response is
|
||||
@@ -121,7 +119,7 @@ values. Python also nicely handles type promotion transparently between int
|
||||
and long objects. For example if you multiply two int objects you may get back
|
||||
a long object if necessary. In general Python int and long objects may be
|
||||
freely mixed without the programmer needing to be aware of which type of
|
||||
integral object is being operated on.
|
||||
intergral object is being operated on.
|
||||
|
||||
The leads to the following rule, always parse a string representing an
|
||||
integral value using the int() constructor even if it might have large
|
||||
@@ -164,16 +162,16 @@ Basic rules on handling these values
|
||||
1. Reading a serial number from CMS requires conversion from hexadecimal
|
||||
by converting it into a Python int or long object, use the int constructor:
|
||||
|
||||
serial_number = int(serial_number, 16)
|
||||
>>> serial_number = int(serial_number, 16)
|
||||
|
||||
2. Big integers passed to XMLRPC must be decimal unicode strings
|
||||
|
||||
unicode(serial_number)
|
||||
>>> unicode(serial_number)
|
||||
|
||||
3. Big integers received from XMLRPC must be converted back to int or long
|
||||
objects from the decimal string representation.
|
||||
|
||||
serial_number = int(serial_number)
|
||||
>>> serial_number = int(serial_number)
|
||||
|
||||
Xpath pattern matching on node names:
|
||||
-------------------------------------
|
||||
@@ -203,7 +201,7 @@ want to pass the node name. To do this use the name() function. One way we could
|
||||
solve the chapter problem above is by using a predicate which says if the node
|
||||
name begins with 'chapter' it's a match. Here is how you can do that.
|
||||
|
||||
doc.xpath("//book/*[starts-with(name(), 'chapter')]/section[2]")
|
||||
>>> doc.xpath("//book/*[starts-with(name(), 'chapter')]/section[2]")
|
||||
|
||||
The built-in starts-with() returns true if its first argument starts with its
|
||||
second argument. Thus the example above says if the node name of the second
|
||||
@@ -220,10 +218,10 @@ it to bind to those namespaces during its evaluation. Then we just use the
|
||||
EXSLT regular expression match() function on the node name. Here is how this is
|
||||
done:
|
||||
|
||||
regexpNS = "http://exslt.org/regular-expressions"
|
||||
find = etree.XPath("//book/*[re:match(name(), '^chapter(_\d+)$')]/section[2]",
|
||||
namespaces={'re':regexpNS}
|
||||
find(doc)
|
||||
>>> regexpNS = "http://exslt.org/regular-expressions"
|
||||
>>> find = etree.XPath("//book/*[re:match(name(), '^chapter(_\d+)$')]/section[2]",
|
||||
... namespaces={'re':regexpNS}
|
||||
>>> find(doc)
|
||||
|
||||
What is happening here is that etree.XPath() has returned us an evaluator
|
||||
function which we bind to the name 'find'. We've passed it a set of namespaces
|
||||
@@ -231,36 +229,20 @@ as a dict via the 'namespaces' keyword parameter of etree.XPath(). The predicate
|
||||
for the second location step uses the 're:' namespace to find the function name
|
||||
'match'. The re:match() takes a string to search as its first argument and a
|
||||
regular expression pattern as its second argument. In this example the string
|
||||
to search is the node name of the location step because we called the built-in
|
||||
to seach is the node name of the location step because we called the built-in
|
||||
node() function of XPath. The regular expression pattern we've passed says it's
|
||||
a match if the string begins with 'chapter' is followed by any number of
|
||||
digits and nothing else follows.
|
||||
|
||||
'''
|
||||
|
||||
import datetime
|
||||
import json
|
||||
from lxml import etree
|
||||
import os
|
||||
import tempfile
|
||||
import urllib2
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import pki
|
||||
from pki.client import PKIConnection
|
||||
import pki.crypto as cryptoutil
|
||||
from pki.kra import KRAClient
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
from ipalib import Backend
|
||||
from ipapython.dn import DN
|
||||
import ipapython.cookie
|
||||
import ipapython.dogtag
|
||||
from ipapython import ipautil
|
||||
from ipaserver.install.certs import CertDB
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
# These are general status return values used when
|
||||
# CMSServlet.outputError() is invoked.
|
||||
@@ -278,7 +260,6 @@ CMS_STATUS_REJECTED = 5
|
||||
CMS_STATUS_ERROR = 6
|
||||
CMS_STATUS_EXCEPTION = 7
|
||||
|
||||
|
||||
def cms_request_status_to_string(request_status):
|
||||
'''
|
||||
:param request_status: The integral request status value
|
||||
@@ -309,7 +290,7 @@ def parse_and_set_boolean_xml(node, response, response_name):
|
||||
'''
|
||||
:param node: xml node object containing value to parse for boolean result
|
||||
:param response: response dict to set boolean result in
|
||||
:param response_name: name of the response value to set
|
||||
:param response_name: name of the respone value to set
|
||||
:except ValueError:
|
||||
|
||||
Read the value out of a xml text node and interpret it as a boolean value.
|
||||
@@ -665,7 +646,7 @@ def parse_check_request_result_xml(doc):
|
||||
+-------------------------+---------------+-------------------+-----------------+
|
||||
|requestId |string |request_id |string |
|
||||
+-------------------------+---------------+-------------------+-----------------+
|
||||
|status |string |cert_request_status|unicode [1]_ |
|
||||
|staus |string |cert_request_status|unicode [1]_ |
|
||||
+-------------------------+---------------+-------------------+-----------------+
|
||||
|createdOn |long, timestamp|created_on |datetime.datetime|
|
||||
+-------------------------+---------------+-------------------+-----------------+
|
||||
@@ -1218,79 +1199,26 @@ def parse_unrevoke_cert_xml(doc):
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def host_has_service(host, ldap2, service='CA'):
|
||||
"""
|
||||
:param host: A host which might be a master for a service.
|
||||
:param ldap2: connection to the local database
|
||||
:param service: The service for which the host might be a master.
|
||||
:return: (true, false)
|
||||
|
||||
Check if a specified host is a master for a specified service.
|
||||
"""
|
||||
base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'),
|
||||
('cn', 'etc'), api.env.basedn)
|
||||
filter_attrs = {
|
||||
'objectClass': 'ipaConfigObject',
|
||||
'cn': service,
|
||||
'ipaConfigString': 'enabledService',
|
||||
}
|
||||
query_filter = ldap2.make_filter(filter_attrs, rules='&')
|
||||
try:
|
||||
ent, trunc = ldap2.find_entries(filter=query_filter, base_dn=base_dn)
|
||||
if len(ent):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def select_any_master(ldap2, service='CA'):
|
||||
"""
|
||||
:param ldap2: connection to the local database
|
||||
:param service: The service for which we're looking for a master.
|
||||
:return: host as str
|
||||
|
||||
Select any host which is a master for a specified service.
|
||||
"""
|
||||
base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
|
||||
api.env.basedn)
|
||||
filter_attrs = {
|
||||
'objectClass': 'ipaConfigObject',
|
||||
'cn': service,
|
||||
'ipaConfigString': 'enabledService',}
|
||||
query_filter = ldap2.make_filter(filter_attrs, rules='&')
|
||||
try:
|
||||
ent, trunc = ldap2.find_entries(filter=query_filter, base_dn=base_dn)
|
||||
if len(ent):
|
||||
entry = random.choice(ent)
|
||||
return entry.dn[1].value
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
from ipalib import api, errors, SkipPluginModule
|
||||
from ipalib import api, SkipPluginModule
|
||||
if api.env.ra_plugin != 'dogtag':
|
||||
# In this case, abort loading this plugin module...
|
||||
raise SkipPluginModule(reason='dogtag not selected as RA plugin')
|
||||
import os, random
|
||||
from ipaserver.plugins import rabase
|
||||
from ipalib.errors import CertificateOperationError
|
||||
from ipalib.constants import TYPE_ERROR
|
||||
from ipalib.util import cachedproperty
|
||||
from ipapython import dogtag
|
||||
from ipalib import _
|
||||
from ipaplatform.paths import paths
|
||||
|
||||
|
||||
class ra(rabase.rabase):
|
||||
"""
|
||||
Request Authority backend plugin.
|
||||
"""
|
||||
DEFAULT_PROFILE = dogtag.DEFAULT_PROFILE
|
||||
|
||||
def __init__(self, api):
|
||||
def __init__(self):
|
||||
if api.env.in_tree:
|
||||
self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
|
||||
self.pwd_file = self.sec_dir + os.sep + '.pwd'
|
||||
@@ -1307,7 +1235,7 @@ class ra(rabase.rabase):
|
||||
f.close()
|
||||
except IOError:
|
||||
self.password = ''
|
||||
super(ra, self).__init__(api)
|
||||
super(ra, self).__init__()
|
||||
|
||||
def raise_certificate_operation_error(self, func_name, err_msg=None, detail=None):
|
||||
"""
|
||||
@@ -1328,7 +1256,58 @@ class ra(rabase.rabase):
|
||||
err_msg = u'%s (%s)' % (err_msg, detail)
|
||||
|
||||
self.error('%s.%s(): %s', self.fullname, func_name, err_msg)
|
||||
raise errors.CertificateOperationError(error=err_msg)
|
||||
raise CertificateOperationError(error=err_msg)
|
||||
|
||||
def _host_has_service(self, host, service='CA'):
|
||||
"""
|
||||
:param host: A host which might be a master for a service.
|
||||
:param service: The service for which the host might be a master.
|
||||
:return: (true, false)
|
||||
|
||||
Check if a specified host is a master for a specified service.
|
||||
"""
|
||||
ldap2 = self.api.Backend.ldap2
|
||||
base_dn = DN(('cn', host), ('cn', 'masters'), ('cn', 'ipa'),
|
||||
('cn', 'etc'), api.env.basedn)
|
||||
filter_attrs = {
|
||||
'objectClass': 'ipaConfigObject',
|
||||
'cn': service,
|
||||
'ipaConfigString': 'enabledService',
|
||||
}
|
||||
filter = ldap2.make_filter(filter_attrs, rules='&')
|
||||
try:
|
||||
ent, trunc = ldap2.find_entries(filter=filter, base_dn=base_dn)
|
||||
if len(ent):
|
||||
return True
|
||||
except Exception, e:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _select_any_master(self, service='CA'):
|
||||
"""
|
||||
:param service: The service for which we're looking for a master.
|
||||
:return: host
|
||||
as str
|
||||
|
||||
Select any host which is a master for a specified service.
|
||||
"""
|
||||
ldap2 = self.api.Backend.ldap2
|
||||
base_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'),
|
||||
api.env.basedn)
|
||||
filter_attrs = {
|
||||
'objectClass': 'ipaConfigObject',
|
||||
'cn': service,
|
||||
'ipaConfigString': 'enabledService',
|
||||
}
|
||||
filter = ldap2.make_filter(filter_attrs, rules='&')
|
||||
try:
|
||||
ent, trunc = ldap2.find_entries(filter=filter, base_dn=base_dn)
|
||||
if len(ent):
|
||||
entry = random.choice(ent)
|
||||
return entry.dn[1].value
|
||||
except Exception, e:
|
||||
pass
|
||||
return None
|
||||
|
||||
@cachedproperty
|
||||
def ca_host(self):
|
||||
@@ -1338,13 +1317,12 @@ class ra(rabase.rabase):
|
||||
|
||||
Select our CA host.
|
||||
"""
|
||||
ldap2 = self.api.Backend.ldap2
|
||||
if host_has_service(api.env.ca_host, ldap2, "CA"):
|
||||
if self._host_has_service(host=api.env.ca_host):
|
||||
return api.env.ca_host
|
||||
if api.env.host != api.env.ca_host:
|
||||
if host_has_service(api.env.host, ldap2, "CA"):
|
||||
if self._host_has_service(host=api.env.host):
|
||||
return api.env.host
|
||||
host = select_any_master(ldap2)
|
||||
host = self._select_any_master()
|
||||
if host:
|
||||
return host
|
||||
else:
|
||||
@@ -1354,8 +1332,8 @@ class ra(rabase.rabase):
|
||||
"""
|
||||
:param url: The URL to post to.
|
||||
:param kw: Keyword arguments to encode into POST body.
|
||||
:return: (http_status, http_headers, http_body)
|
||||
as (integer, dict, str)
|
||||
:return: (http_status, http_reason_phrase, http_headers, http_body)
|
||||
as (integer, unicode, dict, str)
|
||||
|
||||
Perform an HTTP request.
|
||||
"""
|
||||
@@ -1365,8 +1343,8 @@ class ra(rabase.rabase):
|
||||
"""
|
||||
:param url: The URL to post to.
|
||||
:param kw: Keyword arguments to encode into POST body.
|
||||
:return: (http_status, http_headers, http_body)
|
||||
as (integer, dict, str)
|
||||
:return: (http_status, http_reason_phrase, http_headers, http_body)
|
||||
as (integer, unicode, dict, str)
|
||||
|
||||
Perform an HTTPS request
|
||||
"""
|
||||
@@ -1385,8 +1363,7 @@ class ra(rabase.rabase):
|
||||
parser = etree.XMLParser()
|
||||
doc = etree.fromstring(xml_text, parser)
|
||||
result = parse_func(doc)
|
||||
self.debug("%s() xml_text:\n%s\n"
|
||||
"parse_result:\n%s" % (parse_func.__name__, xml_text, result))
|
||||
self.debug("%s() xml_text:\n%s\nparse_result:\n%s" % (parse_func.__name__, xml_text, result))
|
||||
return result
|
||||
|
||||
def check_request_status(self, request_id):
|
||||
@@ -1426,16 +1403,16 @@ class ra(rabase.rabase):
|
||||
self.debug('%s.check_request_status()', self.fullname)
|
||||
|
||||
# Call CMS
|
||||
http_status, http_headers, http_body = \
|
||||
http_status, http_reason_phrase, http_headers, http_body = \
|
||||
self._request('/ca/ee/ca/checkRequest',
|
||||
self.env.ca_port,
|
||||
requestId=request_id,
|
||||
xml='true')
|
||||
|
||||
# Parse and handle errors
|
||||
if http_status != 200:
|
||||
if (http_status != 200):
|
||||
self.raise_certificate_operation_error('check_request_status',
|
||||
detail=http_status)
|
||||
detail=http_reason_phrase)
|
||||
|
||||
parse_result = self.get_parse_result_xml(http_body, parse_check_request_result_xml)
|
||||
request_status = parse_result['request_status']
|
||||
@@ -1446,14 +1423,14 @@ class ra(rabase.rabase):
|
||||
|
||||
# Return command result
|
||||
cmd_result = {}
|
||||
if 'serial_numbers' in parse_result and len(parse_result['serial_numbers']) > 0:
|
||||
if parse_result.has_key('serial_numbers') and len(parse_result['serial_numbers']) > 0:
|
||||
# see module documentation concerning serial numbers and XMLRPC
|
||||
cmd_result['serial_number'] = unicode(parse_result['serial_numbers'][0])
|
||||
|
||||
if 'request_id' in parse_result:
|
||||
if parse_result.has_key('request_id'):
|
||||
cmd_result['request_id'] = parse_result['request_id']
|
||||
|
||||
if 'cert_request_status' in parse_result:
|
||||
if parse_result.has_key('cert_request_status'):
|
||||
cmd_result['cert_request_status'] = parse_result['cert_request_status']
|
||||
|
||||
return cmd_result
|
||||
@@ -1463,10 +1440,10 @@ class ra(rabase.rabase):
|
||||
Retrieve an existing certificate.
|
||||
|
||||
:param serial_number: Certificate serial number. Must be a string value
|
||||
because serial numbers may be of any magnitude and
|
||||
because serial numbers may be of any magnitue and
|
||||
XMLRPC cannot handle integers larger than 64-bit.
|
||||
The string value should be decimal, but may optionally
|
||||
be prefixed with a hex radix prefix if the integral value
|
||||
be prefixed with a hex radix prefix if the integal value
|
||||
is represented as hexadecimal. If no radix prefix is
|
||||
supplied the string will be interpreted as decimal.
|
||||
|
||||
@@ -1511,7 +1488,7 @@ class ra(rabase.rabase):
|
||||
serial_number = int(serial_number, 0)
|
||||
|
||||
# Call CMS
|
||||
http_status, http_headers, http_body = \
|
||||
http_status, http_reason_phrase, http_headers, http_body = \
|
||||
self._sslget('/ca/agent/ca/displayBySerial',
|
||||
self.env.ca_agent_port,
|
||||
serialNumber=str(serial_number),
|
||||
@@ -1519,9 +1496,9 @@ class ra(rabase.rabase):
|
||||
|
||||
|
||||
# Parse and handle errors
|
||||
if http_status != 200:
|
||||
if (http_status != 200):
|
||||
self.raise_certificate_operation_error('get_certificate',
|
||||
detail=http_status)
|
||||
detail=http_reason_phrase)
|
||||
|
||||
parse_result = self.get_parse_result_xml(http_body, parse_display_cert_xml)
|
||||
request_status = parse_result['request_status']
|
||||
@@ -1533,24 +1510,23 @@ class ra(rabase.rabase):
|
||||
# Return command result
|
||||
cmd_result = {}
|
||||
|
||||
if 'certificate' in parse_result:
|
||||
if parse_result.has_key('certificate'):
|
||||
cmd_result['certificate'] = parse_result['certificate']
|
||||
|
||||
if 'serial_number' in parse_result:
|
||||
if parse_result.has_key('serial_number'):
|
||||
# see module documentation concerning serial numbers and XMLRPC
|
||||
cmd_result['serial_number'] = unicode(parse_result['serial_number'])
|
||||
cmd_result['serial_number_hex'] = u'0x%X' % int(cmd_result['serial_number'])
|
||||
|
||||
if 'revocation_reason' in parse_result:
|
||||
if parse_result.has_key('revocation_reason'):
|
||||
cmd_result['revocation_reason'] = parse_result['revocation_reason']
|
||||
|
||||
return cmd_result
|
||||
|
||||
|
||||
def request_certificate(self, csr, profile_id, request_type='pkcs10'):
|
||||
def request_certificate(self, csr, request_type='pkcs10'):
|
||||
"""
|
||||
:param csr: The certificate signing request.
|
||||
:param profile_id: The profile to use for the request.
|
||||
:param request_type: The request type (defaults to ``'pkcs10'``).
|
||||
|
||||
Submit certificate signing request.
|
||||
@@ -1579,17 +1555,17 @@ class ra(rabase.rabase):
|
||||
self.debug('%s.request_certificate()', self.fullname)
|
||||
|
||||
# Call CMS
|
||||
http_status, http_headers, http_body = \
|
||||
http_status, http_reason_phrase, http_headers, http_body = \
|
||||
self._sslget('/ca/eeca/ca/profileSubmitSSLClient',
|
||||
self.env.ca_ee_port,
|
||||
profileId=profile_id,
|
||||
profileId='caIPAserviceCert',
|
||||
cert_request_type=request_type,
|
||||
cert_request=csr,
|
||||
xml='true')
|
||||
# Parse and handle errors
|
||||
if http_status != 200:
|
||||
if (http_status != 200):
|
||||
self.raise_certificate_operation_error('request_certificate',
|
||||
detail=http_status)
|
||||
detail=http_reason_phrase)
|
||||
|
||||
parse_result = self.get_parse_result_xml(http_body, parse_profile_submit_result_xml)
|
||||
# Note different status return, it's not request_status, it's error_code
|
||||
@@ -1608,18 +1584,18 @@ class ra(rabase.rabase):
|
||||
return cmd_result
|
||||
request = parse_result['requests'][0]
|
||||
|
||||
if 'serial_number' in request:
|
||||
if request.has_key('serial_number'):
|
||||
# see module documentation concerning serial numbers and XMLRPC
|
||||
cmd_result['serial_number'] = unicode(request['serial_number'])
|
||||
cmd_result['serial_number_hex'] = u'0x%X' % request['serial_number']
|
||||
|
||||
if 'certificate' in request:
|
||||
if request.has_key('certificate'):
|
||||
cmd_result['certificate'] = request['certificate']
|
||||
|
||||
if 'request_id' in request:
|
||||
if request.has_key('request_id'):
|
||||
cmd_result['request_id'] = request['request_id']
|
||||
|
||||
if 'subject' in request:
|
||||
if request.has_key('subject'):
|
||||
cmd_result['subject'] = request['subject']
|
||||
|
||||
return cmd_result
|
||||
@@ -1628,10 +1604,10 @@ class ra(rabase.rabase):
|
||||
def revoke_certificate(self, serial_number, revocation_reason=0):
|
||||
"""
|
||||
:param serial_number: Certificate serial number. Must be a string value
|
||||
because serial numbers may be of any magnitude and
|
||||
because serial numbers may be of any magnitue and
|
||||
XMLRPC cannot handle integers larger than 64-bit.
|
||||
The string value should be decimal, but may optionally
|
||||
be prefixed with a hex radix prefix if the integral value
|
||||
be prefixed with a hex radix prefix if the integal value
|
||||
is represented as hexadecimal. If no radix prefix is
|
||||
supplied the string will be interpreted as decimal.
|
||||
:param revocation_reason: Integer code of revocation reason.
|
||||
@@ -1658,7 +1634,7 @@ class ra(rabase.rabase):
|
||||
serial_number = int(serial_number, 0)
|
||||
|
||||
# Call CMS
|
||||
http_status, http_headers, http_body = \
|
||||
http_status, http_reason_phrase, http_headers, http_body = \
|
||||
self._sslget('/ca/agent/ca/doRevoke',
|
||||
self.env.ca_agent_port,
|
||||
op='revoke',
|
||||
@@ -1668,9 +1644,9 @@ class ra(rabase.rabase):
|
||||
xml='true')
|
||||
|
||||
# Parse and handle errors
|
||||
if http_status != 200:
|
||||
if (http_status != 200):
|
||||
self.raise_certificate_operation_error('revoke_certificate',
|
||||
detail=http_status)
|
||||
detail=http_reason_phrase)
|
||||
|
||||
parse_result = self.get_parse_result_xml(http_body, parse_revoke_cert_xml)
|
||||
request_status = parse_result['request_status']
|
||||
@@ -1692,10 +1668,10 @@ class ra(rabase.rabase):
|
||||
def take_certificate_off_hold(self, serial_number):
|
||||
"""
|
||||
:param serial_number: Certificate serial number. Must be a string value
|
||||
because serial numbers may be of any magnitude and
|
||||
because serial numbers may be of any magnitue and
|
||||
XMLRPC cannot handle integers larger than 64-bit.
|
||||
The string value should be decimal, but may optionally
|
||||
be prefixed with a hex radix prefix if the integral value
|
||||
be prefixed with a hex radix prefix if the integal value
|
||||
is represented as hexadecimal. If no radix prefix is
|
||||
supplied the string will be interpreted as decimal.
|
||||
|
||||
@@ -1721,16 +1697,16 @@ class ra(rabase.rabase):
|
||||
serial_number = int(serial_number, 0)
|
||||
|
||||
# Call CMS
|
||||
http_status, http_headers, http_body = \
|
||||
http_status, http_reason_phrase, http_headers, http_body = \
|
||||
self._sslget('/ca/agent/ca/doUnrevoke',
|
||||
self.env.ca_agent_port,
|
||||
serialNumber=str(serial_number),
|
||||
xml='true')
|
||||
|
||||
# Parse and handle errors
|
||||
if http_status != 200:
|
||||
if (http_status != 200):
|
||||
self.raise_certificate_operation_error('take_certificate_off_hold',
|
||||
detail=http_status)
|
||||
detail=http_reason_phrase)
|
||||
|
||||
|
||||
parse_result = self.get_parse_result_xml(http_body, parse_unrevoke_cert_xml)
|
||||
@@ -1743,7 +1719,7 @@ class ra(rabase.rabase):
|
||||
# Return command result
|
||||
cmd_result = {}
|
||||
|
||||
if 'error_string' in parse_result:
|
||||
if parse_result.has_key('error_string'):
|
||||
cmd_result['error_string'] = parse_result['error_string']
|
||||
|
||||
if parse_result.get('unrevoked') == 'yes':
|
||||
@@ -1841,25 +1817,23 @@ class ra(rabase.rabase):
|
||||
payload = etree.tostring(doc, pretty_print=False, xml_declaration=True, encoding='UTF-8')
|
||||
self.debug('%s.find(): request: %s', self.fullname, payload)
|
||||
|
||||
url = 'http://%s/ca/rest/certs/search?size=%d' % (
|
||||
ipautil.format_netloc(self.ca_host, 8080),
|
||||
options.get('sizelimit', 100))
|
||||
url = 'http://%s/ca/rest/certs/search?size=%d' % (ipautil.format_netloc(self.ca_host, ipapython.dogtag.configured_constants().UNSECURE_PORT), options.get('sizelimit', 100))
|
||||
|
||||
opener = urllib.request.build_opener()
|
||||
opener = urllib2.build_opener()
|
||||
opener.addheaders = [('Accept-Encoding', 'gzip, deflate'),
|
||||
('User-Agent', 'IPA')]
|
||||
|
||||
req = urllib.request.Request(url=url, data=payload, headers={'Content-Type': 'application/xml'})
|
||||
req = urllib2.Request(url=url, data=payload, headers={'Content-Type': 'application/xml'})
|
||||
try:
|
||||
response = opener.open(req)
|
||||
except urllib.error.HTTPError as e:
|
||||
except urllib2.HTTPError, e:
|
||||
self.debug('HTTP Response code: %d' % e.getcode())
|
||||
if e.getcode() == 501:
|
||||
self.raise_certificate_operation_error('find',
|
||||
detail=_('find not supported on CAs upgraded from 9 to 10'))
|
||||
self.raise_certificate_operation_error('find',
|
||||
detail=e.msg)
|
||||
except urllib.error.URLError as e:
|
||||
except urllib2.URLError, e:
|
||||
self.raise_certificate_operation_error('find',
|
||||
detail=e.reason)
|
||||
|
||||
@@ -1868,7 +1842,7 @@ class ra(rabase.rabase):
|
||||
parser = etree.XMLParser()
|
||||
try:
|
||||
doc = etree.fromstring(data[0], parser)
|
||||
except etree.XMLSyntaxError as e:
|
||||
except etree.XMLSyntaxError, e:
|
||||
self.raise_certificate_operation_error('find',
|
||||
detail=e.msg)
|
||||
|
||||
@@ -1892,257 +1866,4 @@ class ra(rabase.rabase):
|
||||
|
||||
return results
|
||||
|
||||
|
||||
api.register(ra)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
class kra(Backend):
|
||||
"""
|
||||
KRA backend plugin (for Vault)
|
||||
"""
|
||||
|
||||
def __init__(self, api, kra_port=443):
|
||||
|
||||
self.kra_port = kra_port
|
||||
|
||||
super(kra, self).__init__(api)
|
||||
|
||||
@property
|
||||
def kra_host(self):
|
||||
"""
|
||||
:return: host
|
||||
as str
|
||||
|
||||
Select our KRA host.
|
||||
"""
|
||||
ldap2 = self.api.Backend.ldap2
|
||||
if host_has_service(api.env.ca_host, ldap2, "KRA"):
|
||||
return api.env.ca_host
|
||||
if api.env.host != api.env.ca_host:
|
||||
if host_has_service(api.env.host, ldap2, "KRA"):
|
||||
return api.env.host
|
||||
host = select_any_master(ldap2, "KRA")
|
||||
if host:
|
||||
return host
|
||||
else:
|
||||
return api.env.ca_host
|
||||
|
||||
def get_client(self):
|
||||
"""
|
||||
Returns an authenticated KRA client to access KRA services.
|
||||
|
||||
Raises a generic exception if KRA is not enabled.
|
||||
"""
|
||||
|
||||
if not self.api.Command.kra_is_enabled()['result']:
|
||||
# TODO: replace this with a more specific exception
|
||||
raise RuntimeError('KRA service is not enabled')
|
||||
|
||||
crypto = cryptoutil.NSSCryptoProvider(
|
||||
paths.HTTPD_ALIAS_DIR,
|
||||
password_file=paths.ALIAS_PWDFILE_TXT)
|
||||
|
||||
# TODO: obtain KRA host & port from IPA service list or point to KRA load balancer
|
||||
# https://fedorahosted.org/freeipa/ticket/4557
|
||||
connection = PKIConnection(
|
||||
'https',
|
||||
self.kra_host,
|
||||
str(self.kra_port),
|
||||
'kra')
|
||||
|
||||
connection.set_authentication_cert(paths.KRA_AGENT_PEM)
|
||||
|
||||
return KRAClient(connection, crypto)
|
||||
|
||||
api.register(kra)
|
||||
|
||||
|
||||
class RestClient(Backend):
|
||||
"""Simple Dogtag REST client to be subclassed by other backends.
|
||||
|
||||
This class is a context manager. Authenticated calls must be
|
||||
executed in a ``with`` suite::
|
||||
|
||||
class ra_certprofile(RestClient):
|
||||
path = 'profile'
|
||||
...
|
||||
|
||||
api.register(ra_certprofile)
|
||||
|
||||
with api.Backend.ra_certprofile as profile_api:
|
||||
# REST client is now logged in
|
||||
profile_api.create_profile(...)
|
||||
|
||||
"""
|
||||
path = None
|
||||
|
||||
@staticmethod
|
||||
def _parse_dogtag_error(body):
|
||||
try:
|
||||
return pki.PKIException.from_json(json.loads(body))
|
||||
except:
|
||||
return None
|
||||
|
||||
def __init__(self, api):
|
||||
if api.env.in_tree:
|
||||
self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
|
||||
self.pwd_file = self.sec_dir + os.sep + '.pwd'
|
||||
else:
|
||||
self.sec_dir = paths.HTTPD_ALIAS_DIR
|
||||
self.pwd_file = paths.ALIAS_PWDFILE_TXT
|
||||
self.noise_file = self.sec_dir + os.sep + '.noise'
|
||||
self.ipa_key_size = "2048"
|
||||
self.ipa_certificate_nickname = "ipaCert"
|
||||
self.ca_certificate_nickname = "caCert"
|
||||
self._read_password()
|
||||
super(RestClient, self).__init__(api)
|
||||
|
||||
# session cookie
|
||||
self.override_port = None
|
||||
self.cookie = None
|
||||
|
||||
def _read_password(self):
|
||||
try:
|
||||
with open(self.pwd_file) as f:
|
||||
self.password = f.readline().strip()
|
||||
except IOError:
|
||||
self.password = ''
|
||||
|
||||
@cachedproperty
|
||||
def ca_host(self):
|
||||
"""
|
||||
:return: host
|
||||
as str
|
||||
|
||||
Select our CA host.
|
||||
"""
|
||||
ldap2 = self.api.Backend.ldap2
|
||||
if host_has_service(api.env.ca_host, ldap2, "CA"):
|
||||
return api.env.ca_host
|
||||
if api.env.host != api.env.ca_host:
|
||||
if host_has_service(api.env.host, ldap2, "CA"):
|
||||
return api.env.host
|
||||
host = select_any_master(ldap2)
|
||||
if host:
|
||||
return host
|
||||
else:
|
||||
return api.env.ca_host
|
||||
|
||||
def __enter__(self):
|
||||
"""Log into the REST API"""
|
||||
if self.cookie is not None:
|
||||
return
|
||||
status, resp_headers, resp_body = dogtag.https_request(
|
||||
self.ca_host, self.override_port or self.env.ca_agent_port,
|
||||
'/ca/rest/account/login',
|
||||
self.sec_dir, self.password, self.ipa_certificate_nickname,
|
||||
method='GET'
|
||||
)
|
||||
cookies = ipapython.cookie.Cookie.parse(resp_headers.get('set-cookie', ''))
|
||||
if status != 200 or len(cookies) == 0:
|
||||
raise errors.RemoteRetrieveError(reason=_('Failed to authenticate to CA REST API'))
|
||||
self.cookie = str(cookies[0])
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
"""Log out of the REST API"""
|
||||
dogtag.https_request(
|
||||
self.ca_host, self.override_port or self.env.ca_agent_port,
|
||||
'/ca/rest/account/logout',
|
||||
self.sec_dir, self.password, self.ipa_certificate_nickname,
|
||||
method='GET'
|
||||
)
|
||||
self.cookie = None
|
||||
|
||||
def _ssldo(self, method, path, headers=None, body=None):
|
||||
"""
|
||||
:param url: The URL to post to.
|
||||
:param kw: Keyword arguments to encode into POST body.
|
||||
:return: (http_status, http_headers, http_body)
|
||||
as (integer, dict, str)
|
||||
|
||||
Perform an HTTPS request
|
||||
"""
|
||||
if self.cookie is None:
|
||||
raise errors.RemoteRetrieveError(
|
||||
reason=_("REST API is not logged in."))
|
||||
|
||||
headers = headers or {}
|
||||
headers['Cookie'] = self.cookie
|
||||
|
||||
resource = os.path.join('/ca/rest', self.path, path)
|
||||
|
||||
# perform main request
|
||||
status, resp_headers, resp_body = dogtag.https_request(
|
||||
self.ca_host, self.override_port or self.env.ca_agent_port,
|
||||
resource,
|
||||
self.sec_dir, self.password, self.ipa_certificate_nickname,
|
||||
method=method, headers=headers, body=body
|
||||
)
|
||||
if status < 200 or status >= 300:
|
||||
explanation = self._parse_dogtag_error(resp_body) or ''
|
||||
raise errors.RemoteRetrieveError(
|
||||
reason=_('Non-2xx response from CA REST API: %(status)d. %(explanation)s')
|
||||
% {'status': status, 'explanation': explanation}
|
||||
)
|
||||
return (status, resp_headers, resp_body)
|
||||
|
||||
|
||||
class ra_certprofile(RestClient):
|
||||
"""
|
||||
Profile management backend plugin.
|
||||
"""
|
||||
path = 'profiles'
|
||||
|
||||
def create_profile(self, profile_data):
|
||||
"""
|
||||
Import the profile into Dogtag
|
||||
"""
|
||||
self._ssldo('POST', 'raw',
|
||||
headers={
|
||||
'Content-type': 'application/xml',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body=profile_data
|
||||
)
|
||||
|
||||
def read_profile(self, profile_id):
|
||||
"""
|
||||
Read the profile configuration from Dogtag
|
||||
"""
|
||||
status, resp_headers, resp_body = self._ssldo(
|
||||
'GET', profile_id + '/raw')
|
||||
return resp_body
|
||||
|
||||
def update_profile(self, profile_id, profile_data):
|
||||
"""
|
||||
Update the profile configuration in Dogtag
|
||||
"""
|
||||
self._ssldo('PUT', profile_id + '/raw',
|
||||
headers={
|
||||
'Content-type': 'application/xml',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body=profile_data
|
||||
)
|
||||
|
||||
def enable_profile(self, profile_id):
|
||||
"""
|
||||
Enable the profile in Dogtag
|
||||
"""
|
||||
self._ssldo('POST', profile_id + '?action=enable')
|
||||
|
||||
def disable_profile(self, profile_id):
|
||||
"""
|
||||
Enable the profile in Dogtag
|
||||
"""
|
||||
self._ssldo('POST', profile_id + '?action=disable')
|
||||
|
||||
def delete_profile(self, profile_id):
|
||||
"""
|
||||
Delete the profile from Dogtag
|
||||
"""
|
||||
self._ssldo('DELETE', profile_id, headers={'Accept': 'application/json'})
|
||||
|
||||
api.register(ra_certprofile)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
Joining an IPA domain
|
||||
"""
|
||||
|
||||
import six
|
||||
import krbV
|
||||
|
||||
from ipalib import api
|
||||
from ipalib import Command, Str
|
||||
@@ -29,8 +29,14 @@ from ipalib import errors
|
||||
from ipalib import _
|
||||
from ipaserver.install import installutils
|
||||
|
||||
if six.PY3:
|
||||
unicode = str
|
||||
|
||||
def get_realm():
|
||||
"""
|
||||
Returns the default kerberos realm configured for this server.
|
||||
"""
|
||||
krbctx = krbV.default_context()
|
||||
|
||||
return unicode(krbctx.default_realm)
|
||||
|
||||
|
||||
def validate_host(ugettext, cn):
|
||||
@@ -60,7 +66,7 @@ class join(Command):
|
||||
takes_options = (
|
||||
Str('realm',
|
||||
doc=_("The IPA realm"),
|
||||
default_from=lambda: api.env.realm,
|
||||
default_from=lambda: get_realm(),
|
||||
autofill=True,
|
||||
),
|
||||
Str('nshardwareplatform?',
|
||||
|
||||
@@ -30,12 +30,11 @@ Backend plugin for LDAP.
|
||||
import os
|
||||
import pwd
|
||||
|
||||
import krbV
|
||||
import ldap as _ldap
|
||||
|
||||
from ipalib import krb_utils
|
||||
from ipapython.dn import DN
|
||||
from ipapython.ipaldap import (LDAPClient, AUTOBIND_AUTO, AUTOBIND_ENABLED,
|
||||
AUTOBIND_DISABLED)
|
||||
from ipapython.ipaldap import SASL_GSSAPI, IPASimpleLDAPObject, LDAPClient
|
||||
|
||||
|
||||
try:
|
||||
@@ -57,60 +56,35 @@ from ipalib.crud import CrudBackend
|
||||
from ipalib.request import context
|
||||
|
||||
|
||||
class ldap2(CrudBackend, LDAPClient):
|
||||
class ldap2(LDAPClient, CrudBackend):
|
||||
"""
|
||||
LDAP Backend Take 2.
|
||||
"""
|
||||
|
||||
def __init__(self, api, ldap_uri=None):
|
||||
if ldap_uri is None:
|
||||
ldap_uri = api.env.ldap_uri
|
||||
def __init__(self, shared_instance=True, ldap_uri=None, base_dn=None,
|
||||
schema=None):
|
||||
try:
|
||||
ldap_uri = ldap_uri or api.env.ldap_uri
|
||||
except AttributeError:
|
||||
ldap_uri = 'ldap://example.com'
|
||||
|
||||
force_schema_updates = api.env.context in ('installer', 'updates')
|
||||
CrudBackend.__init__(self, shared_instance=shared_instance)
|
||||
LDAPClient.__init__(self, ldap_uri)
|
||||
|
||||
CrudBackend.__init__(self, api)
|
||||
LDAPClient.__init__(self, ldap_uri,
|
||||
force_schema_updates=force_schema_updates)
|
||||
try:
|
||||
if base_dn is not None:
|
||||
self.base_dn = DN(base_dn)
|
||||
else:
|
||||
self.base_dn = DN(api.env.basedn)
|
||||
except AttributeError:
|
||||
self.base_dn = DN()
|
||||
|
||||
self.__time_limit = None
|
||||
self.__size_limit = None
|
||||
|
||||
@property
|
||||
def time_limit(self):
|
||||
if self.__time_limit is None:
|
||||
return float(self.get_ipa_config().single_value.get(
|
||||
'ipasearchtimelimit', 2))
|
||||
return self.__time_limit
|
||||
|
||||
@time_limit.setter
|
||||
def time_limit(self, val):
|
||||
self.__time_limit = float(val)
|
||||
|
||||
@time_limit.deleter
|
||||
def time_limit(self):
|
||||
self.__time_limit = None
|
||||
|
||||
@property
|
||||
def size_limit(self):
|
||||
if self.__size_limit is None:
|
||||
return int(self.get_ipa_config().single_value.get(
|
||||
'ipasearchrecordslimit', 0))
|
||||
return self.__size_limit
|
||||
|
||||
@size_limit.setter
|
||||
def size_limit(self, val):
|
||||
self.__size_limit = int(val)
|
||||
|
||||
@size_limit.deleter
|
||||
def size_limit(self):
|
||||
self.__size_limit = None
|
||||
|
||||
def _connect(self):
|
||||
def _init_connection(self):
|
||||
# Connectible.conn is a proxy to thread-local storage;
|
||||
# do not set it
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
def __del__(self):
|
||||
if self.isconnected():
|
||||
self.disconnect()
|
||||
|
||||
@@ -119,14 +93,13 @@ class ldap2(CrudBackend, LDAPClient):
|
||||
|
||||
def create_connection(self, ccache=None, bind_dn=None, bind_pw='',
|
||||
tls_cacertfile=None, tls_certfile=None, tls_keyfile=None,
|
||||
debug_level=0, autobind=AUTOBIND_AUTO, serverctrls=None,
|
||||
clientctrls=None, time_limit=None, size_limit=None):
|
||||
debug_level=0, autobind=False, serverctrls=None, clientctrls=None):
|
||||
"""
|
||||
Connect to LDAP server.
|
||||
|
||||
Keyword arguments:
|
||||
ldapuri -- the LDAP server to connect to
|
||||
ccache -- Kerberos ccache name
|
||||
ccache -- Kerberos V5 ccache object or name
|
||||
bind_dn -- dn used to bind to the server
|
||||
bind_pw -- password used to bind to the server
|
||||
debug_level -- LDAP debug level option
|
||||
@@ -147,19 +120,15 @@ class ldap2(CrudBackend, LDAPClient):
|
||||
if tls_keyfile is not None:
|
||||
_ldap.set_option(_ldap.OPT_X_TLS_KEYFILE, tls_keyfile)
|
||||
|
||||
if time_limit is not None:
|
||||
self.time_limit = time_limit
|
||||
if size_limit is not None:
|
||||
self.size_limit = size_limit
|
||||
|
||||
if debug_level:
|
||||
_ldap.set_option(_ldap.OPT_DEBUG_LEVEL, debug_level)
|
||||
|
||||
client = LDAPClient(self.ldap_uri,
|
||||
force_schema_updates=self._force_schema_updates)
|
||||
conn = client._conn
|
||||
|
||||
with client.error_handler():
|
||||
with self.error_handler():
|
||||
force_updates = api.env.context in ('installer', 'updates')
|
||||
conn = IPASimpleLDAPObject(
|
||||
self.ldap_uri, force_schema_updates=force_updates)
|
||||
if self.ldap_uri.startswith('ldapi://') and ccache:
|
||||
conn.set_option(_ldap.OPT_HOST_NAME, api.env.host)
|
||||
minssf = conn.get_option(_ldap.OPT_X_SASL_SSF_MIN)
|
||||
maxssf = conn.get_option(_ldap.OPT_X_SASL_SSF_MAX)
|
||||
# Always connect with at least an SSF of 56, confidentiality
|
||||
@@ -169,62 +138,171 @@ class ldap2(CrudBackend, LDAPClient):
|
||||
conn.set_option(_ldap.OPT_X_SASL_SSF_MIN, minssf)
|
||||
if maxssf < minssf:
|
||||
conn.set_option(_ldap.OPT_X_SASL_SSF_MAX, minssf)
|
||||
if ccache is not None:
|
||||
if isinstance(ccache, krbV.CCache):
|
||||
principal = ccache.principal().name
|
||||
# Get a fully qualified CCACHE name (schema+name)
|
||||
# As we do not use the krbV.CCache object later,
|
||||
# we can safely overwrite it
|
||||
ccache = "%(type)s:%(name)s" % dict(type=ccache.type,
|
||||
name=ccache.name)
|
||||
else:
|
||||
principal = krbV.CCache(name=ccache,
|
||||
context=krbV.default_context()).principal().name
|
||||
|
||||
ldapi = self.ldap_uri.startswith('ldapi://')
|
||||
|
||||
if bind_pw:
|
||||
client.simple_bind(bind_dn, bind_pw,
|
||||
server_controls=serverctrls,
|
||||
client_controls=clientctrls)
|
||||
elif autobind != AUTOBIND_DISABLED and os.getegid() == 0 and ldapi:
|
||||
try:
|
||||
pw_name = pwd.getpwuid(os.geteuid()).pw_name
|
||||
client.external_bind(pw_name,
|
||||
server_controls=serverctrls,
|
||||
client_controls=clientctrls)
|
||||
except errors.NotFound:
|
||||
if autobind == AUTOBIND_ENABLED:
|
||||
# autobind was required and failed, raise
|
||||
# exception that it failed
|
||||
raise
|
||||
else:
|
||||
if ldapi:
|
||||
with client.error_handler():
|
||||
conn.set_option(_ldap.OPT_HOST_NAME, self.api.env.host)
|
||||
if ccache is None:
|
||||
os.environ.pop('KRB5CCNAME', None)
|
||||
else:
|
||||
os.environ['KRB5CCNAME'] = ccache
|
||||
|
||||
principal = krb_utils.get_principal(ccache_name=ccache)
|
||||
|
||||
client.gssapi_bind(server_controls=serverctrls,
|
||||
client_controls=clientctrls)
|
||||
setattr(context, 'principal', principal)
|
||||
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,
|
||||
serverctrls=serverctrls,
|
||||
clientctrls=clientctrls)
|
||||
else:
|
||||
conn.simple_bind_s(bind_dn, bind_pw,
|
||||
serverctrls=serverctrls,
|
||||
clientctrls=clientctrls)
|
||||
|
||||
return conn
|
||||
|
||||
def destroy_connection(self):
|
||||
"""Disconnect from LDAP server."""
|
||||
try:
|
||||
if self.conn is not None:
|
||||
self.unbind()
|
||||
except errors.PublicError:
|
||||
self.conn.unbind_s()
|
||||
except _ldap.LDAPError:
|
||||
# ignore when trying to unbind multiple times
|
||||
pass
|
||||
|
||||
del self.time_limit
|
||||
del self.size_limit
|
||||
|
||||
def find_entries(self, filter=None, attrs_list=None, base_dn=None,
|
||||
scope=_ldap.SCOPE_SUBTREE, time_limit=None,
|
||||
size_limit=None, search_refs=False, paged_search=False):
|
||||
|
||||
def _get_limits():
|
||||
"""Get configured global limits, caching them for more calls"""
|
||||
if not _lims:
|
||||
config = self.get_ipa_config()
|
||||
_lims['time'] = config.get('ipasearchtimelimit', [None])[0]
|
||||
_lims['size'] = config.get('ipasearchrecordslimit', [None])[0]
|
||||
return _lims
|
||||
_lims = {}
|
||||
|
||||
if time_limit is None:
|
||||
time_limit = _get_limits()['time']
|
||||
if size_limit is None:
|
||||
size_limit = _get_limits()['size']
|
||||
|
||||
has_memberindirect = False
|
||||
has_memberofindirect = False
|
||||
if attrs_list:
|
||||
new_attrs_list = []
|
||||
for attr_name in attrs_list:
|
||||
if attr_name == 'memberindirect':
|
||||
has_memberindirect = True
|
||||
elif attr_name == 'memberofindirect':
|
||||
has_memberofindirect = True
|
||||
else:
|
||||
new_attrs_list.append(attr_name)
|
||||
attrs_list = new_attrs_list
|
||||
|
||||
res, truncated = super(ldap2, self).find_entries(
|
||||
filter=filter, attrs_list=attrs_list, base_dn=base_dn, scope=scope,
|
||||
time_limit=time_limit, size_limit=size_limit,
|
||||
search_refs=search_refs, paged_search=paged_search)
|
||||
|
||||
if has_memberindirect or has_memberofindirect:
|
||||
|
||||
# For the memberof searches, we want to apply the global limit
|
||||
# if it's larger than the requested one, so decreasing limits on
|
||||
# the individual query only affects the query itself.
|
||||
# See https://fedorahosted.org/freeipa/ticket/4398
|
||||
def _max_with_none(a, b):
|
||||
"""Maximum of a and b, treating None as infinity"""
|
||||
if a is None or b is None:
|
||||
return None
|
||||
else:
|
||||
return max(a, b)
|
||||
time_limit = _max_with_none(time_limit, _get_limits()['time'])
|
||||
size_limit = _max_with_none(size_limit, _get_limits()['size'])
|
||||
|
||||
for entry in res:
|
||||
if has_memberindirect:
|
||||
self._process_memberindirect(
|
||||
entry, time_limit=time_limit, size_limit=size_limit)
|
||||
if has_memberofindirect:
|
||||
self._process_memberofindirect(
|
||||
entry, time_limit=time_limit, size_limit=size_limit)
|
||||
|
||||
return (res, truncated)
|
||||
|
||||
def _process_memberindirect(self, group_entry, time_limit=None,
|
||||
size_limit=None):
|
||||
filter = self.make_filter({'memberof': group_entry.dn})
|
||||
try:
|
||||
result, truncated = self.find_entries(
|
||||
base_dn=self.api.env.basedn,
|
||||
filter=filter,
|
||||
attrs_list=['member'],
|
||||
time_limit=time_limit,
|
||||
size_limit=size_limit,
|
||||
paged_search=True)
|
||||
if truncated:
|
||||
raise errors.LimitsExceeded()
|
||||
except errors.NotFound:
|
||||
result = []
|
||||
|
||||
indirect = set()
|
||||
for entry in result:
|
||||
indirect.update(entry.get('member', []))
|
||||
indirect.difference_update(group_entry.get('member', []))
|
||||
|
||||
if indirect:
|
||||
group_entry['memberindirect'] = list(indirect)
|
||||
|
||||
def _process_memberofindirect(self, entry, time_limit=None,
|
||||
size_limit=None):
|
||||
dn = entry.dn
|
||||
filter = self.make_filter(
|
||||
{'member': dn, 'memberuser': dn, 'memberhost': dn})
|
||||
try:
|
||||
result, truncated = self.find_entries(
|
||||
base_dn=self.api.env.basedn,
|
||||
filter=filter,
|
||||
attrs_list=[''],
|
||||
time_limit=time_limit,
|
||||
size_limit=size_limit)
|
||||
if truncated:
|
||||
raise errors.LimitsExceeded()
|
||||
except errors.NotFound:
|
||||
result = []
|
||||
|
||||
direct = set()
|
||||
indirect = set(entry.get('memberof', []))
|
||||
for group_entry in result:
|
||||
dn = group_entry.dn
|
||||
if dn in indirect:
|
||||
indirect.remove(dn)
|
||||
direct.add(dn)
|
||||
|
||||
entry['memberof'] = list(direct)
|
||||
if indirect:
|
||||
entry['memberofindirect'] = list(indirect)
|
||||
|
||||
config_defaults = {'ipasearchtimelimit': [2], 'ipasearchrecordslimit': [0]}
|
||||
def get_ipa_config(self, attrs_list=None):
|
||||
"""Returns the IPA configuration entry (dn, entry_attrs)."""
|
||||
|
||||
dn = self.api.Object.config.get_dn()
|
||||
dn = api.Object.config.get_dn()
|
||||
assert isinstance(dn, DN)
|
||||
|
||||
try:
|
||||
config_entry = getattr(context, 'config_entry')
|
||||
if config_entry.conn.conn is self.conn:
|
||||
if config_entry.conn is self.conn:
|
||||
return config_entry
|
||||
except AttributeError:
|
||||
# Not in our context yet
|
||||
@@ -239,7 +317,9 @@ class ldap2(CrudBackend, LDAPClient):
|
||||
config_entry = entries[0]
|
||||
except errors.NotFound:
|
||||
config_entry = self.make_entry(dn)
|
||||
|
||||
for a in self.config_defaults:
|
||||
if a not in config_entry:
|
||||
config_entry[a] = self.config_defaults[a]
|
||||
context.config_entry = config_entry
|
||||
return config_entry
|
||||
|
||||
@@ -254,14 +334,12 @@ class ldap2(CrudBackend, LDAPClient):
|
||||
"""
|
||||
|
||||
upg_dn = DN(('cn', 'UPG Definition'), ('cn', 'Definitions'), ('cn', 'Managed Entries'),
|
||||
('cn', 'etc'), self.api.env.basedn)
|
||||
('cn', 'etc'), api.env.basedn)
|
||||
|
||||
try:
|
||||
with self.error_handler():
|
||||
upg_entries = self.conn.search_s(str(upg_dn), _ldap.SCOPE_BASE,
|
||||
attrlist=['*'])
|
||||
upg_entries = self._convert_result(upg_entries)
|
||||
except errors.NotFound:
|
||||
upg_entries = self.conn.search_s(upg_dn, _ldap.SCOPE_BASE,
|
||||
attrlist=['*'])
|
||||
except _ldap.NO_SUCH_OBJECT:
|
||||
upg_entries = None
|
||||
if not upg_entries or 'originfilter' not in upg_entries[0]:
|
||||
raise errors.ACIError(info=_(
|
||||
@@ -281,7 +359,7 @@ class ldap2(CrudBackend, LDAPClient):
|
||||
|
||||
principal = getattr(context, 'principal')
|
||||
entry = self.find_entry_by_attr("krbprincipalname", principal,
|
||||
"krbPrincipalAux", base_dn=self.api.env.basedn)
|
||||
"krbPrincipalAux", base_dn=api.env.basedn)
|
||||
sctrl = [GetEffectiveRightsControl(True, "dn: " + str(entry.dn))]
|
||||
self.conn.set_option(_ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
try:
|
||||
@@ -370,15 +448,14 @@ class ldap2(CrudBackend, LDAPClient):
|
||||
pw = old_pass
|
||||
if (otp):
|
||||
pw = old_pass+otp
|
||||
|
||||
with LDAPClient(self.ldap_uri, force_schema_updates=False) as conn:
|
||||
conn.simple_bind(dn, pw)
|
||||
conn.unbind()
|
||||
with self.error_handler():
|
||||
conn = IPASimpleLDAPObject(
|
||||
self.ldap_uri, force_schema_updates=False)
|
||||
conn.simple_bind_s(dn, pw)
|
||||
conn.unbind_s()
|
||||
|
||||
with self.error_handler():
|
||||
old_pass = self.encode(old_pass)
|
||||
new_pass = self.encode(new_pass)
|
||||
self.conn.passwd_s(str(dn), old_pass, new_pass)
|
||||
self.conn.passwd_s(dn, old_pass, new_pass)
|
||||
|
||||
def add_entry_to_group(self, dn, group_dn, member_attr='member', allow_same=False):
|
||||
"""
|
||||
@@ -410,9 +487,7 @@ class ldap2(CrudBackend, LDAPClient):
|
||||
# update group entry
|
||||
try:
|
||||
with self.error_handler():
|
||||
modlist = [(a, self.encode(b), self.encode(c))
|
||||
for a, b, c in modlist]
|
||||
self.conn.modify_s(str(group_dn), modlist)
|
||||
self.conn.modify_s(group_dn, modlist)
|
||||
except errors.DatabaseError:
|
||||
raise errors.AlreadyGroupMember()
|
||||
|
||||
@@ -432,9 +507,7 @@ class ldap2(CrudBackend, LDAPClient):
|
||||
# update group entry
|
||||
try:
|
||||
with self.error_handler():
|
||||
modlist = [(a, self.encode(b), self.encode(c))
|
||||
for a, b, c in modlist]
|
||||
self.conn.modify_s(str(group_dn), modlist)
|
||||
self.conn.modify_s(group_dn, modlist)
|
||||
except errors.MidairCollision:
|
||||
raise errors.NotGroupMember()
|
||||
|
||||
@@ -488,7 +561,7 @@ class ldap2(CrudBackend, LDAPClient):
|
||||
(_ldap.MOD_REPLACE, 'krblastpwdchange', None)]
|
||||
|
||||
with self.error_handler():
|
||||
self.conn.modify_s(str(dn), mod)
|
||||
self.conn.modify_s(dn, mod)
|
||||
|
||||
# CrudBackend methods
|
||||
|
||||
|
||||
Binary file not shown.
@@ -41,14 +41,14 @@ class rabase(Backend):
|
||||
"""
|
||||
Request Authority backend plugin.
|
||||
"""
|
||||
def __init__(self, api):
|
||||
def __init__(self):
|
||||
if api.env.in_tree:
|
||||
self.sec_dir = api.env.dot_ipa + os.sep + 'alias'
|
||||
self.pwd_file = self.sec_dir + os.sep + '.pwd'
|
||||
else:
|
||||
self.sec_dir = paths.HTTPD_ALIAS_DIR
|
||||
self.pwd_file = paths.ALIAS_PWDFILE_TXT
|
||||
super(rabase, self).__init__(api)
|
||||
super(rabase, self).__init__()
|
||||
|
||||
|
||||
def check_request_status(self, request_id):
|
||||
@@ -67,12 +67,11 @@ class rabase(Backend):
|
||||
"""
|
||||
raise errors.NotImplementedError(name='%s.get_certificate' % self.name)
|
||||
|
||||
def request_certificate(self, csr, profile_id, request_type='pkcs10'):
|
||||
def request_certificate(self, csr, request_type='pkcs10'):
|
||||
"""
|
||||
Submit certificate signing request.
|
||||
|
||||
:param csr: The certificate signing request.
|
||||
:param profile_id: Profile to use for this request.
|
||||
:param request_type: The request type (defaults to ``'pkcs10'``).
|
||||
"""
|
||||
raise errors.NotImplementedError(name='%s.request_certificate' % self.name)
|
||||
|
||||
Reference in New Issue
Block a user