From 2d5f9a56793d6e20d0f69a49ff43fce41ccb7910 Mon Sep 17 00:00:00 2001
From: Endi Sukma Dewata <edewata@redhat.com>
Date: Thu, 21 Feb 2013 19:47:35 -0500
Subject: [PATCH] Added security domain info validation.

The installer script has been modified to validate security domain
info in both interactive and silent installation.

A basic Python API has been added to access the REST interface.

Ticket #473
---
 CMakeLists.txt                           | 10 +++++
 base/common/CMakeLists.txt               |  8 ++++
 base/common/python/pki/account.py        | 31 ++++++++++++++
 base/common/python/pki/client.py         | 53 +++++++++++++++++++++++
 base/common/python/pki/system.py         | 38 +++++++++++++++++
 base/deploy/CMakeLists.txt               | 16 -------
 base/deploy/src/pkispawn                 | 73 +++++++++++++++++++++++---------
 base/deploy/src/scriptlets/pkilogging.py | 40 +++++++----------
 base/deploy/src/scriptlets/pkiparser.py  | 23 ++++++++++
 specs/pki-core.spec                      | 35 +++++++++------
 10 files changed, 254 insertions(+), 73 deletions(-)
 create mode 100644 base/common/python/pki/__init__.py
 create mode 100644 base/common/python/pki/account.py
 create mode 100644 base/common/python/pki/client.py
 create mode 100644 base/common/python/pki/system.py

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d2a08f36a572a6f15a7270ea222c3cd460ea513d..6462ca379f13093553168161c7a96aa462145ec6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -119,6 +119,16 @@ endif ()
 set(CMAKE_THREAD_PREFER_PTHREADS ON)
 find_package(Threads)
 
+find_package(PythonInterp REQUIRED)
+execute_process(
+    COMMAND
+        ${PYTHON_EXECUTABLE} -c
+        "from distutils.sysconfig import get_python_lib; print get_python_lib()"
+    OUTPUT_VARIABLE
+        PYTHON_SITE_PACKAGES
+    OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+
 # config.h checks
 include(ConfigureChecks.cmake)
 configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h)
diff --git a/base/common/CMakeLists.txt b/base/common/CMakeLists.txt
index 73877c82ca51a9dc8fd5b1fe058ce64356de54a0..e4813398687d7dccbd21ed33916930d4a2cecfe0 100644
--- a/base/common/CMakeLists.txt
+++ b/base/common/CMakeLists.txt
@@ -1,5 +1,13 @@
 project(common NONE)
 
+# install Python libraries
+install(
+    DIRECTORY
+        python/
+    DESTINATION
+        ${PYTHON_SITE_PACKAGES}
+)
+
 # install systemd scripts
 install(
     FILES
diff --git a/base/common/python/pki/__init__.py b/base/common/python/pki/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/base/common/python/pki/account.py b/base/common/python/pki/account.py
new file mode 100644
index 0000000000000000000000000000000000000000..84f2d0ef03936f921afc15c687b3b9779e026f3d
--- /dev/null
+++ b/base/common/python/pki/account.py
@@ -0,0 +1,31 @@
+#!/usr/bin/python
+# Authors:
+#     Endi S. Dewata <edewata@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2013 Red Hat, Inc.
+# All rights reserved.
+#
+
+class AccountClient:
+
+    def __init__(self, connection):
+        self.connection = connection
+
+    def login(self):
+        self.connection.get('account/login')
+
+    def logout(self):
+        self.connection.get('account/logout')
diff --git a/base/common/python/pki/client.py b/base/common/python/pki/client.py
new file mode 100644
index 0000000000000000000000000000000000000000..7635fe8797b422f5fbc36b244435a06e74b2852b
--- /dev/null
+++ b/base/common/python/pki/client.py
@@ -0,0 +1,53 @@
+#!/usr/bin/python
+# Authors:
+#     Endi S. Dewata <edewata@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2013 Red Hat, Inc.
+# All rights reserved.
+#
+
+import requests
+
+class PKIConnection:
+
+    def __init__(self,
+            protocol='http',
+            hostname='localhost',
+            port=80,
+            subsystem='ca'):
+
+        self.protocol = protocol
+        self.hostname = hostname
+        self.port = port
+        self.subsystem = subsystem
+
+        self.serverURI = self.protocol + '://' +\
+            self.hostname + ':' + self.port + '/' + \
+            self.subsystem
+
+        self.session = requests.Session()
+        self.session.headers.update({'Accept': 'application/json'})
+
+    def authenticate(self, username=None, password=None):
+        if username is not None and password is not None:
+            self.session.auth = (username, password)
+
+    def get(self, path):
+        r = self.session.get(
+            self.serverURI + '/rest/' + path,
+            verify=False)
+        r.raise_for_status()
+        return r
\ No newline at end of file
diff --git a/base/common/python/pki/system.py b/base/common/python/pki/system.py
new file mode 100644
index 0000000000000000000000000000000000000000..61ffbb9ff6c7a08a2b2d2a0a590e55cf37a7c8ef
--- /dev/null
+++ b/base/common/python/pki/system.py
@@ -0,0 +1,38 @@
+#!/usr/bin/python
+# Authors:
+#     Endi S. Dewata <edewata@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2013 Red Hat, Inc.
+# All rights reserved.
+#
+
+class SecurityDomainInfo:
+
+    def __init__(self):
+        pass
+
+class SecurityDomainClient:
+
+    def __init__(self, connection):
+        self.connection = connection
+
+    def getSecurityDomainInfo(self):
+        r = self.connection.get('securityDomain/domainInfo')
+
+        info = SecurityDomainInfo()
+        info.name = r.json['DomainInfo']['@id']
+
+        return info
diff --git a/base/deploy/CMakeLists.txt b/base/deploy/CMakeLists.txt
index 94abccdbcee8da8327108264414ef919a5bba7a9..ff9d64545e650fd722273e4e7c5ab1355e9bf18b 100644
--- a/base/deploy/CMakeLists.txt
+++ b/base/deploy/CMakeLists.txt
@@ -105,15 +105,6 @@ install(
         ${SYSCONF_INSTALL_DIR}/pki/
 )
 
-find_package(PythonInterp REQUIRED)
-execute_process(
-    COMMAND
-        ${PYTHON_EXECUTABLE} -c
-        "from distutils.sysconfig import get_python_lib; print get_python_lib()"
-    OUTPUT_VARIABLE
-        PYTHON_SITE_PACKAGES
-    OUTPUT_STRIP_TRAILING_WHITESPACE
-)
 install(
     FILES
         src/scriptlets/configuration.jy
@@ -147,13 +138,6 @@ install(
         "execute_process(
             COMMAND
                 ${CMAKE_COMMAND} -E touch
-                \"\$ENV{DESTDIR}${PYTHON_SITE_PACKAGES}/pki/__init__.py\")"
-)
-install(
-    CODE
-        "execute_process(
-            COMMAND
-                ${CMAKE_COMMAND} -E touch
                 \"\$ENV{DESTDIR}${PYTHON_SITE_PACKAGES}/pki/deployment/__init__.py\")"
 )
 
diff --git a/base/deploy/src/pkispawn b/base/deploy/src/pkispawn
index 509f08e4e26e0b1e638bb440d9c3ec1ec4839dfe..a146603874651ca792be6d76bc95fa2c733e31b2 100755
--- a/base/deploy/src/pkispawn
+++ b/base/deploy/src/pkispawn
@@ -30,10 +30,12 @@ try:
     import ldap
     import logging
     import os
+    import requests
     import socket
     import struct
     import subprocess
     import time
+    import urllib2
     from time import strftime as date
     from pki.deployment import pkiconfig as config
     from pki.deployment.pkiparser import PKIConfigParser
@@ -220,15 +222,33 @@ def main(argv):
             print
 
             print "Security Domain:"
-            parser.read_text('Name', config.pki_subsystem, 'pki_security_domain_name')
-
-            if config.pki_subsystem != "CA":
-                parser.read_text('Hostname', config.pki_subsystem, 'pki_security_domain_hostname')
-                parser.read_text('Secure HTTP port', config.pki_subsystem, 'pki_security_domain_https_port')
-                parser.read_text('Username', config.pki_subsystem, 'pki_security_domain_user')
-                parser.read_password(
-                    'Password', config.pki_subsystem, 'pki_security_domain_password',
-                    verifyMessage='Verify password')
+
+            if config.pki_subsystem == "CA":
+                parser.read_text('Name', config.pki_subsystem, 'pki_security_domain_name')
+
+            else:
+                while True:
+                    parser.read_text('Hostname', config.pki_subsystem, 'pki_security_domain_hostname')
+                    parser.read_text('Secure HTTP port', config.pki_subsystem, 'pki_security_domain_https_port')
+
+                    try:
+                        parser.sd_connect()
+                        info = parser.sd_get_info()
+                        parser.print_text('Name: ' + info.name)
+                        parser.set_property(config.pki_subsystem, 'pki_security_domain_name', info.name)
+                        break
+                    except requests.exceptions.ConnectionError as e:
+                        parser.print_text('ERROR: ' + str(e))
+
+                while True:
+                    parser.read_text('Username', config.pki_subsystem, 'pki_security_domain_user')
+                    parser.read_password('Password', config.pki_subsystem, 'pki_security_domain_password')
+
+                    try:
+                        parser.sd_authenticate()
+                        break
+                    except requests.exceptions.HTTPError as e:
+                        parser.print_text('ERROR: ' + str(e))
 
             print
 
@@ -316,23 +336,38 @@ def main(argv):
     config.pki_log.debug(pkilogging.format(config.pki_master_dict),
                          extra=config.PKI_INDENTATION_LEVEL_0)
 
-    if not interactive:
+    if not interactive and\
+        not config.str2bool(config.pki_master_dict['pki_skip_configuration']):
         try:
-            if not config.str2bool(config.pki_master_dict['pki_skip_configuration']):
-                parser.ds_connect()
-                parser.ds_bind()
+            parser.ds_connect()
+            parser.ds_bind()
 
-                if parser.ds_base_dn_exists() and\
-                    not config.str2bool(config.pki_master_dict['pki_ds_remove_data']):
-                    print 'ERROR:  Base DN already exists.'
-                    sys.exit(1)
+            if parser.ds_base_dn_exists() and\
+                not config.str2bool(config.pki_master_dict['pki_ds_remove_data']):
+                print 'ERROR:  Base DN already exists.'
+                sys.exit(1)
 
-                parser.ds_close()
+            parser.ds_close()
 
         except ldap.LDAPError as e:
-            print 'ERROR:  ' + e.message['desc']
+            print 'ERROR:  Unable to access directory server: ' + e.message['desc']
             sys.exit(1)
 
+        if config.pki_subsystem != "CA":
+            try:
+                parser.sd_connect()
+                info = parser.sd_get_info()
+                parser.set_property(config.pki_subsystem, 'pki_security_domain_name', info.name)
+                parser.sd_authenticate()
+
+            except requests.exceptions.ConnectionError as e:
+                print('ERROR:  Unable to access security domain: ' + str(e))
+                sys.exit(1)
+
+            except requests.exceptions.HTTPError as e:
+                print('ERROR:  Unable to access security domain: ' + str(e))
+                sys.exit(1)
+
     print "Installing " + config.pki_subsystem + " into " + config.pki_master_dict['pki_instance_path'] + "."
 
     # Process the various "scriptlets" to create the specified PKI subsystem.
diff --git a/base/deploy/src/scriptlets/pkilogging.py b/base/deploy/src/scriptlets/pkilogging.py
index 3c146a12cbb6035d83891f3d43ae4d01ab77da77..31961614551c4ab25da1d12d4761f93df43ad476 100644
--- a/base/deploy/src/scriptlets/pkilogging.py
+++ b/base/deploy/src/scriptlets/pkilogging.py
@@ -43,42 +43,34 @@ def format(dict):
     return pp.pformat(new_dict)
 
 # PKI Deployment Logging Functions
-def enable_pki_logger(log_dir, log_name, log_level, console_log_level, logger):
+def enable_pki_logger(log_dir, log_name, log_level, console_log_level, name):
     if not os.path.isdir(log_dir):
         try:
             os.makedirs(log_dir)
         except OSError:
             return OSError
 
-    # Establish 'file' logger using 'basicConfig()'
-    logging.LoggerAdapter(logging.getLogger(''), {'indent' : ''})
-    logging.basicConfig(level=log_level,
-                        format='%(asctime)s %(name)-12s ' +\
-                               '%(levelname)-8s ' +\
-                               '%(indent)s%(message)s',
-                        datefmt='%Y-%m-%d %H:%M:%S',
-                        filename=log_dir + "/" + log_name,
-                        filemode='w')
+    # Configure logger
+    logger = logging.getLogger(name)
+    logger.setLevel(log_level)
 
-    # Establish 'console' logger
+    # Configure console handler
     console = logging.StreamHandler()
-    logging.LoggerAdapter(console, {'indent' : ''})
     console.setLevel(console_log_level)
     console_format = logging.Formatter('%(name)-12s: ' +\
                                        '%(levelname)-8s ' +\
                                        '%(indent)s%(message)s')
     console.setFormatter(console_format)
-    logging.getLogger('').addHandler(console)
+    logger.addHandler(console)
 
-    # Establish 'file' logger
-#   file = logging.FileHandler(log_dir + "/" + log_name, 'w')
-#   logging.LoggerAdapter(file, {'indent' : ''})
-#   file.setLevel(log_level)
-#   file_format = logging.Formatter('%(asctime)s %(name)-12s: ' +\
-#                                   '%(levelname)-8s ' +\
-#                                   '%(indent)s%(message)s',
-#                                   '%Y-%m-%d %H:%M:%S')
-#   file.setFormatter(file_format)
-#   logging.getLogger('').addHandler(file)
+    # Configure file handler
+    file = logging.FileHandler(log_dir + "/" + log_name, 'w')
+    file.setLevel(log_level)
+    file_format = logging.Formatter('%(asctime)s %(name)-12s: ' +\
+                                    '%(levelname)-8s ' +\
+                                    '%(indent)s%(message)s',
+                                    '%Y-%m-%d %H:%M:%S')
+    file.setFormatter(file_format)
+    logger.addHandler(file)
 
-    return logging.getLogger(logger)
+    return logger
diff --git a/base/deploy/src/scriptlets/pkiparser.py b/base/deploy/src/scriptlets/pkiparser.py
index 19f7210e8f01393ad32dadc63a7164e5541fa9e6..51ff56a742517b7c3d41d9575b2f96be8367c803 100644
--- a/base/deploy/src/scriptlets/pkiparser.py
+++ b/base/deploy/src/scriptlets/pkiparser.py
@@ -38,6 +38,9 @@ import pkilogging
 import pkiconfig as config
 import pkimessages as log
 
+import pki.account
+import pki.client
+import pki.system
 
 class PKIConfigParser:
 
@@ -385,6 +388,26 @@ class PKIConfigParser:
     def ds_close(self):
         self.ds_connection.unbind_s()
 
+    def sd_connect(self):
+        self.sd_connection = pki.client.PKIConnection(
+            protocol='https',
+            hostname=config.pki_master_dict['pki_security_domain_hostname'],
+            port=config.pki_master_dict['pki_security_domain_https_port'],
+            subsystem='ca')
+
+    def sd_get_info(self):
+        sd = pki.system.SecurityDomainClient(self.sd_connection)
+        return sd.getSecurityDomainInfo()
+
+    def sd_authenticate(self):
+        self.sd_connection.authenticate(
+            config.pki_master_dict['pki_security_domain_user'],
+            config.pki_master_dict['pki_security_domain_password'])
+
+        account = pki.account.AccountClient(self.sd_connection)
+        account.login()
+        account.logout()
+
     def compose_pki_master_dictionary(self):
         "Create a single master PKI dictionary from the sectional dictionaries"
         try:
diff --git a/specs/pki-core.spec b/specs/pki-core.spec
index d661f4131a2ed851772234de4ea25581a20421bd..1ddc56347aff907be3311aef03a758b447a4c738 100644
--- a/specs/pki-core.spec
+++ b/specs/pki-core.spec
@@ -5,7 +5,7 @@ distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
 
 Name:             pki-core
 Version:          10.0.1
-Release:          1%{?dist}
+Release:          2%{?dist}
 Summary:          Certificate System - PKI Core Components
 URL:              http://pki.fedoraproject.org/
 License:          GPLv2
@@ -181,22 +181,23 @@ Requires:         apache-commons-logging
 Requires:         java >= 1:1.6.0
 Requires:         javassist
 Requires:         jettison
+Requires:         jpackage-utils >= 0:1.7.5-10
+%if 0%{?rhel} || 0%{?fedora} >= 19
+Requires:         jss >= 4.2.6-28
+%else
+Requires:         jss >= 4.2.6-24
+%endif
 Requires:         ldapjdk
+Requires:         python-requests
+%if  0%{?rhel}
+Requires:         resteasy-base
+%else
+Requires:         resteasy >= 2.3.2-1
+%endif
 Requires:         xalan-j2
 Requires:         xerces-j2
 Requires:         xml-commons-apis
 Requires:         xml-commons-resolver
-Requires:         jpackage-utils >= 0:1.7.5-10
-%if 0%{?rhel} || 0%{?fedora} >= 19
-Requires:         jss >= 4.2.6-28
-%else
-Requires:         jss >= 4.2.6-24
-%endif
-%if  0%{?rhel}
-Requires:         resteasy-base
-%else
-Requires:         resteasy >= 2.3.2-1
-%endif
 
 %description -n   pki-base
 The PKI Framework contains the common and client libraries and utilities.
@@ -796,6 +797,10 @@ fi
 %{_javadir}/pki/pki-cmsutil.jar
 %{_javadir}/pki/pki-nsutil.jar
 %{_javadir}/pki/pki-certsrv.jar
+%dir %{python_sitelib}/pki
+%{python_sitelib}/pki/*.py
+%{python_sitelib}/pki/*.pyc
+%{python_sitelib}/pki/*.pyo
 
 %files -n pki-tools
 %defattr(-,root,root,-)
@@ -840,8 +845,6 @@ fi
 %{_sbindir}/pkispawn
 %{_sbindir}/pkidestroy
 #%{_bindir}/pki-setup-proxy
-%dir %{python_sitelib}/pki
-%{python_sitelib}/pki/_*
 %{python_sitelib}/pki/deployment/
 %dir %{_datadir}/pki/deployment
 %{_datadir}/pki/deployment/config/
@@ -985,6 +988,10 @@ fi
 
 
 %changelog
+* Tue Jan 15 2013 Ade Lee <alee@redhat.com> 10.0.1-2
+- Added dependency on python-requests.
+- Reorganized Python module packaging.
+
 * Tue Jan 15 2013 Ade Lee <alee@redhat.com> 10.0.1-1
 - TRAC Ticket 214 - Missing error description for duplicate user
 - TRAC Ticket 213 - Add nonces for cert revocation
-- 
1.7.12.1

