mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2026-09-03 20:52:56 -05:00
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:
committed by
Rob Crittenden
parent
152585e731
commit
0292ebd1e5
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user