Add detection for users from trusted/invalid realms

When user from other realm than FreeIPA's tries to use Web UI
(login via forms-based auth or with valid trusted realm ticket),
the 401 Unauthorized error with X-Ipa-Rejection-Reason=denied
is returned.

Also, the support for usernames of the form user@SERVER.REALM
or user@server.realm was added.

https://fedorahosted.org/freeipa/ticket/3252
This commit is contained in:
Tomas Babej
2012-12-06 10:34:23 -05:00
committed by Rob Crittenden
parent 152585e731
commit 0292ebd1e5
4 changed files with 55 additions and 18 deletions
+15
View File
@@ -105,6 +105,21 @@ def validate_host_dns(log, fqdn):
) )
raise errors.DNSNotARecordError() raise errors.DNSNotARecordError()
def normalize_name(name):
result = dict()
components = name.split('@')
if len(components) == 2:
result['domain'] = unicode(components[1]).lower()
result['name'] = unicode(components[0]).lower()
else:
components = name.split('\\')
if len(components) == 2:
result['flatname'] = unicode(components[0]).lower()
result['name'] = unicode(components[1]).lower()
else:
result['name'] = unicode(name).lower()
return result
def isvalid_base64(data): def isvalid_base64(data):
""" """
Validate the incoming data as valid base64 data or not. Validate the incoming data as valid base64 data or not.
+3 -16
View File
@@ -31,6 +31,7 @@ from ipapython import ipautil
from ipapython.ipa_log_manager import * from ipapython.ipa_log_manager import *
from ipapython.dn import DN from ipapython.dn import DN
from ipaserver.install import installutils from ipaserver.install import installutils
from ipalib.util import normalize_name
import os, string, struct, copy import os, string, struct, copy
import uuid import uuid
@@ -184,21 +185,6 @@ class DomainValidator(object):
return True return True
return False return False
def normalize_name(self, name):
result = dict()
components = name.split('@')
if len(components) == 2:
result['domain'] = unicode(components[1]).lower()
result['name'] = unicode(components[0]).lower()
else:
components = name.split('\\')
if len(components) == 2:
result['flatname'] = unicode(components[0]).lower()
result['name'] = unicode(components[1]).lower()
else:
result['name'] = unicode(name).lower()
return result
def get_sid_trusted_domain_object(self, object_name): def get_sid_trusted_domain_object(self, object_name):
"""Returns SID for the trusted domain object (user or group only)""" """Returns SID for the trusted domain object (user or group only)"""
if not self.domain: if not self.domain:
@@ -209,7 +195,8 @@ class DomainValidator(object):
if len(self._domains) == 0: if len(self._domains) == 0:
# Our domain is configured but no trusted domains are configured # Our domain is configured but no trusted domains are configured
return None return None
components = self.normalize_name(object_name)
components = normalize_name(object_name)
if not ('domain' in components or 'flatname' in components): if not ('domain' in components or 'flatname' in components):
# No domain or realm specified, ambiguous search # No domain or realm specified, ambiguous search
return False return False
+2
View File
@@ -727,6 +727,8 @@ class ldap2(CrudBackend):
except _ldap.SERVER_DOWN: except _ldap.SERVER_DOWN:
raise NetworkError(uri=self.ldap_uri, raise NetworkError(uri=self.ldap_uri,
error=u'LDAP Server Down') error=u'LDAP Server Down')
except _ldap.LOCAL_ERROR:
raise errors.ACIError(info=info)
except _ldap.SUCCESS: except _ldap.SUCCESS:
pass pass
except _ldap.LDAPError, e: except _ldap.LDAPError, e:
+35 -2
View File
@@ -40,7 +40,7 @@ from ipalib.backend import Executioner
from ipalib.errors import PublicError, InternalError, CommandError, JSONError, ConversionError, CCacheError, RefererError, InvalidSessionPassword, NotFound, ACIError, ExecutionError from ipalib.errors import PublicError, InternalError, CommandError, JSONError, ConversionError, CCacheError, RefererError, InvalidSessionPassword, NotFound, ACIError, ExecutionError
from ipalib.request import context, Connection, destroy_context from ipalib.request import context, Connection, destroy_context
from ipalib.rpc import xml_dumps, xml_loads from ipalib.rpc import xml_dumps, xml_loads
from ipalib.util import parse_time_duration from ipalib.util import parse_time_duration, normalize_name
from ipapython.dn import DN from ipapython.dn import DN
from ipaserver.plugins.ldap2 import ldap2 from ipaserver.plugins.ldap2 import ldap2
from ipapython.compat import json from ipapython.compat import json
@@ -809,7 +809,11 @@ class jsonserver_session(jsonserver, KerberosSession):
# Store the session data in the per-thread context # Store the session data in the per-thread context
setattr(context, 'session_data', session_data) setattr(context, 'session_data', session_data)
self.create_context(ccache=ipa_ccache_name) # This may fail if a ticket from wrong realm was handled via browser
try:
self.create_context(ccache=ipa_ccache_name)
except ACIError, e:
return self.unauthorized(environ, start_response, str(e), 'denied')
try: try:
response = super(jsonserver_session, self).__call__(environ, start_response) response = super(jsonserver_session, self).__call__(environ, start_response)
@@ -927,6 +931,35 @@ class login_password(Backend, KerberosSession, HTTP_Status):
else: else:
return self.bad_request(environ, start_response, "no user specified") 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) password = query_dict.get('password', None)
if password is not None: if password is not None:
if len(password) == 1: if len(password) == 1: