This is an automated email from the ASF dual-hosted git repository.

amagyar pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new a8c5ce2  [AMBARI-23311] Use Ambari CLI to specify which services 
should be setup for SSO integration (#765)
a8c5ce2 is described below

commit a8c5ce20444d4cfc88419b84b328ef2f298e3aed
Author: smolnar82 <[email protected]>
AuthorDate: Wed Mar 28 09:47:25 2018 +0200

    [AMBARI-23311] Use Ambari CLI to specify which services should be setup for 
SSO integration (#765)
    
    * AMBARI-23311. Use Ambari CLI to specify which services should be setup 
for SSO integration
    
    * AMBARI-23311. Added Ambari username/pw CLI options for the setup-sso tool
    
    * AMBARI-23311. Saving/fetching 'ambari.sso.manage_services' and add 
'Ambari' as a static service to be asked
---
 ambari-server/src/main/python/ambari-server.py     |   3 +
 .../src/main/python/ambari_server/serverUtils.py   |  90 +++++++++-
 .../src/main/python/ambari_server/setupSecurity.py |  31 +---
 .../src/main/python/ambari_server/setupSso.py      | 197 ++++++++++++++++++---
 ambari-server/src/test/python/TestAmbariServer.py  |  14 +-
 ambari-server/src/test/python/TestServerUtils.py   |  28 ++-
 ambari-server/src/test/python/TestSetupSso.py      | 185 ++++++++++++++++++-
 7 files changed, 482 insertions(+), 66 deletions(-)

diff --git a/ambari-server/src/main/python/ambari-server.py 
b/ambari-server/src/main/python/ambari-server.py
index 1715387..2383086 100755
--- a/ambari-server/src/main/python/ambari-server.py
+++ b/ambari-server/src/main/python/ambari-server.py
@@ -574,10 +574,13 @@ def init_ldap_setup_parser_options(parser):
 @OsFamilyFuncImpl(OsFamilyImpl.DEFAULT)
 def init_setup_sso_options(parser):
   parser.add_option('--sso-enabled', default=None, help="Indicates whether to 
enable/disable SSO", dest="sso_enabled")
+  parser.add_option('--sso-enabled-services', default=None, help="A comma 
separated list of services that are expected to be configured for SSO (you are 
allowed to use '*' to indicate ALL services)", dest='sso_enabled_services')
   parser.add_option('--sso-provider-url', default=None, help="The URL of SSO 
provider; this must be provided when --sso-enabled is set to 'true'", 
dest="sso_provider_url")
   parser.add_option('--sso-public-cert-file', default=None, help="The path 
where the public certificate PEM is located; this must be provided when 
--sso-enabled is set to 'true'", dest="sso_public_cert_file")
   parser.add_option('--sso-jwt-cookie-name', default=None, help="The name of 
the JWT cookie", dest="sso_jwt_cookie_name")
   parser.add_option('--sso-jwt-audience-list', default=None, help="A comma 
separated list of JWT audience(s)", dest="sso_jwt_audience_list")
+  parser.add_option('--ambari-admin-username', default=None, help="Ambari 
Admin username for LDAP setup", dest="ambari_admin_username")
+  parser.add_option('--ambari-admin-password', default=None, help="Ambari 
Admin password for LDAP setup", dest="ambari_admin_password")
 
 @OsFamilyFuncImpl(OsFamilyImpl.DEFAULT)
 def init_pam_setup_parser_options(parser):
diff --git a/ambari-server/src/main/python/ambari_server/serverUtils.py 
b/ambari-server/src/main/python/ambari_server/serverUtils.py
index 4621646..7a0d3e7 100644
--- a/ambari-server/src/main/python/ambari_server/serverUtils.py
+++ b/ambari-server/src/main/python/ambari_server/serverUtils.py
@@ -17,9 +17,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 '''
-
+import ambari_simplejson as json # simplejson is much faster comparing to 
Python 2.6 json module and has the same functions set.
+import base64
 import os
+import sys
 import time
+import urllib2
+
 from ambari_commons.exceptions import FatalException, NonFatalException
 from ambari_commons.logging_utils import get_verbose
 from ambari_commons.os_family_impl import OsFamilyFuncImpl, OsFamilyImpl
@@ -28,6 +32,8 @@ from ambari_commons.os_utils import run_os_command
 from ambari_server.resourceFilesKeeper import ResourceFilesKeeper, 
KeeperException
 from ambari_server.serverConfiguration import configDefaults, PID_NAME, 
get_resources_location, get_stack_location, \
   CLIENT_API_PORT, CLIENT_API_PORT_PROPERTY, SSL_API, DEFAULT_SSL_API_PORT, 
SSL_API_PORT
+from ambari_server.userInput import get_validated_string_input
+from contextlib import closing
 
 
 # Ambari server API properties
@@ -133,3 +139,85 @@ def get_ambari_server_api_base(properties):
     if api_port_prop is not None:
       api_port = api_port_prop
   return '{0}://{1}:{2!s}/api/v1/'.format(api_protocol, SERVER_API_HOST, 
api_port)
+
+
+def get_ambari_admin_username_password_pair(options):
+  """
+  Returns the Ambari administrator credential.
+  If not supplied via command line options, the user is queried for the 
username and password.
+  :param options: the collected command line options
+  :return: the Ambari admin credentials
+  """
+  admin_login = options.ambari_admin_username \
+    if hasattr(options, 'ambari_admin_username') and 
options.ambari_admin_username is not None \
+    else get_validated_string_input("Enter Ambari Admin login: ", None, None, 
None, False, False)
+  admin_password = options.ambari_admin_password \
+    if hasattr(options, 'ambari_admin_password') and 
options.ambari_admin_password is not None \
+    else get_validated_string_input("Enter Ambari Admin password: ", None, 
None, None, True, False)
+  return admin_login, admin_password
+
+
+def get_cluster_name(properties, admin_login, admin_password):
+  """
+  Fetches the name of the first cluster (in case there are more)
+  from the response of host:port/api/v1/clusters call
+  """
+  url = get_ambari_server_api_base(properties) + 'clusters'
+  admin_auth = base64.encodestring('%s:%s' % (admin_login, 
admin_password)).replace('\n', '')
+  request = urllib2.Request(url)
+  request.add_header('Authorization', 'Basic %s' % admin_auth)
+  request.add_header('X-Requested-By', 'ambari')
+  request.get_method = lambda: 'GET'
+
+  request_in_progress = True
+  cluster_name = None
+  sys.stdout.write('\nFetching cluster name')
+  numOfTries = 0
+  while request_in_progress:
+    numOfTries += 1
+    if (numOfTries == 60):
+      raise FatalException(1, "Could not fetch cluster name within a minute; 
giving up!")
+    sys.stdout.write('.')
+    sys.stdout.flush()
+
+    try:
+      with closing(urllib2.urlopen(request)) as response:
+        response_status_code = response.getcode()
+        if response_status_code != 200:
+          request_in_progress = False
+          err = 'Error while fetching cluster name. Http status code - ' + 
str(response_status_code)
+          raise FatalException(1, err)
+        else:
+            response_body = json.loads(response.read())
+            items = response_body['items']
+            if len(items) > 0:
+               cluster_name = items[0]['Clusters']['cluster_name']
+               sys.stdout.write('\nFound cluster name: 
{0}'.format(cluster_name))
+            if not cluster_name:
+              time.sleep(1)
+            else:
+              request_in_progress = False
+    except Exception as e:
+      request_in_progress = False
+      err = 'Error while fetching cluster name. Error details: %s' % e
+      raise FatalException(1, err)
+
+    return cluster_name
+
+
+def perform_changes_via_rest_api(properties, admin_login, admin_password, 
url_postfix, get_method, request_data = None):
+  url = get_ambari_server_api_base(properties) + url_postfix
+  admin_auth = base64.encodestring('%s:%s' % (admin_login, 
admin_password)).replace('\n', '')
+  request = urllib2.Request(url)
+  request.add_header('Authorization', 'Basic %s' % admin_auth)
+  request.add_header('X-Requested-By', 'ambari')
+  if request_data is not None:
+    request.add_data(json.dumps(request_data))
+  request.get_method = lambda: get_method
+
+  with closing(urllib2.urlopen(request)) as response:
+    response_status_code = response.getcode()
+    if response_status_code not in (200, 201):
+      err = 'Error while performing changes via Ambari REST API. Http status 
code - ' + str(response_status_code)
+      raise FatalException(1, err)
+
diff --git a/ambari-server/src/main/python/ambari_server/setupSecurity.py 
b/ambari-server/src/main/python/ambari_server/setupSecurity.py
index f30915b..956468c 100644
--- a/ambari-server/src/main/python/ambari_server/setupSecurity.py
+++ b/ambari-server/src/main/python/ambari_server/setupSecurity.py
@@ -50,7 +50,7 @@ from ambari_server.serverConfiguration import configDefaults, 
parse_properties_f
   SSL_TRUSTSTORE_PASSWORD_PROPERTY, SSL_TRUSTSTORE_PATH_PROPERTY, 
SSL_TRUSTSTORE_TYPE_PROPERTY, \
   SSL_API, SSL_API_PORT, DEFAULT_SSL_API_PORT, CLIENT_API_PORT, 
JDK_NAME_PROPERTY, JCE_NAME_PROPERTY, JAVA_HOME_PROPERTY, \
   get_resources_location, SECURITY_MASTER_KEY_LOCATION, SETUP_OR_UPGRADE_MSG, 
CHECK_AMBARI_KRB_JAAS_CONFIGURATION_PROPERTY
-from ambari_server.serverUtils import is_server_runing, 
get_ambari_server_api_base
+from ambari_server.serverUtils import is_server_runing, 
get_ambari_server_api_base, get_ambari_admin_username_password_pair, 
perform_changes_via_rest_api
 from ambari_server.setupActions import SETUP_ACTION, LDAP_SETUP_ACTION
 from ambari_server.userInput import get_validated_string_input, 
get_prompt_default, read_password, get_YN_input, quit_if_has_answer
 from ambari_server.serverClassPath import ServerClassPath
@@ -349,6 +349,7 @@ def sync_ldap(options):
     err = 'Must specify a sync option (all, existing, users or groups).  
Please invoke ambari-server.py --help to print the options.'
     raise FatalException(1, err)
 
+  #TODO: use serverUtils.get_ambari_admin_username_password_pair (requires 
changes in ambari-server.py too to modify option names)
   admin_login = ldap_sync_options.ldap_sync_admin_name\
     if ldap_sync_options.ldap_sync_admin_name is not None and 
ldap_sync_options.ldap_sync_admin_name \
     else get_validated_string_input(prompt="Enter Ambari Admin login: ", 
default=None,
@@ -673,39 +674,17 @@ def init_ldap_properties_list_reqd(properties, options):
   ]
   return ldap_properties
 
-def get_ambari_admin_username_password_pair(options):
-  admin_login = options.ambari_admin_username if options.ambari_admin_username 
is not None else get_validated_string_input("Enter Ambari Admin login: ", None, 
None, None, False, False)
-  admin_password = options.ambari_admin_password if 
options.ambari_admin_password is not None else 
get_validated_string_input("Enter Ambari Admin password: ", None, None, None, 
True, False)
-
-  return admin_login, admin_password
-
 def update_ldap_configuration(options, properties, ldap_property_value_map):
   admin_login, admin_password = 
get_ambari_admin_username_password_pair(options)
-  url = get_ambari_server_api_base(properties) + SETUP_LDAP_CONFIG_URL
-  admin_auth = base64.encodestring('%s:%s' % (admin_login, 
admin_password)).replace('\n', '')
-  request = urllib2.Request(url)
-  request.add_header('Authorization', 'Basic %s' % admin_auth)
-  request.add_header('X-Requested-By', 'ambari')
-  data = {
+  request_data = {
     "Configuration": {
       "category": "ldap-configuration",
       "properties": {
       }
     }
   }
-  data['Configuration']['properties'] = ldap_property_value_map
-  request.add_data(json.dumps(data))
-  request.get_method = lambda: 'PUT'
-
-  try:
-    with closing(urllib2.urlopen(request)) as response:
-      response_status_code = response.getcode()
-      if response_status_code != 200:
-        err = 'Error during setup-ldap. Http status code - ' + 
str(response_status_code)
-        raise FatalException(1, err)
-  except Exception as e:
-    err = 'Updating LDAP configuration failed. Error details: %s' % e
-    raise FatalException(1, err)
+  request_data['Configuration']['properties'] = ldap_property_value_map
+  perform_changes_via_rest_api(properties, admin_login, admin_password, 
SETUP_LDAP_CONFIG_URL, 'PUT', request_data)
 
 def setup_ldap(options):
   logger.info("Setup LDAP.")
diff --git a/ambari-server/src/main/python/ambari_server/setupSso.py 
b/ambari-server/src/main/python/ambari_server/setupSso.py
index 9827761..d065849 100644
--- a/ambari-server/src/main/python/ambari_server/setupSso.py
+++ b/ambari-server/src/main/python/ambari_server/setupSso.py
@@ -17,16 +17,23 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 '''
+import ambari_simplejson as json
+import base64
 import logging
 import re
+import sys
+import time
+import urllib2
 
 from ambari_commons.os_utils import is_root, run_os_command, copy_file, 
set_file_permissions, remove_file
 from ambari_commons.exceptions import FatalException, NonFatalException
-from ambari_commons.logging_utils import get_silent, print_warning_msg, 
print_error_msg
+from ambari_commons.logging_utils import get_silent, print_info_msg
 from ambari_server.userInput import get_validated_string_input, get_YN_input, 
get_multi_line_input
+from ambari_server.serverUtils import is_server_runing, 
get_ambari_server_api_base, get_ambari_admin_username_password_pair, 
get_cluster_name, perform_changes_via_rest_api
 from ambari_server.setupSecurity import REGEX_HOSTNAME_PORT, REGEX_TRUE_FALSE
 from ambari_server.serverConfiguration import get_ambari_properties, 
get_value_from_properties, update_properties, \
   store_password_file
+from contextlib import closing
 
 logger = logging.getLogger(__name__)
 
@@ -47,8 +54,16 @@ JWT_PUBLIC_KEY_FILENAME = "jwt-cert.pem"
 JWT_PUBLIC_KEY_HEADER = "-----BEGIN CERTIFICATE-----\n"
 JWT_PUBLIC_KEY_FOOTER = "\n-----END CERTIFICATE-----\n"
 
+SSO_MANAGE_SERVICES = "ambari.sso.manage_services"
+SSO_ENABLED_SERVICES = "ambari.sso.enabled_services"
+WILDCARD_FOR_ALL_SERVICES = "*"
+SERVICE_NAME_AMBARI = 'Ambari'
 
-def validateOptions(options):
+URL_TO_FETCH_SERVICES_ELIGIBLE_FOR_SSO = 
"clusters/:CLUSTER_NAME/services?ServiceInfo/sso_integration_supported=true" ## 
:CLUSTER_NAME should be replaced
+SETUP_SSO_CONFIG_URL = 
'services/AMBARI/components/AMBARI_SERVER/configurations/sso-configuration'
+
+
+def validate_options(options):
   errors = []
   if options.sso_enabled and not re.search(REGEX_TRUE_FALSE, 
options.sso_enabled):
     errors.append("--sso-enabled should be to either 'true' or 'false'")
@@ -66,7 +81,7 @@ def validateOptions(options):
     raise FatalException(1, error_msg.format(str(errors)))
 
 
-def populateSsoProviderUrl(options, properties):
+def populate_sso_provider_url(options, properties):
   if not options.sso_provider_url:
       provider_url = get_value_from_properties(properties, 
JWT_AUTH_PROVIDER_URL, JWT_AUTH_PROVIDER_URL_DEFAULT)
       provider_url = get_validated_string_input("Provider URL [URL] 
({0}):".format(provider_url), provider_url, REGEX_HOSTNAME_PORT,
@@ -77,7 +92,7 @@ def populateSsoProviderUrl(options, properties):
   properties.process_pair(JWT_AUTH_PROVIDER_URL, provider_url)
 
 
-def populateSsoPublicCert(options, properties):
+def populate_sso_public_cert(options, properties):
   if not options.sso_public_cert_file:
     cert_path = properties.get_property(JWT_PUBLIC_KEY)
     cert_string = get_multi_line_input("Public Certificate pem 
({0})".format('stored' if cert_path else 'empty'))
@@ -93,7 +108,7 @@ def populateSsoPublicCert(options, properties):
   properties.process_pair(JWT_PUBLIC_KEY, cert_path)
 
 
-def populateJwtCookieName(options, properties):
+def populate_jwt_cookie_name(options, properties):
   if not options.sso_jwt_cookie_name:
     cookie_name = get_value_from_properties(properties, JWT_COOKIE_NAME, 
JWT_COOKIE_NAME_DEFAULT)
     cookie_name = get_validated_string_input("JWT Cookie name 
({0}):".format(cookie_name), cookie_name, REGEX_ANYTHING,
@@ -104,7 +119,7 @@ def populateJwtCookieName(options, properties):
   properties.process_pair(JWT_COOKIE_NAME, cookie_name)
 
 
-def populateJwtAudiences(options, properties):
+def populate_jwt_audiences(options, properties):
   if not options.sso_jwt_audience_list:
     audiences = properties.get_property(JWT_AUDIENCES)
     audiences = get_validated_string_input("JWT audiences list 
(comma-separated), empty for any ({0}):".format(audiences), audiences,
@@ -113,6 +128,135 @@ def populateJwtAudiences(options, properties):
     audiences = options.sso_jwt_audience_list
 
   properties.process_pair(JWT_AUDIENCES, audiences)
+  
+def get_eligible_services(properties, admin_login, admin_password, 
cluster_name):
+  safe_cluster_name = urllib2.quote(cluster_name)
+  url = get_ambari_server_api_base(properties) + 
URL_TO_FETCH_SERVICES_ELIGIBLE_FOR_SSO.replace(":CLUSTER_NAME", 
safe_cluster_name)
+  admin_auth = base64.encodestring('%s:%s' % (admin_login, 
admin_password)).replace('\n', '')
+  request = urllib2.Request(url)
+  request.add_header('Authorization', 'Basic %s' % admin_auth)
+  request.add_header('X-Requested-By', 'ambari')
+  request.get_method = lambda: 'GET'
+
+  services = [SERVICE_NAME_AMBARI]
+  sys.stdout.write('\nFetching SSO enabled services')
+  numOfTries = 0
+  request_in_progress = True
+  while request_in_progress:
+    numOfTries += 1
+    if (numOfTries == 60):
+      raise FatalException(1, "Could not fetch eligible services within a 
minute; giving up!")
+    sys.stdout.write('.')
+    sys.stdout.flush()
+
+    try:
+      with closing(urllib2.urlopen(request)) as response:
+        response_status_code = response.getcode()
+        if response_status_code != 200:
+          request_in_progress = False
+          err = 'Error while fetching eligible services. Http status code - ' 
+ str(response_status_code)
+          raise FatalException(1, err)
+        else:
+            response_body = json.loads(response.read())
+            items = response_body['items']
+            if len(items) > 0:
+              for item in items:
+                services.append(item['ServiceInfo']['service_name'])
+              if not services:
+                time.sleep(1)
+              else:
+                request_in_progress = False
+            else:
+              request_in_progress = False
+
+    except Exception as e:
+      request_in_progress = False
+      err = 'Error while fetching eligible services. Error details: %s' % e
+      raise FatalException(1, err)
+  if (len(services) == 0):
+    sys.stdout.write('\nThere is no SSO enabled services found\n')
+  else:
+    sys.stdout.write('\nFound SSO enabled services: {0}\n'.format(', 
'.join(str(s) for s in services)))
+  return services
+
+def get_services_requires_sso(options, properties, admin_login, 
admin_password):
+  if not options.sso_enabled_services:
+    configure_for_all_services = get_YN_input("Use SSO for all services [y/n] 
(n): ", False)
+    if configure_for_all_services:
+      services = WILDCARD_FOR_ALL_SERVICES
+    else:
+      cluster_name = get_cluster_name(properties, admin_login, admin_password)
+      eligible_services = get_eligible_services(properties, admin_login, 
admin_password, cluster_name)
+      services = ''
+      for service in eligible_services:
+        question = "Use SSO for {0} [y/n] (y): ".format(service)
+        if get_YN_input(question, True):
+          if len(services) > 0:
+            services = services + ", "
+          services = services + service
+  else:
+    services = options.sso_enabled_services
+
+  return services
+
+def get_sso_property_from_db(properties, admin_login, admin_password, 
property_name):
+  sso_property = None
+  url = get_ambari_server_api_base(properties) + SETUP_SSO_CONFIG_URL
+  admin_auth = base64.encodestring('%s:%s' % (admin_login, 
admin_password)).replace('\n', '')
+  request = urllib2.Request(url)
+  request.add_header('Authorization', 'Basic %s' % admin_auth)
+  request.add_header('X-Requested-By', 'ambari')
+  request.get_method = lambda: 'GET'
+  request_in_progress = True
+
+  sys.stdout.write('\nFetching SSO configuration from DB')
+  numOfTries = 0
+  while request_in_progress:
+    numOfTries += 1
+    if (numOfTries == 60):
+      raise FatalException(1, "Could not fetch SSO configuration within a 
minute; giving up!")
+    sys.stdout.write('.')
+    sys.stdout.flush()
+
+    try:
+      with closing(urllib2.urlopen(request)) as response:
+        response_status_code = response.getcode()
+        if response_status_code != 200:
+          request_in_progress = False
+          if response_status_code == 404:
+            ## This means that there is no SSO configuration in the database 
yet -> we can not fetch the property (but this is NOT an error)
+            sso_property = None
+          else:
+            err = 'Error while fetching SSO configuration. Http status code - 
' + str(response_status_code)
+            raise FatalException(1, err)
+        else:
+            response_body = json.loads(response.read())
+            sso_properties = response_body['Configuration']['properties']
+            sso_property = sso_properties[property_name]
+            if not sso_property:
+              time.sleep(1)
+            else:
+              request_in_progress = False
+    except Exception as e:
+      request_in_progress = False
+      err = 'Error while fetching SSO configuration. Error details: %s' % e
+      raise FatalException(1, err)
+
+  return sso_property
+
+def update_sso_conf(properties, enable_sso, services, admin_login, 
admin_password):
+  sso_configuration_properties = {}
+  sso_configuration_properties[SSO_MANAGE_SERVICES] = "true" if enable_sso 
else "false"
+  sso_configuration_properties[SSO_ENABLED_SERVICES] = services
+  request_data = {
+    "Configuration": {
+      "category": "sso-configuration",
+      "properties": {
+      }
+    }
+  }
+  request_data['Configuration']['properties'] = sso_configuration_properties
+  perform_changes_via_rest_api(properties, admin_login, admin_password, 
SETUP_SSO_CONFIG_URL, 'PUT', request_data)
 
 
 def setup_sso(options):
@@ -120,33 +264,44 @@ def setup_sso(options):
   if not is_root():
     raise FatalException(4, 'ambari-server setup-sso should be run with 
root-level privileges')
 
+  server_status, pid = is_server_runing()
+  if not server_status:
+    err = 'Ambari Server is not running.'
+    raise FatalException(1, err)
+
   if not get_silent():
-    validateOptions(options)
+    validate_options(options)
 
     properties = get_ambari_properties()
 
-    must_setup_params = False
+    admin_login, admin_password = 
get_ambari_admin_username_password_pair(options)
+
     if not options.sso_enabled:
-      sso_enabled = properties.get_property(JWT_AUTH_ENBABLED).lower() in 
['true']
+      sso_enabled_from_db = get_sso_property_from_db(properties, admin_login, 
admin_password, SSO_MANAGE_SERVICES)
+      sso_enabled = sso_enabled_from_db == None or sso_enabled_from_db in 
['true']
+      print_info_msg("SSO is currently {0}".format("not configured" if 
sso_enabled_from_db == None else ("enabled" if sso_enabled else "disabled")), 
True)
       if sso_enabled:
-        if get_YN_input("Do you want to disable SSO authentication [y/n] 
(n)?", False):
-          properties.process_pair(JWT_AUTH_ENBABLED, "false")
+        enable_sso = not get_YN_input("Do you want to disable SSO 
authentication [y/n] (n)? ", False)
       else:
-        if get_YN_input("Do you want to configure SSO authentication [y/n] 
(y)?", True):
-          properties.process_pair(JWT_AUTH_ENBABLED, "true")
-          must_setup_params = True
+        if get_YN_input("Do you want to configure SSO authentication [y/n] 
(y)? ", True):
+          enable_sso = True
         else:
           return False
     else:
-      properties.process_pair(JWT_AUTH_ENBABLED, options.sso_enabled)
-      must_setup_params = options.sso_enabled == 'true'
+      enable_sso = options.sso_enabled == 'true'
+
+    services = ''
+    if enable_sso:
+      populate_sso_provider_url(options, properties)
+      populate_sso_public_cert(options, properties)
+      populate_jwt_cookie_name(options, properties)
+      populate_jwt_audiences(options, properties)
+      services = get_services_requires_sso(options, properties, admin_login, 
admin_password)
 
-    if must_setup_params:
-      populateSsoProviderUrl(options, properties)
-      populateSsoPublicCert(options, properties)
-      populateJwtCookieName(options, properties)
-      populateJwtAudiences(options, properties)
+    update_sso_conf(properties, enable_sso, services, admin_login, 
admin_password)
 
+    enable_jwt_auth = WILDCARD_FOR_ALL_SERVICES == services or 
SERVICE_NAME_AMBARI in services
+    properties.process_pair(JWT_AUTH_ENBABLED, "true" if enable_jwt_auth else 
"false")
     update_properties(properties)
 
     pass
diff --git a/ambari-server/src/test/python/TestAmbariServer.py 
b/ambari-server/src/test/python/TestAmbariServer.py
index b8fb6b7..cbcfff4 100644
--- a/ambari-server/src/test/python/TestAmbariServer.py
+++ b/ambari-server/src/test/python/TestAmbariServer.py
@@ -7332,6 +7332,8 @@ class TestAmbariServer(TestCase):
     sys.stdout = out
 
     options = self._create_empty_options_mock()
+    options.ambari_admin_username = 'admin'
+    options.ambari_admin_password = 'admin'
     is_server_runing_method.return_value = (True, 0)
 
     search_file_message.return_value = "filepath"
@@ -7362,8 +7364,6 @@ class TestAmbariServer(TestCase):
         return 'skip'
       if 'URL Port' in args[0]:
         return '1'
-      if 'Ambari Admin' in args[0]:
-        return 'admin'
       if args[1] == "true" or args[1] == "false":
         return args[1]
       else:
@@ -7419,8 +7419,6 @@ class TestAmbariServer(TestCase):
           return "valid"
       if 'URL Port' in args[0]:
         return '1'
-      if 'Ambari Admin' in args[0]:
-        return 'admin'
       if args[1] == "true" or args[1] == "false":
         return args[1]
       else:
@@ -7502,8 +7500,6 @@ class TestAmbariServer(TestCase):
         return 'skip'
       if 'URL Port' in args[0]:
         return '1'
-      if 'Ambari Admin' in args[0]:
-        return 'admin'
       if 'Primary URL' in args[0]:
         return kwargs['answer']
       if args[1] == "true" or args[1] == "false":
@@ -7518,6 +7514,8 @@ class TestAmbariServer(TestCase):
     urlopen_method.return_value = response
 
     options =  self._create_empty_options_mock()
+    options.ambari_admin_username = 'admin'
+    options.ambari_admin_password = 'admin'
     options.ldap_url = "a:1"
 
     setup_ldap(options)
@@ -7621,8 +7619,6 @@ class TestAmbariServer(TestCase):
         return 'skip'
       if 'URL Port' in args[0]:
         return '1'
-      if 'Ambari Admin' in args[0]:
-        return 'admin'
       if 'Primary URL' in args[0]:
         return kwargs['answer']
       if args[1] == "true" or args[1] == "false":
@@ -7637,6 +7633,8 @@ class TestAmbariServer(TestCase):
     urlopen_method.return_value = response
 
     options = self._create_empty_options_mock()
+    options.ambari_admin_username = 'admin'
+    options.ambari_admin_password = 'admin'
     options.ldap_force_setup = True
 
     setup_ldap(options)
diff --git a/ambari-server/src/test/python/TestServerUtils.py 
b/ambari-server/src/test/python/TestServerUtils.py
index 3a5286d..2f982cf 100644
--- a/ambari-server/src/test/python/TestServerUtils.py
+++ b/ambari-server/src/test/python/TestServerUtils.py
@@ -33,7 +33,7 @@ with patch.object(platform, "linux_distribution", 
return_value = MagicMock(retur
   with patch("os.path.isdir", return_value = MagicMock(return_value=True)):
     with patch("os.access", return_value = MagicMock(return_value=True)):
       with patch.object(os_utils, "parse_log4j_file", 
return_value={'ambari.log.dir': '/var/log/ambari-server'}):
-        from ambari_server.serverUtils import get_ambari_server_api_base
+        from ambari_server.serverUtils import get_ambari_server_api_base, 
get_ambari_admin_username_password_pair
         from ambari_server.serverConfiguration import CLIENT_API_PORT, 
CLIENT_API_PORT_PROPERTY, SSL_API, DEFAULT_SSL_API_PORT, SSL_API_PORT
 
 @patch.object(platform, "linux_distribution", new = 
MagicMock(return_value=('Redhat', '6.4', 'Final')))
@@ -67,6 +67,32 @@ class TestServerUtils(TestCase):
     self.assertEquals(result, 'https://127.0.0.1:8443/api/v1/')
 
 
+  def test_get_ambari_admin_credentials_from_cli_options(self):
+    user_name = "admin"
+    password = "s#perS3cr3tP4ssw0d!"
+    options = MagicMock()
+    options.ambari_admin_username = user_name
+    options.ambari_admin_password = password
+    user, pw = get_ambari_admin_username_password_pair(options)
+    self.assertEquals(user, user_name)
+    self.assertEquals(pw, password)
+    
+  @patch("ambari_server.serverUtils.get_validated_string_input")
+  def test_get_ambari_admin_credentials_from_user_input(self, 
get_validated_string_input_mock):
+    user_name = "admin"
+    password = "s#perS3cr3tP4ssw0d!"
+    options = MagicMock()
+    options.ambari_admin_username = None
+    options.ambari_admin_password = None
+
+    def valid_input_side_effect(*args, **kwargs):
+      return user_name if 'Ambari Admin login' in args[0] else password
+
+    get_validated_string_input_mock.side_effect = valid_input_side_effect
+
+    user, pw = get_ambari_admin_username_password_pair(options)
+    self.assertEquals(user, user_name)
+    self.assertEquals(pw, password)
 
 class FakeProperties(object):
   def __init__(self, prop_map):
diff --git a/ambari-server/src/test/python/TestSetupSso.py 
b/ambari-server/src/test/python/TestSetupSso.py
index af1ac6f..8e7112c 100644
--- a/ambari-server/src/test/python/TestSetupSso.py
+++ b/ambari-server/src/test/python/TestSetupSso.py
@@ -15,6 +15,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 
or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 '''
+import operator
 import os
 import platform
 import sys
@@ -75,14 +76,35 @@ class TestSetupSso(unittest.TestCase):
     pass
 
 
+  @patch("ambari_server.setupSso.is_server_runing")
+  @patch("ambari_server.setupSso.is_root")
+  def test_sso_setup_should_fail_if_server_is_not_running(self, is_root_mock, 
is_server_runing_mock):
+    out = StringIO.StringIO()
+    sys.stdout = out
+
+    is_root_mock.return_value = True
+    is_server_runing_mock.return_value = (False, 0)
+    options = self._create_empty_options_mock()
+
+    try:
+      setup_sso(options)
+      self.fail("Should fail with non-fatal exception")
+    except FatalException as e:
+      self.assertTrue("Ambari Server is not running" in e.reason)
+      pass
+
+    sys.stdout = sys.__stdout__
+    pass
 
   @patch("ambari_server.setupSso.get_silent")
+  @patch("ambari_server.setupSso.is_server_runing")
   @patch("ambari_server.setupSso.is_root")
-  def test_silent_mode_is_not_allowed(self, is_root_mock, get_silent_mock):
+  def test_silent_mode_is_not_allowed(self, is_root_mock, 
is_server_runing_mock, get_silent_mock):
     out = StringIO.StringIO()
     sys.stdout = out
 
     is_root_mock.return_value = True
+    is_server_runing_mock.return_value = (True, 0)
     get_silent_mock.return_value = True
     options = self._create_empty_options_mock()
 
@@ -99,12 +121,14 @@ class TestSetupSso(unittest.TestCase):
 
 
   @patch("ambari_server.setupSso.get_silent")
+  @patch("ambari_server.setupSso.is_server_runing")
   @patch("ambari_server.setupSso.is_root")
-  def test_invalid_sso_enabled_cli_option_should_result_in_error(self, 
is_root_mock, get_silent_mock):
+  def test_invalid_sso_enabled_cli_option_should_result_in_error(self, 
is_root_mock, is_server_runing_mock, get_silent_mock):
     out = StringIO.StringIO()
     sys.stdout = out
 
     is_root_mock.return_value = True
+    is_server_runing_mock.return_value = (True, 0)
     get_silent_mock.return_value = False
     options = self._create_empty_options_mock()
     options.sso_enabled = 'not_true_or_false'
@@ -122,12 +146,14 @@ class TestSetupSso(unittest.TestCase):
 
 
   @patch("ambari_server.setupSso.get_silent")
+  @patch("ambari_server.setupSso.is_server_runing")
   @patch("ambari_server.setupSso.is_root")
-  def 
test_missing_sso_provider_url_cli_option_when_enabling_sso_should_result_in_error(self,
 is_root_mock, get_silent_mock):
+  def 
test_missing_sso_provider_url_cli_option_when_enabling_sso_should_result_in_error(self,
 is_root_mock, is_server_runing_mock, get_silent_mock):
     out = StringIO.StringIO()
     sys.stdout = out
 
     is_root_mock.return_value = True
+    is_server_runing_mock.return_value = (True, 0)
     get_silent_mock.return_value = False
     options = self._create_empty_options_mock()
     options.sso_enabled = 'true'
@@ -146,12 +172,14 @@ class TestSetupSso(unittest.TestCase):
 
 
   @patch("ambari_server.setupSso.get_silent")
+  @patch("ambari_server.setupSso.is_server_runing")
   @patch("ambari_server.setupSso.is_root")
-  def 
test_missing_sso_public_cert_file_cli_option_when_enabling_sso_should_result_in_error(self,
 is_root_mock, get_silent_mock):
+  def 
test_missing_sso_public_cert_file_cli_option_when_enabling_sso_should_result_in_error(self,
 is_root_mock, is_server_runing_mock, get_silent_mock):
     out = StringIO.StringIO()
     sys.stdout = out
 
     is_root_mock.return_value = True
+    is_server_runing_mock.return_value = (True, 0)
     get_silent_mock.return_value = False
     options = self._create_empty_options_mock()
     options.sso_enabled = 'true'
@@ -170,12 +198,14 @@ class TestSetupSso(unittest.TestCase):
 
 
   @patch("ambari_server.setupSso.get_silent")
+  @patch("ambari_server.setupSso.is_server_runing")
   @patch("ambari_server.setupSso.is_root")
-  def 
test_invalid_sso_provider_url_cli_option_when_enabling_sso_should_result_in_error(self,
 is_root_mock, get_silent_mock):
+  def 
test_invalid_sso_provider_url_cli_option_when_enabling_sso_should_result_in_error(self,
 is_root_mock, is_server_runing_mock, get_silent_mock):
     out = StringIO.StringIO()
     sys.stdout = out
 
     is_root_mock.return_value = True
+    is_server_runing_mock.return_value = (True, 0)
     get_silent_mock.return_value = False
     options = self._create_empty_options_mock()
     options.sso_enabled = 'true'
@@ -193,21 +223,25 @@ class TestSetupSso(unittest.TestCase):
 
 
 
+  @patch("ambari_server.setupSso.perform_changes_via_rest_api")
   @patch("ambari_server.setupSso.update_properties")
   @patch("ambari_server.setupSso.get_ambari_properties")
   @patch("ambari_server.setupSso.get_silent")
+  @patch("ambari_server.setupSso.is_server_runing")
   @patch("ambari_server.setupSso.is_root")
-  def test_all_cli_options_are_collected_when_enabling_sso(self, is_root_mock, 
get_silent_mock, get_ambari_properties_mock, update_properties_mock):
+  def test_all_cli_options_are_collected_when_enabling_sso(self, is_root_mock, 
is_server_runing_mock, get_silent_mock, get_ambari_properties_mock, 
update_properties_mock, perform_changes_via_rest_api_mock):
     out = StringIO.StringIO()
     sys.stdout = out
 
     is_root_mock.return_value = True
+    is_server_runing_mock.return_value = (True, 0)
     get_silent_mock.return_value = False
 
     properties = Properties();
     get_ambari_properties_mock.return_value = properties
 
     sso_enabled = 'true'
+    sso_enabled_services = 'Ambari, SERVICE1, SERVICE2'
     sso_provider_url = 'http://testHost:8080'
     sso_public_cert_file = '/test/file/path'
     sso_jwt_cookie_name = 'test_cookie'
@@ -218,6 +252,7 @@ class TestSetupSso(unittest.TestCase):
     options.sso_public_cert_file = sso_public_cert_file
     options.sso_jwt_cookie_name = sso_jwt_cookie_name
     options.sso_jwt_audience_list = sso_jwt_audience_list
+    options.sso_enabled_services = sso_enabled_services
 
     setup_sso(options)
 
@@ -227,21 +262,24 @@ class TestSetupSso(unittest.TestCase):
     self.assertEqual(properties.get_property(JWT_PUBLIC_KEY), 
sso_public_cert_file)
     self.assertEqual(properties.get_property(JWT_COOKIE_NAME), 
sso_jwt_cookie_name)
     self.assertEqual(properties.get_property(JWT_AUDIENCES), 
sso_jwt_audience_list)
+    self.assertTrue(perform_changes_via_rest_api_mock.called)
 
     sys.stdout = sys.__stdout__
     pass
 
 
-
+  @patch("urllib2.urlopen")
   @patch("ambari_server.setupSso.update_properties")
   @patch("ambari_server.setupSso.get_ambari_properties")
   @patch("ambari_server.setupSso.get_silent")
+  @patch("ambari_server.setupSso.is_server_runing")
   @patch("ambari_server.setupSso.is_root")
-  def test_only_sso_enabled_cli_option_is_collected_when_disabling_sso(self, 
is_root_mock, get_silent_mock, get_ambari_properties_mock, 
update_properties_mock):
+  def test_only_sso_enabled_cli_option_is_collected_when_disabling_sso(self, 
is_root_mock, is_server_runing_mock, get_silent_mock, 
get_ambari_properties_mock, update_properties_mock, urlopen_mock):
     out = StringIO.StringIO()
     sys.stdout = out
 
     is_root_mock.return_value = True
+    is_server_runing_mock.return_value = (True, 0)
     get_silent_mock.return_value = False
 
     properties = Properties();
@@ -259,6 +297,10 @@ class TestSetupSso(unittest.TestCase):
     options.sso_jwt_cookie_name = sso_jwt_cookie_name
     options.sso_jwt_audience_list = sso_jwt_audience_list
 
+    response = MagicMock()
+    response.getcode.return_value = 200
+    urlopen_mock.return_value = response
+
     setup_sso(options)
 
     self.assertTrue(update_properties_mock.called)
@@ -268,6 +310,130 @@ class TestSetupSso(unittest.TestCase):
     self.assertTrue(JWT_COOKIE_NAME not in properties.propertyNames())
     self.assertTrue(JWT_AUDIENCES not in properties.propertyNames())
 
+    sys.stdout = sys.__stdout__
+    pass
+
+
+  @patch("ambari_server.setupSso.perform_changes_via_rest_api")
+  @patch("ambari_server.setupSso.get_YN_input")
+  @patch("ambari_server.setupSso.update_properties")
+  @patch("ambari_server.setupSso.get_ambari_properties")
+  @patch("ambari_server.setupSso.get_silent")
+  @patch("ambari_server.setupSso.is_server_runing")
+  @patch("ambari_server.setupSso.is_root")
+  def test_sso_is_enabled_for_all_services_via_user_input(self, is_root_mock, 
is_server_runing_mock, get_silent_mock, get_ambari_properties_mock, 
update_properties_mock, get_YN_input_mock,
+                                                             
perform_changes_via_rest_api_mock):
+    out = StringIO.StringIO()
+    sys.stdout = out
+
+    is_root_mock.return_value = True
+    is_server_runing_mock.return_value = (True, 0)
+    get_silent_mock.return_value = False
+    get_ambari_properties_mock.return_value = Properties()
+
+    def yn_input_side_effect(*args, **kwargs):
+      if 'all services' in args[0]:
+        return True
+      else:
+        raise Exception("ShouldNotBeInvoked") # only the 'Use SSO for all 
services' question should be asked for now
+
+    get_YN_input_mock.side_effect = yn_input_side_effect
+
+    options = self._create_empty_options_mock()
+    options.sso_enabled = 'true'
+    options.sso_provider_url = 'http://testHost:8080'
+    options.sso_public_cert_file = '/test/file/path'
+    options.sso_jwt_cookie_name = 'test_cookie'
+    options.sso_jwt_audience_list = 'test, audience, list'
+
+    setup_sso(options)
+
+    requestCall = perform_changes_via_rest_api_mock.call_args_list[0]
+    args, kwargs = requestCall
+    requestData = args[5]
+    self.assertTrue(isinstance(requestData, dict))
+    ssoProperties = requestData['Configuration']['properties'];
+    properties_updated_in_ambari_db = sorted(ssoProperties.iteritems(), 
key=operator.itemgetter(0))
+    properties_should_be_updated_in_ambari_db = 
sorted({"ambari.sso.enabled_services": "*", "ambari.sso.manage_services": 
"true"}.iteritems(), key=operator.itemgetter(0))
+    self.assertEqual(properties_should_be_updated_in_ambari_db, 
properties_updated_in_ambari_db)
+
+    sys.stdout = sys.__stdout__
+    pass
+
+
+  @patch("urllib2.urlopen")
+  @patch("ambari_server.setupSso.perform_changes_via_rest_api")
+  @patch("ambari_server.setupSso.get_cluster_name")
+  @patch("ambari_server.setupSso.get_YN_input")
+  @patch("ambari_server.setupSso.update_properties")
+  @patch("ambari_server.setupSso.get_ambari_properties")
+  @patch("ambari_server.setupSso.get_silent")
+  @patch("ambari_server.setupSso.is_server_runing")
+  @patch("ambari_server.setupSso.is_root")
+  def test_sso_enabled_services_are_collected_via_user_input(self, 
is_root_mock, is_server_runing_mock, get_silent_mock, 
get_ambari_properties_mock, update_properties_mock, get_YN_input_mock,
+                                                             
get_cluster_name_mock, perform_changes_via_rest_api_mock, urlopen_mock):
+    out = StringIO.StringIO()
+    sys.stdout = out
+
+    is_root_mock.return_value = True
+    is_server_runing_mock.return_value = (True, 0)
+    get_silent_mock.return_value = False
+    get_ambari_properties_mock.return_value = Properties()
+    get_cluster_name_mock.return_value = 'cluster1'
+
+    def yn_input_side_effect(*args, **kwargs):
+      if 'all services' in args[0]:
+        return False
+      else:
+        return True
+
+    get_YN_input_mock.side_effect = yn_input_side_effect
+
+    eligible_services = \
+    """
+        {
+          "href": 
"http://c7401:8080/api/v1/clusters/cluster1/services?ServiceInfo/sso_integration_supported=true";,
+          "items": [
+            {
+                "href": 
"http://c7401:8080/api/v1/clusters/cluster1/services/HDFS";,
+                "ServiceInfo": {
+                    "cluster_name": "cluster1",
+                    "service_name": "HDFS"
+                }
+            },
+            {
+                "href": 
"http://c7401:8080/api/v1/clusters/cluster1/services/ZOOKEPER";,
+                "ServiceInfo": {
+                    "cluster_name": "cluster1",
+                    "service_name": "ZOOKEPER"
+                }
+            }
+          ]
+        }
+    """
+
+    response = MagicMock()
+    response.getcode.return_value = 200
+    response.read.return_value = eligible_services
+    urlopen_mock.return_value = response
+
+    options = self._create_empty_options_mock()
+    options.sso_enabled = 'true'
+    options.sso_provider_url = 'http://testHost:8080'
+    options.sso_public_cert_file = '/test/file/path'
+    options.sso_jwt_cookie_name = 'test_cookie'
+    options.sso_jwt_audience_list = 'test, audience, list'
+
+    setup_sso(options)
+
+    requestCall = perform_changes_via_rest_api_mock.call_args_list[0]
+    args, kwargs = requestCall
+    requestData = args[5]
+    self.assertTrue(isinstance(requestData, dict))
+    ssoProperties = requestData['Configuration']['properties'];
+    properties_updated_in_ambari_db = sorted(ssoProperties.iteritems(), 
key=operator.itemgetter(0))
+    properties_should_be_updated_in_ambari_db = 
sorted({"ambari.sso.enabled_services": "Ambari, HDFS, ZOOKEPER", 
"ambari.sso.manage_services": "true"}.iteritems(), key=operator.itemgetter(0))
+    self.assertEqual(properties_should_be_updated_in_ambari_db, 
properties_updated_in_ambari_db)
 
     sys.stdout = sys.__stdout__
     pass
@@ -275,9 +441,10 @@ class TestSetupSso(unittest.TestCase):
   def _create_empty_options_mock(self):
     options = MagicMock()
     options.sso_enabled = None
+    options.sso_enabled_services = None
     options.sso_provider_url = None
     options.sso_public_cert_file = None
     options.sso_jwt_cookie_name = None
-    options.sso__jwt_audience_list = None
+    options.sso_jwt_audience_list = None
     return options
     
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
[email protected].

Reply via email to