def get_trusted_domain_object_sid(self, object_name): result = pysss_nss_idmap.getsidbyname(object_name) if object_name in result and (pysss_nss_idmap.SID_KEY in result[object_name]): object_sid = result[object_name][pysss_nss_idmap.SID_KEY] return object_sid # Else, we are going to contact AD DC LDAP components = normalize_name(object_name) if not ('domain' in components or 'flatname' in components): # No domain or realm specified, ambiguous search raise errors.ValidationError(name=_('trusted domain object'), error= _('Ambiguous search, user domain was not specified')) attrs = ['objectSid'] filter = '(&(sAMAccountName=%(name)s)(|(objectClass=user)(objectClass=group)))' \ % dict(name=components['name']) scope = _ldap.SCOPE_SUBTREE entries = self.get_trusted_domain_objects(components.get('domain'), components.get('flatname'), filter, attrs, scope) if len(entries) > 1: # Treat non-unique entries as invalid raise errors.ValidationError(name=_('trusted domain object'), error= _('Trusted domain did not return a unique object')) sid = self.__sid_to_str(entries[0][1]['objectSid'][0]) try: test_sid = security.dom_sid(sid) return unicode(test_sid) except TypeError, e: raise errors.ValidationError(name=_('trusted domain object'), error= _('Trusted domain did not return a valid SID for the object'))
def get_sid_trusted_domain_object(self, object_name): """Returns SID for the trusted domain object (user or group only)""" if not self.domain: # our domain is not configured or self.is_configured() never run return None if not self._domains: self._domains = self.get_trusted_domains() if len(self._domains) == 0: # Our domain is configured but no trusted domains are configured return None components = normalize_name(object_name) if not ('domain' in components or 'flatname' in components): # No domain or realm specified, ambiguous search return False entry = None if 'domain' in components and components['domain'] in self._domains: # Now we have a name to check against our list of trusted domains entry = self.resolve_against_gc(components['domain'], components['name']) elif 'flatname' in components: # Flatname was specified, traverse through the list of trusted # domains first to find the proper one for domain in self._domains: if self._domains[domain][0] == components['flatname']: entry = self.resolve_against_gc(domain, components['name']) if entry: break if entry: try: test_sid = security.dom_sid(entry) return unicode(test_sid) except TypeError, e: return False
def __get_trusted_domain_user_and_groups(self, object_name): """ Returns a tuple with user SID and a list of SIDs of all groups he is a member of. LIMITATIONS: - only Trusted Admins group members can use this function as it uses secret for IPA-Trusted domain link - List of group SIDs does not contain group memberships outside of the trusted domain """ components = normalize_name(object_name) domain = components.get('domain') flatname = components.get('flatname') name = components.get('name') is_valid_sid = is_sid_valid(object_name) if is_valid_sid: # Find a trusted domain for the SID domain = self.get_domain_by_sid(object_name) # Now search a trusted domain for a user with this SID attrs = ['cn'] filter = '(&(objectClass=user)(objectSid=%(sid)s))' \ % dict(sid=object_name) try: entries = self.get_trusted_domain_objects(domain=domain, filter=filter, attrs=attrs, scope=_ldap.SCOPE_SUBTREE) except errors.NotFound: raise errors.NotFound(reason=_('trusted domain user not found')) user_dn = entries[0][0] elif domain or flatname: attrs = ['cn'] filter = '(&(sAMAccountName=%(name)s)(objectClass=user))' \ % dict(name=name) try: entries = self.get_trusted_domain_objects(domain, flatname, filter, attrs, _ldap.SCOPE_SUBTREE) except errors.NotFound: raise errors.NotFound(reason=_('trusted domain user not found')) user_dn = entries[0][0] else: # No domain or realm specified, ambiguous search raise errors.ValidationError(name=_('trusted domain object'), error= _('Ambiguous search, user domain was not specified')) # Get SIDs of user object and it's groups # tokenGroups attribute must be read with a scope BASE for a known user # distinguished name to avoid search error attrs = ['objectSID', 'tokenGroups'] filter = "(objectClass=user)" entries = self.get_trusted_domain_objects(domain, flatname, filter, attrs, _ldap.SCOPE_BASE, user_dn) object_sid = self.__sid_to_str(entries[0][1]['objectSid'][0]) group_sids = [self.__sid_to_str(sid) for sid in entries[0][1]['tokenGroups']] return (object_sid, group_sids)
def __call__(self, environ, start_response): self.debug('WSGI login_password.__call__:') # Get the user and password parameters from the request content_type = environ.get('CONTENT_TYPE', '').lower() if not content_type.startswith('application/x-www-form-urlencoded'): return self.bad_request(environ, start_response, "Content-Type must be application/x-www-form-urlencoded") method = environ.get('REQUEST_METHOD', '').upper() if method == 'POST': query_string = read_input(environ) else: return self.bad_request(environ, start_response, "HTTP request method must be POST") try: query_dict = urlparse.parse_qs(query_string) except Exception as e: return self.bad_request(environ, start_response, "cannot parse query data") user = query_dict.get('user', None) if user is not None: if len(user) == 1: user = user[0] else: return self.bad_request(environ, start_response, "more than one user parameter") else: return self.bad_request(environ, start_response, "no user specified") # allows login in the form user@SERVER_REALM or user@server_realm # FIXME: uppercasing may be removed when better handling of UPN # is introduced parts = normalize_name(user) if "domain" in parts: # username is of the form user@SERVER_REALM or user@server_realm # check whether the realm is server's realm # Users from other realms are not supported # (they do not have necessary LDAP entry, LDAP connect will fail) if parts["domain"].upper()==self.api.env.realm: user=parts["name"] else: return self.unauthorized(environ, start_response, '', 'denied') elif "flatname" in parts: # username is of the form NetBIOS\user return self.unauthorized(environ, start_response, '', 'denied') else: # username is of the form user or of some wild form, e.g. # user@REALM1@REALM2 or NetBIOS1\NetBIOS2\user (see normalize_name) # wild form username will fail at kinit, so nothing needs to be done pass password = query_dict.get('password', None) if password is not None: if len(password) == 1: password = password[0] else: return self.bad_request(environ, start_response, "more than one password parameter") else: return self.bad_request(environ, start_response, "no password specified") # Get the ccache we'll use and attempt to get credentials in it with user,password ipa_ccache_name = get_ipa_ccache_name() try: self.kinit(user, self.api.env.realm, password, ipa_ccache_name) except PasswordExpired as e: return self.unauthorized(environ, start_response, str(e), 'password-expired') except InvalidSessionPassword as e: return self.unauthorized(environ, start_response, str(e), 'invalid-password') return self.finalize_kerberos_acquisition('login_password', ipa_ccache_name, environ, start_response)
def execute(self, *args, **options): # First receive all needed information: # 1. HBAC rules (whether enabled or disabled) # 2. Required options are (user, target host, service) # 3. Options: rules to test (--rules, --enabled, --disabled), request for detail output rules = [] # Use all enabled IPA rules by default all_enabled = True all_disabled = False # We need a local copy of test rules in order find incorrect ones testrules = {} if 'rules' in options: testrules = list(options['rules']) # When explicit rules are provided, disable assumptions all_enabled = False all_disabled = False sizelimit = None if 'sizelimit' in options: sizelimit = int(options['sizelimit']) # Check if --disabled is specified, include all disabled IPA rules if options['disabled']: all_disabled = True all_enabled = False # Finally, if enabled is specified implicitly, override above decisions if options['enabled']: all_enabled = True hbacset = [] if len(testrules) == 0: hbacset = self.api.Command.hbacrule_find( sizelimit=sizelimit, no_members=False)['result'] else: for rule in testrules: try: hbacset.append(self.api.Command.hbacrule_show(rule)['result']) except Exception: pass # We have some rules, import them # --enabled will import all enabled rules (default) # --disabled will import all disabled rules # --rules will implicitly add the rules from a rule list for rule in hbacset: ipa_rule = _convert_to_ipa_rule(rule) if ipa_rule.name in testrules: ipa_rule.enabled = True rules.append(ipa_rule) testrules.remove(ipa_rule.name) elif all_enabled and ipa_rule.enabled: # Option --enabled forces to include all enabled IPA rules into test rules.append(ipa_rule) elif all_disabled and not ipa_rule.enabled: # Option --disabled forces to include all disabled IPA rules into test ipa_rule.enabled = True rules.append(ipa_rule) # Check if there are unresolved rules left if len(testrules) > 0: # Error, unresolved rules are left in --rules return {'summary' : unicode(_(u'Unresolved rules in --rules')), 'error': testrules, 'matched': None, 'notmatched': None, 'warning' : None, 'value' : False} # Rules are converted to pyhbac format, build request and then test it request = pyhbac.HbacRequest() if options['user'] != u'all': # check first if this is not a trusted domain user if _dcerpc_bindings_installed: is_valid_sid = ipaserver.dcerpc.is_sid_valid(options['user']) else: is_valid_sid = False components = util.normalize_name(options['user']) if is_valid_sid or 'domain' in components or 'flatname' in components: # this is a trusted domain user if not _dcerpc_bindings_installed: raise errors.NotFound(reason=_( 'Cannot perform external member validation without ' 'Samba 4 support installed. Make sure you have installed ' 'server-trust-ad sub-package of IPA on the server')) domain_validator = ipaserver.dcerpc.DomainValidator(self.api) if not domain_validator.is_configured(): raise errors.NotFound(reason=_( 'Cannot search in trusted domains without own domain configured. ' 'Make sure you have run ipa-adtrust-install on the IPA server first')) user_sid, group_sids = domain_validator.get_trusted_domain_user_and_groups(options['user']) request.user.name = user_sid # Now search for all external groups that have this user or # any of its groups in its external members. Found entires # memberOf links will be then used to gather all groups where # this group is assigned, including the nested ones filter_sids = "(&(objectclass=ipaexternalgroup)(|(ipaExternalMember=%s)))" \ % ")(ipaExternalMember=".join(group_sids + [user_sid]) ldap = self.api.Backend.ldap2 group_container = DN(api.env.container_group, api.env.basedn) try: entries, _truncated = ldap.find_entries( filter_sids, ['memberof'], group_container) except errors.NotFound: request.user.groups = [] else: groups = [] for entry in entries: memberof_dns = entry.get('memberof', []) for memberof_dn in memberof_dns: if memberof_dn.endswith(group_container): groups.append(memberof_dn[0][0].value) request.user.groups = sorted(set(groups)) else: # try searching for a local user try: request.user.name = options['user'] search_result = self.api.Command.user_show(request.user.name)['result'] groups = search_result['memberof_group'] if 'memberofindirect_group' in search_result: groups += search_result['memberofindirect_group'] request.user.groups = sorted(set(groups)) except Exception: pass if options['service'] != u'all': try: request.service.name = options['service'] service_result = self.api.Command.hbacsvc_show(request.service.name)['result'] if 'memberof_hbacsvcgroup' in service_result: request.service.groups = service_result['memberof_hbacsvcgroup'] except Exception: pass if options['targethost'] != u'all': try: request.targethost.name = self.canonicalize(options['targethost']) tgthost_result = self.api.Command.host_show(request.targethost.name)['result'] groups = tgthost_result['memberof_hostgroup'] if 'memberofindirect_hostgroup' in tgthost_result: groups += tgthost_result['memberofindirect_hostgroup'] request.targethost.groups = sorted(set(groups)) except Exception: pass matched_rules = [] notmatched_rules = [] error_rules = [] warning_rules = [] result = {'warning':None, 'matched':None, 'notmatched':None, 'error':None} if not options['nodetail']: # Validate runs rules one-by-one and reports failed ones for ipa_rule in rules: try: res = request.evaluate([ipa_rule]) if res == pyhbac.HBAC_EVAL_ALLOW: matched_rules.append(ipa_rule.name) if res == pyhbac.HBAC_EVAL_DENY: notmatched_rules.append(ipa_rule.name) except pyhbac.HbacError as e: code, rule_name = e.args if code == pyhbac.HBAC_EVAL_ERROR: error_rules.append(rule_name) self.log.info('Native IPA HBAC rule "%s" parsing error: %s' % \ (rule_name, pyhbac.hbac_result_string(code))) except (TypeError, IOError) as info: self.log.error('Native IPA HBAC module error: %s' % info) access_granted = len(matched_rules) > 0 else: res = request.evaluate(rules) access_granted = (res == pyhbac.HBAC_EVAL_ALLOW) result['summary'] = _('Access granted: %s') % (access_granted) if len(matched_rules) > 0: result['matched'] = matched_rules if len(notmatched_rules) > 0: result['notmatched'] = notmatched_rules if len(error_rules) > 0: result['error'] = error_rules if len(warning_rules) > 0: result['warning'] = warning_rules result['value'] = access_granted return result
def __call__(self, environ, start_response): self.debug('WSGI login_password.__call__:') # Get the user and password parameters from the request content_type = environ.get('CONTENT_TYPE', '').lower() if not content_type.startswith('application/x-www-form-urlencoded'): return self.bad_request( environ, start_response, "Content-Type must be application/x-www-form-urlencoded") method = environ.get('REQUEST_METHOD', '').upper() if method == 'POST': query_string = read_input(environ) else: return self.bad_request(environ, start_response, "HTTP request method must be POST") try: query_dict = parse_qs(query_string) except Exception as e: return self.bad_request(environ, start_response, "cannot parse query data") user = query_dict.get('user', None) if user is not None: if len(user) == 1: user = user[0] else: return self.bad_request(environ, start_response, "more than one user parameter") else: return self.bad_request(environ, start_response, "no user specified") # allows login in the form user@SERVER_REALM or user@server_realm # FIXME: uppercasing may be removed when better handling of UPN # is introduced parts = normalize_name(user) if "domain" in parts: # username is of the form user@SERVER_REALM or user@server_realm # check whether the realm is server's realm # Users from other realms are not supported # (they do not have necessary LDAP entry, LDAP connect will fail) if parts["domain"].upper() == self.api.env.realm: user = parts["name"] else: return self.unauthorized(environ, start_response, '', 'denied') elif "flatname" in parts: # username is of the form NetBIOS\user return self.unauthorized(environ, start_response, '', 'denied') else: # username is of the form user or of some wild form, e.g. # user@REALM1@REALM2 or NetBIOS1\NetBIOS2\user (see normalize_name) # wild form username will fail at kinit, so nothing needs to be done pass password = query_dict.get('password', None) if password is not None: if len(password) == 1: password = password[0] else: return self.bad_request(environ, start_response, "more than one password parameter") else: return self.bad_request(environ, start_response, "no password specified") # Get the ccache we'll use and attempt to get credentials in it with user,password ipa_ccache_name = get_ipa_ccache_name() try: self.kinit(user, self.api.env.realm, password, ipa_ccache_name) except PasswordExpired as e: return self.unauthorized(environ, start_response, str(e), 'password-expired') except InvalidSessionPassword as e: return self.unauthorized(environ, start_response, str(e), 'invalid-password') except KrbPrincipalExpired as e: return self.unauthorized(environ, start_response, str(e), 'krbprincipal-expired') except UserLocked as e: return self.unauthorized(environ, start_response, str(e), 'user-locked') return self.finalize_kerberos_acquisition('login_password', ipa_ccache_name, environ, start_response)
def execute(self, *args, **options): # First receive all needed information: # 1. HBAC rules (whether enabled or disabled) # 2. Required options are (user, target host, service) # 3. Options: rules to test (--rules, --enabled, --disabled), request for detail output rules = [] # Use all enabled IPA rules by default all_enabled = True all_disabled = False # We need a local copy of test rules in order find incorrect ones testrules = {} if 'rules' in options: testrules = list(options['rules']) # When explicit rules are provided, disable assumptions all_enabled = False all_disabled = False sizelimit = None if 'sizelimit' in options: sizelimit = int(options['sizelimit']) # Check if --disabled is specified, include all disabled IPA rules if options['disabled']: all_disabled = True all_enabled = False # Finally, if enabled is specified implicitly, override above decisions if options['enabled']: all_enabled = True hbacset = [] if len(testrules) == 0: hbacset = self.api.Command.hbacrule_find( sizelimit=sizelimit, no_members=False)['result'] else: for rule in testrules: try: hbacset.append(self.api.Command.hbacrule_show(rule)['result']) except Exception: pass # We have some rules, import them # --enabled will import all enabled rules (default) # --disabled will import all disabled rules # --rules will implicitly add the rules from a rule list for rule in hbacset: ipa_rule = convert_to_ipa_rule(rule) if ipa_rule.name in testrules: ipa_rule.enabled = True rules.append(ipa_rule) testrules.remove(ipa_rule.name) elif all_enabled and ipa_rule.enabled: # Option --enabled forces to include all enabled IPA rules into test rules.append(ipa_rule) elif all_disabled and not ipa_rule.enabled: # Option --disabled forces to include all disabled IPA rules into test ipa_rule.enabled = True rules.append(ipa_rule) # Check if there are unresolved rules left if len(testrules) > 0: # Error, unresolved rules are left in --rules return {'summary' : unicode(_(u'Unresolved rules in --rules')), 'error': testrules, 'matched': None, 'notmatched': None, 'warning' : None, 'value' : False} # Rules are converted to pyhbac format, build request and then test it request = pyhbac.HbacRequest() if options['user'] != u'all': # check first if this is not a trusted domain user if _dcerpc_bindings_installed: is_valid_sid = ipaserver.dcerpc.is_sid_valid(options['user']) else: is_valid_sid = False components = util.normalize_name(options['user']) if is_valid_sid or 'domain' in components or 'flatname' in components: # this is a trusted domain user if not _dcerpc_bindings_installed: raise errors.NotFound(reason=_( 'Cannot perform external member validation without ' 'Samba 4 support installed. Make sure you have installed ' 'server-trust-ad sub-package of IPA on the server')) domain_validator = ipaserver.dcerpc.DomainValidator(self.api) if not domain_validator.is_configured(): raise errors.NotFound(reason=_( 'Cannot search in trusted domains without own domain configured. ' 'Make sure you have run ipa-adtrust-install on the IPA server first')) user_sid, group_sids = domain_validator.get_trusted_domain_user_and_groups(options['user']) request.user.name = user_sid # Now search for all external groups that have this user or # any of its groups in its external members. Found entires # memberOf links will be then used to gather all groups where # this group is assigned, including the nested ones filter_sids = "(&(objectclass=ipaexternalgroup)(|(ipaExternalMember=%s)))" \ % ")(ipaExternalMember=".join(group_sids + [user_sid]) ldap = self.api.Backend.ldap2 group_container = DN(api.env.container_group, api.env.basedn) try: entries, _truncated = ldap.find_entries( filter_sids, ['memberof'], group_container) except errors.NotFound: request.user.groups = [] else: groups = [] for entry in entries: memberof_dns = entry.get('memberof', []) for memberof_dn in memberof_dns: if memberof_dn.endswith(group_container): groups.append(memberof_dn[0][0].value) request.user.groups = sorted(set(groups)) else: # try searching for a local user try: request.user.name = options['user'] search_result = self.api.Command.user_show(request.user.name)['result'] groups = search_result['memberof_group'] if 'memberofindirect_group' in search_result: groups += search_result['memberofindirect_group'] request.user.groups = sorted(set(groups)) except Exception: pass if options['service'] != u'all': try: request.service.name = options['service'] service_result = self.api.Command.hbacsvc_show(request.service.name)['result'] if 'memberof_hbacsvcgroup' in service_result: request.service.groups = service_result['memberof_hbacsvcgroup'] except Exception: pass if options['targethost'] != u'all': try: request.targethost.name = self.canonicalize(options['targethost']) tgthost_result = self.api.Command.host_show(request.targethost.name)['result'] groups = tgthost_result['memberof_hostgroup'] if 'memberofindirect_hostgroup' in tgthost_result: groups += tgthost_result['memberofindirect_hostgroup'] request.targethost.groups = sorted(set(groups)) except Exception: pass matched_rules = [] notmatched_rules = [] error_rules = [] warning_rules = [] result = {'warning':None, 'matched':None, 'notmatched':None, 'error':None} if not options['nodetail']: # Validate runs rules one-by-one and reports failed ones for ipa_rule in rules: try: res = request.evaluate([ipa_rule]) if res == pyhbac.HBAC_EVAL_ALLOW: matched_rules.append(ipa_rule.name) if res == pyhbac.HBAC_EVAL_DENY: notmatched_rules.append(ipa_rule.name) except pyhbac.HbacError as e: code, rule_name = e.args if code == pyhbac.HBAC_EVAL_ERROR: error_rules.append(rule_name) self.log.info('Native IPA HBAC rule "%s" parsing error: %s' % \ (rule_name, pyhbac.hbac_result_string(code))) except (TypeError, IOError) as info: self.log.error('Native IPA HBAC module error: %s' % info) access_granted = len(matched_rules) > 0 else: res = request.evaluate(rules) access_granted = (res == pyhbac.HBAC_EVAL_ALLOW) result['summary'] = _('Access granted: %s') % (access_granted) if len(matched_rules) > 0: result['matched'] = matched_rules if len(notmatched_rules) > 0: result['notmatched'] = notmatched_rules if len(error_rules) > 0: result['error'] = error_rules if len(warning_rules) > 0: result['warning'] = warning_rules result['value'] = access_granted return result
class login_password(Backend, KerberosSession, HTTP_Status): content_type = 'text/plain' key = '/session/login_password' def __init__(self): super(login_password, self).__init__() def _on_finalize(self): super(login_password, self)._on_finalize() self.api.Backend.wsgi_dispatch.mount(self, self.key) self.kerb_session_on_finalize() def __call__(self, environ, start_response): self.debug('WSGI login_password.__call__:') # Get the user and password parameters from the request content_type = environ.get('CONTENT_TYPE', '').lower() if not content_type.startswith('application/x-www-form-urlencoded'): return self.bad_request( environ, start_response, "Content-Type must be application/x-www-form-urlencoded") method = environ.get('REQUEST_METHOD', '').upper() if method == 'POST': query_string = read_input(environ) else: return self.bad_request(environ, start_response, "HTTP request method must be POST") try: query_dict = urlparse.parse_qs(query_string) except Exception, e: return self.bad_request(environ, start_response, "cannot parse query data") user = query_dict.get('user', None) if user is not None: if len(user) == 1: user = user[0] else: return self.bad_request(environ, start_response, "more than one user parameter") else: return self.bad_request(environ, start_response, "no user specified") # allows login in the form user@SERVER_REALM or user@server_realm # FIXME: uppercasing may be removed when better handling of UPN # is introduced parts = normalize_name(user) if "domain" in parts: # username is of the form user@SERVER_REALM or user@server_realm # check whether the realm is server's realm # Users from other realms are not supported # (they do not have necessary LDAP entry, LDAP connect will fail) if parts["domain"].upper() == self.api.env.realm: user = parts["name"] else: return self.unauthorized(environ, start_response, '', 'denied') elif "flatname" in parts: # username is of the form NetBIOS\user return self.unauthorized(environ, start_response, '', 'denied') else: # username is of the form user or of some wild form, e.g. # user@REALM1@REALM2 or NetBIOS1\NetBIOS2\user (see normalize_name) # wild form username will fail at kinit, so nothing needs to be done pass password = query_dict.get('password', None) if password is not None: if len(password) == 1: password = password[0] else: return self.bad_request(environ, start_response, "more than one password parameter") else: return self.bad_request(environ, start_response, "no password specified") # Get the ccache we'll use and attempt to get credentials in it with user,password ipa_ccache_name = get_ipa_ccache_name() reason = 'invalid-password' try: self.kinit(user, self.api.env.realm, password, ipa_ccache_name) except InvalidSessionPassword, e: # Ok, now why is this bad. Is the password simply bad or is the # password expired? try: dn = DN(('uid', user), self.api.env.container_user, self.api.env.basedn) conn = ldap2(shared_instance=False, ldap_uri=self.api.env.ldap_uri) conn.connect(bind_dn=dn, bind_pw=password) # password is ok, must be expired, lets double-check (userdn, entry_attrs) = conn.get_entry(dn, ['krbpasswordexpiration']) if 'krbpasswordexpiration' in entry_attrs: expiration = entry_attrs['krbpasswordexpiration'][0] try: exp = time.strptime(expiration, '%Y%m%d%H%M%SZ') if exp <= time.gmtime(): reason = 'password-expired' except ValueError, v: self.error('Unable to convert %s to a time string' % expiration) except Exception: # It doesn't really matter how we got here but the user's # password is not accepted or the user is unknown. pass finally: if conn.isconnected(): conn.destroy_connection() return self.unauthorized(environ, start_response, str(e), reason)