Procházet zdrojové kódy

HUE-917 [core] Support SAML based authentication

SAML2 support with LDAP.
Key points: Logging in and Logging out.
To login, the huesaml.backends.SAML2Backend must be used.
Logging out currently not supported without timing out.
Users will have to logout through their IdP.
Tested with Shibboleth and OpenDS.
Abraham Elmahrek před 12 roky
rodič
revize
f3bdad5

+ 39 - 0
desktop/conf.dist/hue.ini

@@ -91,6 +91,7 @@
     # - desktop.auth.backend.SpnegoDjangoBackend
     # - desktop.auth.backend.RemoteUserDjangoBackend
     # - desktop.auth.backend.OAuthBackend
+    # - huesaml.backend.SAML2Backend
     ## backend=desktop.auth.backend.AllowFirstUserDjangoBackend
 
     # Backend to synchronize user-group membership with
@@ -222,6 +223,44 @@
     ## authenticate_url=https://api.twitter.com/oauth/authorize
 
 
+  ###########################################################################
+  # Settings to configure SAML
+  ###########################################################################
+
+  [saml]
+    # Xmlsec1 binary path. This program should be executable by the user running Hue.
+    ## xmlsec_binary=/usr/local/bin/xmlsec1
+
+    # Create users from SSO on login.
+    ## create_users_on_login=true
+
+    # Globally unique identifier of the entity.
+    ## entity_id=http://localhost:8888/saml2/metadata/
+
+    # Consumes assertions sent back from IdP.
+    ## assertion_consumer_service_uri=http://localhost:8888/saml2/acs/
+
+    # Logout using the SSO provider.
+    ## single_logout_service=http://localhost:8888/saml2/ls/
+
+    # Required attributes to ask for from IdP.
+    ## required_attributes=uid
+
+    # Optional attributes to ask for from IdP.
+    ## optional_attributes=true
+
+    # IdP metadata in the form of a file. This is generally an XML file containing metadata that the Identity Provider generates.
+    ## metadata_file=
+
+    # Private key to encrypt metadata with.
+    ## key_file=
+
+    # Signed certificate to send along with encrypted metadata.
+    ## cert_file=
+
+    # A mapping from attributes in the response from the IdP to django user attributes.
+    ## user_attribute_mapping={'uid':'username'}
+
 ###########################################################################
 # Settings to configure your Hadoop cluster.
 ###########################################################################

+ 39 - 0
desktop/conf/pseudo-distributed.ini.tmpl

@@ -100,6 +100,7 @@
     # - desktop.auth.backend.SpnegoDjangoBackend
     # - desktop.auth.backend.RemoteUserDjangoBackend
     # - desktop.auth.backend.OAuthBackend
+    # - huesaml.backend.SAML2Backend
     ## backend=desktop.auth.backend.AllowFirstUserDjangoBackend
 
     # Backend to synchronize user-group membership with
@@ -228,6 +229,44 @@
     ## authenticate_url=https://api.twitter.com/oauth/authorize
 
 
+###########################################################################
+# Settings to configure SAML
+###########################################################################
+
+[saml]
+  # Xmlsec1 binary path. This program should be executable by the user running Hue.
+  ## xmlsec_binary=/usr/local/bin/xmlsec1
+
+  # Create users from SSO on login.
+  ## create_users_on_login=true
+
+  # Globally unique identifier of the entity.
+  ## entity_id=http://localhost:8888/saml2/metadata/
+
+  # Consumes assertions sent back from IdP.
+  ## assertion_consumer_service_uri=http://localhost:8888/saml2/acs/
+
+  # Logout using the SSO provider.
+  ## single_logout_service=http://localhost:8888/saml2/ls/
+
+  # Required attributes to ask for from IdP.
+  ## required_attributes=uid
+
+  # Optional attributes to ask for from IdP.
+  ## optional_attributes=true
+
+  # IdP metadata in the form of a file. This is generally an XML file containing metadata that the Identity Provider generates.
+  ## metadata_file=
+
+  # Private key to encrypt metadata with.
+  ## key_file=
+
+  # Signed certificate to send along with encrypted metadata.
+  ## cert_file=
+
+  # A mapping from attributes in the response from the IdP to django user attributes.
+  ## user_attribute_mapping={'uid':'username'}
+
 ###########################################################################
 # Settings to configure your Hadoop cluster.
 ###########################################################################

+ 8 - 0
desktop/core/src/desktop/settings.py

@@ -24,6 +24,7 @@ import logging
 import os
 import sys
 import desktop.conf
+import huesaml.conf
 import desktop.log
 from desktop.lib.paths import get_desktop_root
 import pkg_resources
@@ -271,6 +272,13 @@ SECRET_KEY = desktop.conf.SECRET_KEY.get()
 if SECRET_KEY == "":
   logging.warning("secret_key should be configured")
 
+# SAML
+SAML_AUTHENTICATION = 'huesaml.backend.SAML2Backend' in AUTHENTICATION_BACKENDS
+if SAML_AUTHENTICATION:
+  from huesaml.saml_settings import *
+  INSTALLED_APPS.append('huesaml')
+  LOGIN_URL = '/saml2/login/'
+  SESSION_EXPIRE_AT_BROWSER_CLOSE = True
 
 ############################################################
 

+ 5 - 0
desktop/core/src/desktop/urls.py

@@ -19,6 +19,7 @@ import logging
 import os
 import re
 
+from django.conf import settings
 from django.conf.urls.defaults import include, patterns
 from django.contrib import admin
 
@@ -75,6 +76,10 @@ dynamic_patterns = patterns('',
 
 static_patterns = []
 
+# SAML specific
+if settings.SAML_AUTHENTICATION:
+  static_patterns.append((r'^saml2/', include('huesaml.urls')))
+
 # Root each app at /appname if they have a "urls" module
 for app in appmanager.DESKTOP_APPS:
   if app.urls:

+ 36 - 0
desktop/libs/huesaml/Makefile

@@ -0,0 +1,36 @@
+#
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+#
+
+
+ifeq ($(ROOT),)
+  $(error "Error: Expect the environment variable $$ROOT to point to the Desktop installation")
+endif
+
+include $(ROOT)/Makefile.sdk
+
+default::
+	@echo '  env-install    : Install into virtual-env'
+
+#
+# env-install
+#   Install app into the virtual environment.
+#
+.PHONY: env-install
+env-install: compile ext-env-install
+	@echo '--- Installing $(APP_NAME) into virtual-env'
+	@$(ENV_PYTHON) setup.py develop -N -q

+ 199 - 0
desktop/libs/huesaml/attribute-maps/SAML2.py

@@ -0,0 +1,199 @@
+__author__ = 'rolandh'
+
+EDUPERSON_OID = "urn:oid:1.3.6.1.4.1.5923.1.1.1."
+X500ATTR_OID = "urn:oid:2.5.4."
+NOREDUPERSON_OID = "urn:oid:1.3.6.1.4.1.2428.90.1."
+NETSCAPE_LDAP = "urn:oid:2.16.840.1.113730.3.1."
+UCL_DIR_PILOT = 'urn:oid:0.9.2342.19200300.100.1.'
+PKCS_9 = "urn:oid:1.2.840.113549.1.9.1."
+UMICH = "urn:oid:1.3.6.1.4.1.250.1.57."
+
+MAP = {
+    "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:uri",
+    "fro": {
+        EDUPERSON_OID+'2': 'eduPersonNickname',
+        EDUPERSON_OID+'9': 'eduPersonScopedAffiliation',
+        EDUPERSON_OID+'11': 'eduPersonAssurance',
+        EDUPERSON_OID+'10': 'eduPersonTargetedID',
+        EDUPERSON_OID+'4': 'eduPersonOrgUnitDN',
+        NOREDUPERSON_OID+'6': 'norEduOrgAcronym',
+        NOREDUPERSON_OID+'7': 'norEduOrgUniqueIdentifier',
+        NOREDUPERSON_OID+'4': 'norEduPersonLIN',
+        EDUPERSON_OID+'1': 'eduPersonAffiliation',
+        NOREDUPERSON_OID+'2': 'norEduOrgUnitUniqueNumber',
+        NETSCAPE_LDAP+'40': 'userSMIMECertificate',
+        NOREDUPERSON_OID+'1': 'norEduOrgUniqueNumber',
+        NETSCAPE_LDAP+'241': 'displayName',
+        UCL_DIR_PILOT+'37': 'associatedDomain',
+        EDUPERSON_OID+'6': 'eduPersonPrincipalName',
+        NOREDUPERSON_OID+'8': 'norEduOrgUnitUniqueIdentifier',
+        NOREDUPERSON_OID+'9': 'federationFeideSchemaVersion',
+        X500ATTR_OID+'53': 'deltaRevocationList',
+        X500ATTR_OID+'52': 'supportedAlgorithms',
+        X500ATTR_OID+'51': 'houseIdentifier',
+        X500ATTR_OID+'50': 'uniqueMember',
+        X500ATTR_OID+'19': 'physicalDeliveryOfficeName',
+        X500ATTR_OID+'18': 'postOfficeBox',
+        X500ATTR_OID+'17': 'postalCode',
+        X500ATTR_OID+'16': 'postalAddress',
+        X500ATTR_OID+'15': 'businessCategory',
+        X500ATTR_OID+'14': 'searchGuide',
+        EDUPERSON_OID+'5': 'eduPersonPrimaryAffiliation',
+        X500ATTR_OID+'12': 'title',
+        X500ATTR_OID+'11': 'ou',
+        X500ATTR_OID+'10': 'o',
+        X500ATTR_OID+'37': 'cACertificate',
+        X500ATTR_OID+'36': 'userCertificate',
+        X500ATTR_OID+'31': 'member',
+        X500ATTR_OID+'30': 'supportedApplicationContext',
+        X500ATTR_OID+'33': 'roleOccupant',
+        X500ATTR_OID+'32': 'owner',
+        NETSCAPE_LDAP+'1': 'carLicense',
+        PKCS_9+'1': 'email',
+        NETSCAPE_LDAP+'3': 'employeeNumber',
+        NETSCAPE_LDAP+'2': 'departmentNumber',
+        X500ATTR_OID+'39': 'certificateRevocationList',
+        X500ATTR_OID+'38': 'authorityRevocationList',
+        NETSCAPE_LDAP+'216': 'userPKCS12',
+        EDUPERSON_OID+'8': 'eduPersonPrimaryOrgUnitDN',
+        X500ATTR_OID+'9': 'street',
+        X500ATTR_OID+'8': 'st',
+        NETSCAPE_LDAP+'39': 'preferredLanguage',
+        EDUPERSON_OID+'7': 'eduPersonEntitlement',
+        X500ATTR_OID+'2': 'knowledgeInformation',
+        X500ATTR_OID+'7': 'l',
+        X500ATTR_OID+'6': 'c',
+        X500ATTR_OID+'5': 'serialNumber',
+        X500ATTR_OID+'4': 'sn',
+        UCL_DIR_PILOT+'60': 'jpegPhoto',
+        X500ATTR_OID+'65': 'pseudonym',
+        NOREDUPERSON_OID+'5': 'norEduPersonNIN',
+        UCL_DIR_PILOT+'3': 'mail',
+        UCL_DIR_PILOT+'25': 'dc',
+        X500ATTR_OID+'40': 'crossCertificatePair',
+        X500ATTR_OID+'42': 'givenName',
+        X500ATTR_OID+'43': 'initials',
+        X500ATTR_OID+'44': 'generationQualifier',
+        X500ATTR_OID+'45': 'x500UniqueIdentifier',
+        X500ATTR_OID+'46': 'dnQualifier',
+        X500ATTR_OID+'47': 'enhancedSearchGuide',
+        X500ATTR_OID+'48': 'protocolInformation',
+        X500ATTR_OID+'54': 'dmdName',
+        NETSCAPE_LDAP+'4': 'employeeType',
+        X500ATTR_OID+'22': 'teletexTerminalIdentifier',
+        X500ATTR_OID+'23': 'facsimileTelephoneNumber',
+        X500ATTR_OID+'20': 'telephoneNumber',
+        X500ATTR_OID+'21': 'telexNumber',
+        X500ATTR_OID+'26': 'registeredAddress',
+        X500ATTR_OID+'27': 'destinationIndicator',
+        X500ATTR_OID+'24': 'x121Address',
+        X500ATTR_OID+'25': 'internationaliSDNNumber',
+        X500ATTR_OID+'28': 'preferredDeliveryMethod',
+        X500ATTR_OID+'29': 'presentationAddress',
+        EDUPERSON_OID+'3': 'eduPersonOrgDN',
+        NOREDUPERSON_OID+'3': 'norEduPersonBirthDate',
+        UMICH+'57': 'labeledURI',
+        UCL_DIR_PILOT+'1': 'uid',
+    },
+    "to": {
+        'roleOccupant': X500ATTR_OID+'33',
+        'gn': X500ATTR_OID+'42',
+        'norEduPersonNIN': NOREDUPERSON_OID+'5',
+        'title': X500ATTR_OID+'12',
+        'facsimileTelephoneNumber': X500ATTR_OID+'23',
+        'mail': UCL_DIR_PILOT+'3',
+        'postOfficeBox': X500ATTR_OID+'18',
+        'fax': X500ATTR_OID+'23',
+        'telephoneNumber': X500ATTR_OID+'20',
+        'norEduPersonBirthDate': NOREDUPERSON_OID+'3',
+        'rfc822Mailbox': UCL_DIR_PILOT+'3',
+        'dc': UCL_DIR_PILOT+'25',
+        'countryName': X500ATTR_OID+'6',
+        'emailAddress': PKCS_9+'1',
+        'employeeNumber': NETSCAPE_LDAP+'3',
+        'organizationName': X500ATTR_OID+'10',
+        'eduPersonAssurance': EDUPERSON_OID+'11',
+        'norEduOrgAcronym': NOREDUPERSON_OID+'6',
+        'registeredAddress': X500ATTR_OID+'26',
+        'physicalDeliveryOfficeName': X500ATTR_OID+'19',
+        'associatedDomain': UCL_DIR_PILOT+'37',
+        'l': X500ATTR_OID+'7',
+        'stateOrProvinceName': X500ATTR_OID+'8',
+        'federationFeideSchemaVersion': NOREDUPERSON_OID+'9',
+        'pkcs9email': PKCS_9+'1',
+        'givenName': X500ATTR_OID+'42',
+        'givenname': X500ATTR_OID+'42',
+        'x500UniqueIdentifier': X500ATTR_OID+'45',
+        'eduPersonNickname': EDUPERSON_OID+'2',
+        'houseIdentifier': X500ATTR_OID+'51',
+        'street': X500ATTR_OID+'9',
+        'supportedAlgorithms': X500ATTR_OID+'52',
+        'preferredLanguage': NETSCAPE_LDAP+'39',
+        'postalAddress': X500ATTR_OID+'16',
+        'email': PKCS_9+'1',
+        'norEduOrgUnitUniqueIdentifier': NOREDUPERSON_OID+'8',
+        'eduPersonPrimaryOrgUnitDN': EDUPERSON_OID+'8',
+        'c': X500ATTR_OID+'6',
+        'teletexTerminalIdentifier': X500ATTR_OID+'22',
+        'o': X500ATTR_OID+'10',
+        'cACertificate': X500ATTR_OID+'37',
+        'telexNumber': X500ATTR_OID+'21',
+        'ou': X500ATTR_OID+'11',
+        'initials': X500ATTR_OID+'43',
+        'eduPersonOrgUnitDN': EDUPERSON_OID+'4',
+        'deltaRevocationList': X500ATTR_OID+'53',
+        'norEduPersonLIN': NOREDUPERSON_OID+'4',
+        'supportedApplicationContext': X500ATTR_OID+'30',
+        'eduPersonEntitlement': EDUPERSON_OID+'7',
+        'generationQualifier': X500ATTR_OID+'44',
+        'eduPersonAffiliation': EDUPERSON_OID+'1',
+        'eduPersonPrincipalName': EDUPERSON_OID+'6',
+        'edupersonprincipalname': EDUPERSON_OID+'6',
+        'localityName': X500ATTR_OID+'7',
+        'owner': X500ATTR_OID+'32',
+        'norEduOrgUnitUniqueNumber': NOREDUPERSON_OID+'2',
+        'searchGuide': X500ATTR_OID+'14',
+        'certificateRevocationList': X500ATTR_OID+'39',
+        'organizationalUnitName': X500ATTR_OID+'11',
+        'userCertificate': X500ATTR_OID+'36',
+        'preferredDeliveryMethod': X500ATTR_OID+'28',
+        'internationaliSDNNumber': X500ATTR_OID+'25',
+        'uniqueMember': X500ATTR_OID+'50',
+        'departmentNumber': NETSCAPE_LDAP+'2',
+        'enhancedSearchGuide': X500ATTR_OID+'47',
+        'userPKCS12': NETSCAPE_LDAP+'216',
+        'eduPersonTargetedID': EDUPERSON_OID+'10',
+        'norEduOrgUniqueNumber': NOREDUPERSON_OID+'1',
+        'x121Address': X500ATTR_OID+'24',
+        'destinationIndicator': X500ATTR_OID+'27',
+        'eduPersonPrimaryAffiliation': EDUPERSON_OID+'5',
+        'surname': X500ATTR_OID+'4',
+        'jpegPhoto': UCL_DIR_PILOT+'60',
+        'eduPersonScopedAffiliation': EDUPERSON_OID+'9',
+        'edupersonscopedaffiliation': EDUPERSON_OID+'9',
+        'protocolInformation': X500ATTR_OID+'48',
+        'knowledgeInformation': X500ATTR_OID+'2',
+        'employeeType': NETSCAPE_LDAP+'4',
+        'userSMIMECertificate': NETSCAPE_LDAP+'40',
+        'member': X500ATTR_OID+'31',
+        'streetAddress': X500ATTR_OID+'9',
+        'dmdName': X500ATTR_OID+'54',
+        'postalCode': X500ATTR_OID+'17',
+        'pseudonym': X500ATTR_OID+'65',
+        'dnQualifier': X500ATTR_OID+'46',
+        'crossCertificatePair': X500ATTR_OID+'40',
+        'eduPersonOrgDN': EDUPERSON_OID+'3',
+        'authorityRevocationList': X500ATTR_OID+'38',
+        'displayName': NETSCAPE_LDAP+'241',
+        'businessCategory': X500ATTR_OID+'15',
+        'serialNumber': X500ATTR_OID+'5',
+        'norEduOrgUniqueIdentifier': NOREDUPERSON_OID+'7',
+        'st': X500ATTR_OID+'8',
+        'carLicense': NETSCAPE_LDAP+'1',
+        'presentationAddress': X500ATTR_OID+'29',
+        'sn': X500ATTR_OID+'4',
+        'domainComponent': UCL_DIR_PILOT+'25',
+        'labeledURI': UMICH+'57',
+        'uid': UCL_DIR_PILOT+'1'
+    }
+}

+ 30 - 0
desktop/libs/huesaml/setup.py

@@ -0,0 +1,30 @@
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+
+from setuptools import setup, find_packages
+
+setup(
+      name = "huesaml",
+      version = "3.0.0",
+      url = 'http://github.com/cloudera/hue',
+      description = "SAML Libraries",
+      packages = find_packages('src'),
+      package_dir = {'': 'src' },
+      install_requires = ['setuptools', 'desktop'],
+      # Even libraries need to be registered as desktop_apps,
+      # if they have configuration, like this one.
+      entry_points = { 'desktop.sdk.lib': 'huesaml=huesaml' },
+)

+ 0 - 0
desktop/libs/huesaml/src/huesaml/__init__.py


+ 75 - 0
desktop/libs/huesaml/src/huesaml/backend.py

@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+"""
+See desktop/auth/backend.py
+"""
+import logging
+from django.contrib.auth.models import User
+from djangosaml2.backends import Saml2Backend as _Saml2Backend
+from desktop.auth.backend import rewrite_user
+from useradmin.models import get_profile, get_default_user_group, UserProfile
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SAML2Backend(_Saml2Backend):
+  """
+  Wrapper around djangosaml2 backend.
+  """
+  def update_user(self, user, attributes, attribute_mapping, force_save=False):
+    # Do this check up here, because the auth call creates a django user upon first login per user
+    is_super = False
+    if not UserProfile.objects.filter(creation_method=str(UserProfile.CreationMethod.EXTERNAL)).exists():
+      # If there are no LDAP users already in the system, the first one will
+      # become a superuser
+      is_super = True
+    elif User.objects.filter(username=user.username).exists():
+      # If the user already exists, we shouldn't change its superuser
+      # privileges. However, if there's a naming conflict with a non-external
+      # user, we should do the safe thing and turn off superuser privs.
+      user = User.objects.get(username=user.username)
+      existing_profile = get_profile(user)
+      if existing_profile.creation_method == str(UserProfile.CreationMethod.EXTERNAL):
+        is_super = user.is_superuser
+
+    user = super(SAML2Backend, self).update_user(user, attributes, attribute_mapping, force_save)
+
+    if user is not None and user.is_active:
+      profile = get_profile(user)
+      profile.creation_method = UserProfile.CreationMethod.EXTERNAL
+      profile.save()
+      user.is_superuser = is_super
+      user = rewrite_user(user)
+
+      default_group = get_default_user_group()
+      if default_group is not None:
+        user.groups.add(default_group)
+        user.save()
+
+      return user
+
+    return None
+
+  def get_user(self, user_id):
+    user = super(SAML2Backend, self).get_user(user_id)
+    user = rewrite_user(user)
+    return user
+
+  @classmethod
+  def manages_passwords_externally(cls):
+    return True

+ 122 - 0
desktop/libs/huesaml/src/huesaml/conf.py

@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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 os
+
+from django.utils.translation import ugettext_lazy as _t
+
+from desktop.lib.conf import Config, coerce_bool
+
+
+BASEDIR = os.path.dirname(os.path.abspath(__file__))
+
+
+def csv(value):
+  if isinstance(value, str):
+    return value.split(',')
+  elif isinstance(value, list):
+    return value
+  return None
+
+def dict_list_map(value):
+  if isinstance(value, str):
+    d = json.loads(value)
+    return {k:(v,) for k, v in d.iteritems()}
+  elif isinstance(value, dict):
+    return value
+  return None
+
+
+XMLSEC_BINARY = Config(
+  key="xmlsec_binary",
+  default="/usr/local/bin/xmlsec1",
+  type=str,
+  help=_t("Xmlsec1 binary path. This program should be executable by the user running Hue."))
+
+CREATE_USERS_ON_LOGIN = Config(
+  key="create_users_on_login",
+  default=True,
+  type=coerce_bool,
+  help=_t("Create users from IdP on login."))
+
+ENTITY_ID = Config(
+  key="entity_id",
+  default="http://localhost:8888/saml2/metadata/",
+  type=str,
+  help=_t("Globally unique identifier of the entity."))
+
+ATTRIBUTE_MAP_DIR = Config(
+  key="attribute_map_dir",
+  default=os.path.abspath( os.path.join(BASEDIR, '..', '..', 'attribute-maps') ),
+  type=str,
+  private=True,
+  help=_t("Attribute map directory contains files that map SAML attributes to pysaml2 attributes."))
+
+ASSERTION_CONSUMER_SERVICE_URI = Config(
+  key="assertion_consumer_service_uri",
+  default="http://localhost:8888/saml2/acs/",
+  type=str,
+  help=_t("Consumes assertions sent back from IdP."))
+
+SINGLE_LOGOUT_SERVICE = Config(
+  key="single_logout_service",
+  default="http://localhost:8888/saml2/ls/",
+  type=str,
+  help=_t("Logout using the IdP."))
+
+ALLOW_UNSOLICITED = Config(
+  key="allow_unsolicited",
+  default=True,
+  type=coerce_bool,
+  private=True,
+  help=_t("Allow imperfect responses."))
+
+REQUIRED_ATTRIBUTES = Config(
+  key="required_attributes",
+  default=[],
+  type=csv,
+  help=_t("Required attributes to ask for from IdP."))
+
+OPTIONAL_ATTRIBUTES = Config(
+  key="optional_attributes",
+  default=[],
+  type=csv,
+  help=_t("Optional attributes to ask for from IdP."))
+
+METADATA_FILE = Config(
+  key="metadata_file",
+  default=os.path.abspath( os.path.join(BASEDIR, '..', '..', 'examples', 'idp.xml') ),
+  type=str,
+  help=_t("IdP metadata in the form of a file. This is generally an XML file containing metadata that the Identity Provider generates."))
+
+KEY_FILE = Config(
+  key="key_file",
+  default=os.path.abspath( os.path.join(BASEDIR, '..', '..', 'examples', 'key.pem') ),
+  type=str,
+  help=_t("key_file is the name of a PEM formatted file that contains the private key of the Hue service. This is presently used both to encrypt/sign assertions and as client key in a HTTPS session."))
+
+CERT_FILE = Config(
+  key="cert_file",
+  default=os.path.abspath( os.path.join(BASEDIR, '..', '..', 'examples', 'cert.pem') ),
+  type=str,
+  help=_t("This is the public part of the service private/public key pair. cert_file must be a PEM formatted certificate chain file."))
+
+USER_ATTRIBUTE_MAPPING = Config(
+  key="user_attribute_mapping",
+  default={'uid': ('username', )},
+  type=dict_list_map,
+  help=_t("A mapping from attributes in the response from the IdP to django user attributes."))

+ 74 - 0
desktop/libs/huesaml/src/huesaml/saml_settings.py

@@ -0,0 +1,74 @@
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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 saml2
+import huesaml.conf
+
+__all__ = ['SAML_CONFIG', 'SAML_ATTRIBUTE_MAPPING', 'SAML_CREATE_UNKNOWN_USER']
+
+
+SAML_CONFIG = {
+  # full path to the xmlsec1 binary programm
+  'xmlsec_binary': huesaml.conf.XMLSEC_BINARY.get(),
+
+  # your entity id, usually your subdomain plus the url to the metadata view
+  'entityid': huesaml.conf.ENTITY_ID.get(),
+
+  # directory with attribute mapping
+  'attribute_map_dir': huesaml.conf.ATTRIBUTE_MAP_DIR.get(),
+
+  # this block states what services we provide
+  'service': {
+    'sp' : {
+      'name': 'example',
+      'endpoints': {
+        # url and binding to the assetion consumer service view
+        # do not change the binding or service name
+        'assertion_consumer_service': [
+          (huesaml.conf.ASSERTION_CONSUMER_SERVICE_URI.get(), saml2.BINDING_HTTP_POST),
+        ],
+        # url and binding to the single logout service view
+        # do not change the binding or service name
+        'single_logout_service': [
+          (huesaml.conf.SINGLE_LOGOUT_SERVICE_URI.get(), saml2.BINDING_HTTP_REDIRECT),
+        ],
+      },
+
+      'allow_unsolicited': huesaml.conf.ALLOW_UNSOLICITED.get(),
+
+      # attributes that this project need to identify a user
+      'required_attributes': huesaml.conf.REQUIRED_ATTRIBUTES.get(),
+
+      # attributes that may be useful to have but not required
+      'optional_attributes': huesaml.conf.OPTIONAL_ATTRIBUTES.get(),
+    },
+  },
+
+  # where the remote metadata is stored
+  'metadata': {
+    'local': huesaml.conf.METADATA_FILE.get(),
+  },
+
+  # set to 1 to output debugging information
+  'debug': 1,
+
+  # certificate
+  'key_file': huesaml.conf.KEY_FILE.get(),
+  'cert_file': huesaml.conf.CERT_FILE.get()
+}
+
+SAML_ATTRIBUTE_MAPPING = huesaml.conf.USER_ATTRIBUTE_MAPPING.get()
+SAML_CREATE_UNKNOWN_USER = huesaml.conf.CREATE_USERS_ON_LOGIN.get()

+ 30 - 0
desktop/libs/huesaml/src/huesaml/urls.py

@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+
+from django.conf.urls.defaults import patterns, handler500, url
+
+urlpatterns = patterns(
+    'djangosaml2.views',
+    url(r'^logout/$', 'logout', name='saml2_logout'),
+    url(r'^ls/$', 'logout_service', name='saml2_ls')
+)
+
+urlpatterns += patterns('huesaml.views',
+                        url(r'^acs/$', 'assertion_consumer_service', name='saml2_acs'),
+                        url(r'^login/$', 'login', name='saml2_login'),
+                        url(r'^metadata/$', 'metadata', name='saml2_metadata'),
+                        url(r'^test/$', 'echo_attributes'))

+ 27 - 0
desktop/libs/huesaml/src/huesaml/views.py

@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# 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.
+
+from djangosaml2.views import login, echo_attributes, metadata, assertion_consumer_service
+
+
+__all__ = ['login', 'echo_attributes']
+
+
+setattr(login, 'login_notrequired', True)
+setattr(echo_attributes, 'login_notrequired', True)
+setattr(assertion_consumer_service, 'login_notrequired', True)
+setattr(metadata, 'login_notrequired', True)