from six import iteritems
import attr
from cfme.configure.configuration.server_settings import (
AmazonAuthenticationView, LdapAuthenticationView, LdapsAuthenticationView
)
from cfme.exceptions import UnknownProviderType
from cfme.utils.conf import credentials, auth_data
auth_prov_data = auth_data.get("auth_providers", {}) # setup on module import
USER_TYPES = {
'principal': 'User Principal Name',
'email': 'E-mail Address',
'dn-cn': 'Distinguished Name (CN=<user>)',
'dn-uid': 'Distinguished Name (UID=<user>)',
'sam': 'SAM Account Name'
}
LDAP_PORT = 389
LDAPS_PORT = 636
[docs]def auth_provider_types():
"""Fetch the registered classes from entry_points manageiq.auth_provider_categories"""
from pkg_resources import iter_entry_points
return {
ep.name: ep.resolve()
for ep in iter_entry_points('manageiq.auth_provider_types')
}
[docs]def auth_class_from_type(auth_prov_type):
"""Using the registered auth provider classes, fetch a class by its type key
Args:
auth_prov_type: string key matching a registered type in entry_points
Raises:
UnknownProviderType when the given type isn't registered in entry_points
"""
try:
return auth_provider_types()[auth_prov_type]
except KeyError:
raise UnknownProviderType('Unknown auth provider type: {}'.format(auth_prov_type))
[docs]def get_auth_crud(auth_prov_key):
"""Get a BaseAuthProvider derived class with the auth_data.yaml configuration for the key
Args:
auth_prov_key: string key matching one in conf/auth_data.yaml 'auth_providers' dict
Raises:
ValueError if the yaml type for given key doesn't match auth_type on fetched class
"""
auth_prov_config = auth_prov_data[auth_prov_key].copy()
klass = auth_class_from_type(auth_prov_config.get('type'))
if auth_prov_config.get('type') != klass.auth_type:
raise ValueError('{} must have type "{}"'.format(klass.__name__, klass.auth_type))
return klass.from_config(auth_prov_config, auth_prov_key)
[docs]@attr.s
class BaseAuthProvider(object):
"""Base class for authentication provider objects """
auth_type = None
view_class = None
key = attr.ib()
[docs] @classmethod
def from_config(cls, prov_config, prov_key):
"""Returns an object using the passed yaml config
Sets defaults for yaml configured objects separate from attr.ib definitions
"""
prov_config.update(credentials[prov_config.get('credentials')])
class_attrs = [att.name for att in cls.__attrs_attrs__]
class_params = {k: v for k, v in iteritems(prov_config) if k in class_attrs}
return cls(key=prov_key, **class_params)
[docs] def as_fill_value(self):
class_attrs = [att.name for att in self.__attrs_attrs__]
include_attrs = [getattr(self.__class__, name)
for name in self.view_class.cls_widget_names()
if name in class_attrs]
return attr.asdict(self, filter=attr.filters.include(*include_attrs))
[docs]@attr.s
class AmazonAuthProvider(BaseAuthProvider):
"""AWS IAM auth provider"""
auth_type = 'amazon'
view_class = AmazonAuthenticationView
access_key = attr.ib()
secret_key = attr.ib()
get_groups = attr.ib(default=False)
[docs]@attr.s
class MIQAuthProvider(BaseAuthProvider):
"""base class for miq auth providers (ldap/ldaps modes in UI)
Intended to be used for freeipa, AD, openldap and openldaps type providers
"""
host1 = attr.ib()
bind_password = attr.ib() # Ordered to adhere to mandatory attrs sequence
host2 = attr.ib(default=None)
host3 = attr.ib(default=None)
port = attr.ib(default=LDAP_PORT)
user_type = attr.ib(default='principal',
validator=lambda item, attribute, value: value in USER_TYPES.keys())
domain_prefix = attr.ib(default=None)
user_suffix = attr.ib(default=None)
base_dn = attr.ib(default=None)
bind_dn = attr.ib(default=None)
get_groups = attr.ib(default=False)
get_roles = attr.ib(default=False)
follow_referrals = attr.ib(default=False)
# attrs for SSL
domain_name = attr.ib(default=None)
cert_filename = attr.ib(default=None)
cert_filepath = attr.ib(default=None)
ipaddress = attr.ib(default=None)
ldap_conf = attr.ib(default=None)
sssd_conf = attr.ib(default=None)
# TODO as_external_value method
[docs]@attr.s
class OpenLDAPAuthProvider(MIQAuthProvider):
"""openldap auth provider, NO SSL No attributes beyond MIQAuthProvider"""
auth_type = 'openldap'
view_class = LdapAuthenticationView
[docs]@attr.s
class OpenLDAPSAuthProvider(MIQAuthProvider):
"""openldap auth provider, WITH SSL"""
auth_type = 'openldaps'
view_class = LdapsAuthenticationView
[docs]@attr.s
class FreeIPAAuthProvider(MIQAuthProvider):
"""freeipa can be used with ldap auth config or external
For ldap config:
* 3 hosts can be configured
* bind_dn is used for admin user validation
* ipa realm and ipadomain are not part of config
* user_type will use the cfme.utils.auth.USER_TYPES dict
For external config:
* 1 host is configured as --ipaserver
* realm and domain are optional params
* all user type, suffix, base/bind_dn, get_groups/roles/referrals args are not used
"""
auth_type = 'freeipa'
view_class = LdapAuthenticationView # TODO could be SSL view, but ATM no difference in widgets
ipaprincipal = attr.ib(default=None) # ipaprincipal in external
iparealm = attr.ib(default=None) # external only, optional
ipadomain = attr.ib(default=None) # external only, optional
[docs] def as_external_value(self):
"""return a dictionary that can be used with appliance_console_cli.configure_ipa"""
external = dict(
ipaserver=self.host1,
principal=self.ipaprincipal,
password=self.bind_password
)
for att in ['iparealm', 'ipadomain']: # optional args for external config
if getattr(self, att): # only include if set, don't pass key if None
external.update({att: getattr(self, att)})
return external