From 0d3ddef93b0a72b824297e5504e435a4427f14bd Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 3 Jan 2009 02:35:36 -0700 Subject: Started fleshing out reoganization of errors in errors.py (with gettext support) --- ipalib/errors2.py | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 ipalib/errors2.py (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py new file mode 100644 index 00000000..f49c10ef --- /dev/null +++ b/ipalib/errors2.py @@ -0,0 +1,213 @@ +# Authors: +# Jason Gerard DeRose +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty inmsgion +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +Custom exception classes. + +Certain errors can be returned in RPC response to relay some error condition +to the caller. + + ============= ======================================== + Error codes Exceptions + ============= ======================================== + 900 `PublicError` + 901 `InternalError` + 902 - 999 *Reserved for future use* + 1000 - 1999 `AuthenticationError` and its subclasses + 2000 - 2999 `AuthorizationError` and its subclasses + 3000 - 3999 `InvocationError` and its subclasses + 4000 - 4999 `ExecutionError` and its subclasses + 5000 - 5999 `GenericError` and its subclasses + ============= ======================================== +""" + +from inspect import isclass +import request + + +class PrivateError(StandardError): + """ + Base class for exceptions that are *never* returned in an RPC response. + """ + + +class PublicError(StandardError): + """ + **900** Base class for exceptions that can be returned in an RPC response. + """ + + code = 900 + + def __init__(self, message=None, **kw): + self.kw = kw + if message is None: + message = self.get_format(request._) % kw + StandardError.__init__(self, message) + + def get_format(self, _): + return _('') + + + + + +class InternalError(PublicError): + """ + **901** Used to conceal a non-public exception. + """ + + code = 901 + + + +############################################################################## +# 1000 - 1999: Authentication Errors +class AuthenticationError(PublicError): + """ + **1000** Base class for authentication errors (*1000 - 1999*). + """ + + code = 1000 + + + +############################################################################## +# 2000 - 2999: Authorization Errors +class AuthorizationError(PublicError): + """ + **2000** Base class for authorization errors (*2000 - 2999*). + """ + + code = 2000 + + + +############################################################################## +# 3000 - 3999: Invocation Errors + +class InvocationError(PublicError): + """ + **3000** Base class for command invocation errors (*3000 - 3999*). + """ + + code = 3000 + + +class CommandError(InvocationError): + """ + **3001** Raised when an unknown command is called. + """ + + code = 3001 + + def get_format(self, _): + return _('Unknown command %(name)r') + + +class RemoteCommandError(InvocationError): + """ + **3002** Raised when client receives a `CommandError` from server. + """ + + code = 3002 + + +class ArgumentError(InvocationError): + """ + **3003** Raised when a command is called with wrong number of arguments. + """ + + code = 3003 + + +class OptionError(InvocationError): + """ + **3004** Raised when a command is called with unknown options. + """ + + code = 3004 + + +class RequirementError(InvocationError): + """ + **3005** Raised when a required parameter is not provided. + """ + + code = 3005 + + +class ConversionError(InvocationError): + """ + **3006** Raised when a parameter value is the wrong type. + """ + + code = 3006 + + +class ValidationError(InvocationError): + """ + **3007** Raised when a parameter value fails a validation rule. + """ + + code = 3007 + + + +############################################################################## +# 4000 - 4999: Execution Errors + +class ExecutionError(PublicError): + """ + **4000** Base class for execution/operation errors (*4000 - 4999*). + """ + + code = 4000 + + + +############################################################################## +# 5000 - 5999: Generic Errors + +class GenericError(PublicError): + """ + **5000** Errors inappropriate for other categories (*5000 - 5999*). + """ + + code = 5000 + + + +def __errors_iter(): + """ + Iterate through all the `PublicError` subclasses. + """ + for (key, value) in globals().items(): + if key.startswith('_') or not isclass(value): + continue + if issubclass(value, PublicError): + yield value + +public_errors = tuple( + sorted(__errors_iter(), key=lambda E: E.code) +) + +if __name__ == '__main__': + for klass in public_errors: + print '%d\t%s' % (klass.code, klass.__name__) + print '(%d public errors)' % len(public_errors) -- cgit From d1517b95ca14773773647434fb589c8224307328 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 3 Jan 2009 15:35:54 -0700 Subject: Ported errors.SubprocessError to errors2 --- ipalib/errors2.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index f49c10ef..676dd79f 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -46,6 +46,37 @@ class PrivateError(StandardError): Base class for exceptions that are *never* returned in an RPC response. """ + format = '' + + def __init__(self, **kw): + self.message = self.format % kw + for (key, value) in kw.iteritems(): + assert not hasattr(self, key), 'conflicting kwarg %s.%s = %r' % ( + self.__class__.__name__, key, value, + ) + setattr(self, key, value) + StandardError.__init__(self, self.message) + + +class SubprocessError(PrivateError): + """ + Raised when ``subprocess.call()`` returns a non-zero exit status. + + This custom exception is needed because Python 2.4 doesn't have the + ``subprocess.CalledProcessError`` exception (which was added in Python 2.5). + + For example: + + >>> e = SubprocessError(returncode=1, argv=('/bin/false',)) + >>> e.returncode + 1 + >>> e.argv + ('/bin/false',) + >>> str(e) + "return code 1 from ('/bin/false',)" + """ + format = 'return code %(returncode)d from %(argv)r' + class PublicError(StandardError): """ -- cgit From 6b6e6b1cab7a633faf16631a565ecb6988dadb48 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 3 Jan 2009 17:27:53 -0700 Subject: Ported plugin registration errors into errors2.py; plugable.Registrar now raises new errors2 exceptions --- ipalib/errors2.py | 83 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 9 deletions(-) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 676dd79f..7793a914 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -43,7 +43,7 @@ import request class PrivateError(StandardError): """ - Base class for exceptions that are *never* returned in an RPC response. + Base class for exceptions that are *never* forwarded in an RPC response. """ format = '' @@ -72,15 +72,82 @@ class SubprocessError(PrivateError): 1 >>> e.argv ('/bin/false',) + >>> e.message + "return code 1 from ('/bin/false',)" >>> str(e) "return code 1 from ('/bin/false',)" """ + format = 'return code %(returncode)d from %(argv)r' +class PluginSubclassError(PrivateError): + """ + Raised when a plugin doesn't subclass from an allowed base. + + For example: + + >>> raise PluginSubclassError(plugin='bad', bases=('base1', 'base2')) + Traceback (most recent call last): + ... + PluginSubclassError: 'bad' not subclass of any base in ('base1', 'base2') + + """ + + format = '%(plugin)r not subclass of any base in %(bases)r' + + +class PluginDuplicateError(PrivateError): + """ + Raised when the same plugin class is registered more than once. + + For example: + + >>> raise PluginDuplicateError(plugin='my_plugin') + Traceback (most recent call last): + ... + PluginDuplicateError: 'my_plugin' was already registered + """ + + format = '%(plugin)r was already registered' + + +class PluginOverrideError(PrivateError): + """ + Raised when a plugin overrides another without using ``override=True``. + + For example: + + >>> raise PluginOverrideError(base='Command', name='env', plugin='my_env') + Traceback (most recent call last): + ... + PluginOverrideError: unexpected override of Command.env with 'my_env' + """ + + format = 'unexpected override of %(base)s.%(name)s with %(plugin)r' + + +class PluginMissingOverrideError(PrivateError): + """ + Raised when a plugin overrides another that has not been registered. + + For example: + + >>> raise PluginMissingOverrideError(base='Command', name='env', plugin='my_env') + Traceback (most recent call last): + ... + PluginMissingOverrideError: Command.env not registered, cannot override with 'my_env' + """ + + format = '%(base)s.%(name)s not registered, cannot override with %(plugin)r' + + + +############################################################################## +# Public errors: class PublicError(StandardError): """ - **900** Base class for exceptions that can be returned in an RPC response. + **900** Base class for exceptions that can be forwarded in an RPC response. """ code = 900 @@ -96,8 +163,6 @@ class PublicError(StandardError): - - class InternalError(PublicError): """ **901** Used to conceal a non-public exception. @@ -108,7 +173,7 @@ class InternalError(PublicError): ############################################################################## -# 1000 - 1999: Authentication Errors +# 1000 - 1999: Authentication errors class AuthenticationError(PublicError): """ **1000** Base class for authentication errors (*1000 - 1999*). @@ -119,7 +184,7 @@ class AuthenticationError(PublicError): ############################################################################## -# 2000 - 2999: Authorization Errors +# 2000 - 2999: Authorization errors class AuthorizationError(PublicError): """ **2000** Base class for authorization errors (*2000 - 2999*). @@ -130,7 +195,7 @@ class AuthorizationError(PublicError): ############################################################################## -# 3000 - 3999: Invocation Errors +# 3000 - 3999: Invocation errors class InvocationError(PublicError): """ @@ -201,7 +266,7 @@ class ValidationError(InvocationError): ############################################################################## -# 4000 - 4999: Execution Errors +# 4000 - 4999: Execution errors class ExecutionError(PublicError): """ @@ -213,7 +278,7 @@ class ExecutionError(PublicError): ############################################################################## -# 5000 - 5999: Generic Errors +# 5000 - 5999: Generic errors class GenericError(PublicError): """ -- cgit From bb6e9cfe9ff25f3a018b23785f71302911eab435 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sat, 3 Jan 2009 18:02:58 -0700 Subject: Plugin.call() now uses errors2 version of SubprocessError --- ipalib/errors2.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 7793a914..7fd4b9c9 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -67,15 +67,19 @@ class SubprocessError(PrivateError): For example: + >>> raise SubprocessError(returncode=2, argv=('ls', '-lh', '/no-foo/')) + Traceback (most recent call last): + ... + SubprocessError: return code 2 from ('ls', '-lh', '/no-foo/') + + The exit code of the sub-process is available via the ``returncode`` + instance attribute. For example: + >>> e = SubprocessError(returncode=1, argv=('/bin/false',)) >>> e.returncode 1 - >>> e.argv + >>> e.argv # argv is also available ('/bin/false',) - >>> e.message - "return code 1 from ('/bin/false',)" - >>> str(e) - "return code 1 from ('/bin/false',)" """ format = 'return code %(returncode)d from %(argv)r' -- cgit From c161784973fdedb146a4087d8692b157214c4db0 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 4 Jan 2009 00:46:21 -0700 Subject: Added request.ugettext() and request.ungettext() functions; added corresponding unit tests --- ipalib/errors2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 7fd4b9c9..4cb84870 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -38,7 +38,7 @@ to the caller. """ from inspect import isclass -import request +from request import ugettext, ungettext class PrivateError(StandardError): @@ -159,7 +159,7 @@ class PublicError(StandardError): def __init__(self, message=None, **kw): self.kw = kw if message is None: - message = self.get_format(request._) % kw + message = self.get_format() % kw StandardError.__init__(self, message) def get_format(self, _): -- cgit From 2608838ef1f96b0c8d3ff3ed4310eaa63ba73031 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 4 Jan 2009 03:52:08 -0700 Subject: Quite a bit of work on new public errors and their unit tests --- ipalib/errors2.py | 103 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 95 insertions(+), 8 deletions(-) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 4cb84870..5ec97fbe 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -28,7 +28,9 @@ to the caller. ============= ======================================== 900 `PublicError` 901 `InternalError` - 902 - 999 *Reserved for future use* + 902 `RemoteInternalError` + 903 `VersionError` + 904 - 999 *Reserved for future use* 1000 - 1999 `AuthenticationError` and its subclasses 2000 - 2999 `AuthorizationError` and its subclasses 3000 - 3999 `InvocationError` and its subclasses @@ -39,6 +41,7 @@ to the caller. from inspect import isclass from request import ugettext, ungettext +from constants import TYPE_ERROR class PrivateError(StandardError): @@ -157,23 +160,90 @@ class PublicError(StandardError): code = 900 def __init__(self, message=None, **kw): - self.kw = kw if message is None: - message = self.get_format() % kw + message = self.get_format(ugettext) % kw + assert type(message) is unicode + elif type(message) is not unicode: + raise TypeError( + TYPE_ERROR % ('message', unicode, message, type(message)) + ) + self.message = message + for (key, value) in kw.iteritems(): + assert not hasattr(self, key), 'conflicting kwarg %s.%s = %r' % ( + self.__class__.__name__, key, value, + ) + setattr(self, key, value) StandardError.__init__(self, message) def get_format(self, _): return _('') - class InternalError(PublicError): """ **901** Used to conceal a non-public exception. + + For example: + + >>> raise InternalError() + Traceback (most recent call last): + ... + InternalError: an internal error has occured """ code = 901 + def __init__(self, message=None): + """ + Security issue: ignore any information given to constructor. + """ + PublicError.__init__(self, self.get_format(ugettext)) + + def get_format(self, _): + return _('an internal error has occured') + + +class RemoteInternalError(PublicError): + """ + **902** Raised when client catches an `InternalError` from server. + + For example: + + >>> raise RemoteInternalError(uri='http://localhost:8888') + Traceback (most recent call last): + ... + RemoteInternalError: an internal error has occured on server 'http://localhost:8888' + """ + + code = 902 + + def get_format(self, _): + return _('an internal error has occured on server %(uri)r') + + +class VersionError(PublicError): + """ + **903** Raised when client and server versions are incompatible. + + For example: + + >>> raise VersionError(client='2.0', server='2.1', uri='http://localhost:8888') + Traceback (most recent call last): + ... + VersionError: 2.0 client incompatible with 2.1 server at 'http://localhost:8888' + + """ + + code = 903 + + def get_format(self, _): + return _( + '%(client)s client incompatible with %(server)s server at %(uri)r' + ) + + + + ############################################################################## @@ -212,21 +282,38 @@ class InvocationError(PublicError): class CommandError(InvocationError): """ **3001** Raised when an unknown command is called. + + For example: + + >>> raise CommandError(name='foobar') + Traceback (most recent call last): + ... + CommandError: unknown command 'foobar' """ code = 3001 def get_format(self, _): - return _('Unknown command %(name)r') + return _('unknown command %(name)r') class RemoteCommandError(InvocationError): """ - **3002** Raised when client receives a `CommandError` from server. + **3002** Raised when client catches a `CommandError` from server. + + For example: + + >>> raise RemoteCommandError(name='foobar', uri='http://localhost:8888') + Traceback (most recent call last): + ... + RemoteCommandError: command 'foobar' unknown on server 'http://localhost:8888' """ code = 3002 + def get_format(self, _): + return _('command %(name)r unknown on server %(uri)r') + class ArgumentError(InvocationError): """ @@ -254,7 +341,7 @@ class RequirementError(InvocationError): class ConversionError(InvocationError): """ - **3006** Raised when a parameter value is the wrong type. + **3006** Raised when parameter value can't be converted to correct type. """ code = 3006 @@ -286,7 +373,7 @@ class ExecutionError(PublicError): class GenericError(PublicError): """ - **5000** Errors inappropriate for other categories (*5000 - 5999*). + **5000** Base class for errors that don't fit elsewhere (*5000 - 5999*). """ code = 5000 -- cgit From ff66c7ece65a042b645b2cdaef700f143727cc2f Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Sun, 4 Jan 2009 18:20:39 -0700 Subject: Added more public exceptions and did some other cleanup in errors2 --- ipalib/errors2.py | 142 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 44 deletions(-) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 5ec97fbe..51b9a02f 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -27,10 +27,14 @@ to the caller. Error codes Exceptions ============= ======================================== 900 `PublicError` - 901 `InternalError` - 902 `RemoteInternalError` - 903 `VersionError` - 904 - 999 *Reserved for future use* + 901 `VersionError` + 902 `InternalError` + 903 `ServerInternalError` + 904 `CommandError` + 905 `ServerCommandError` + 906 `NetworkError` + 907 `ServerNetworkError` + 908 - 999 *Reserved for future use* 1000 - 1999 `AuthenticationError` and its subclasses 2000 - 2999 `AuthorizationError` and its subclasses 3000 - 3999 `InvocationError` and its subclasses @@ -179,9 +183,30 @@ class PublicError(StandardError): return _('') +class VersionError(PublicError): + """ + **901** Raised when client and server versions are incompatible. + + For example: + + >>> raise VersionError(cver='2.0', sver='2.1', server='https://localhost') + Traceback (most recent call last): + ... + VersionError: 2.0 client incompatible with 2.1 server at 'https://localhost' + + """ + + code = 901 + + def get_format(self, _): + return _( + '%(cver)s client incompatible with %(sver)s server at %(server)r' + ) + + class InternalError(PublicError): """ - **901** Used to conceal a non-public exception. + **902** Raised to conceal a non-public exception. For example: @@ -191,7 +216,7 @@ class InternalError(PublicError): InternalError: an internal error has occured """ - code = 901 + code = 902 def __init__(self, message=None): """ @@ -203,47 +228,96 @@ class InternalError(PublicError): return _('an internal error has occured') -class RemoteInternalError(PublicError): +class ServerInternalError(PublicError): """ - **902** Raised when client catches an `InternalError` from server. + **903** Raised when client catches an `InternalError` from server. For example: - >>> raise RemoteInternalError(uri='http://localhost:8888') + >>> raise ServerInternalError(server='https://localhost') Traceback (most recent call last): ... - RemoteInternalError: an internal error has occured on server 'http://localhost:8888' + ServerInternalError: an internal error has occured on server at 'https://localhost' """ - code = 902 + code = 903 def get_format(self, _): - return _('an internal error has occured on server %(uri)r') + return _('an internal error has occured on server at %(server)r') -class VersionError(PublicError): +class CommandError(PublicError): """ - **903** Raised when client and server versions are incompatible. + **904** Raised when an unknown command is called. For example: - >>> raise VersionError(client='2.0', server='2.1', uri='http://localhost:8888') + >>> raise CommandError(name='foobar') Traceback (most recent call last): ... - VersionError: 2.0 client incompatible with 2.1 server at 'http://localhost:8888' + CommandError: unknown command 'foobar' + """ + + code = 904 + + def get_format(self, _): + return _('unknown command %(name)r') + +class ServerCommandError(PublicError): """ + **905** Raised when client catches a `CommandError` from server. - code = 903 + For example: + + >>> e = CommandError(name='foobar') + >>> raise ServerCommandError(error=e.message, server='https://localhost') + Traceback (most recent call last): + ... + ServerCommandError: error on server 'https://localhost': unknown command 'foobar' + """ + + code = 905 def get_format(self, _): - return _( - '%(client)s client incompatible with %(server)s server at %(uri)r' - ) + return _('error on server %(server)r: %(error)s') + + +class NetworkError(PublicError): + """ + **906** Raised when a network connection cannot be created. + + For example: + + >>> raise NetworkError(uri='ldap://localhost:389') + Traceback (most recent call last): + ... + NetworkError: cannot connect to 'ldap://localhost:389' + """ + code = 906 + def get_format(self, _): + return _('cannot connect to %(uri)r') + + +class ServerNetworkError(PublicError): + """ + **907** Raised when client catches a `NetworkError` from server. + + For example: + + >>> e = NetworkError(uri='ldap://localhost:389') + >>> raise ServerNetworkError(error=e.message, server='https://localhost') + Traceback (most recent call last): + ... + ServerNetworkError: error on server 'https://localhost': cannot connect to 'ldap://localhost:389' + """ + code = 907 + def get_format(self, _): + return _('error on server %(server)r: %(error)s') ############################################################################## @@ -279,41 +353,21 @@ class InvocationError(PublicError): code = 3000 -class CommandError(InvocationError): +class EncodingError(InvocationError): """ - **3001** Raised when an unknown command is called. - - For example: - - >>> raise CommandError(name='foobar') - Traceback (most recent call last): - ... - CommandError: unknown command 'foobar' + **3001** Raised when received text is incorrectly encoded. """ code = 3001 - def get_format(self, _): - return _('unknown command %(name)r') - -class RemoteCommandError(InvocationError): +class BinaryEncodingError(InvocationError): """ - **3002** Raised when client catches a `CommandError` from server. - - For example: - - >>> raise RemoteCommandError(name='foobar', uri='http://localhost:8888') - Traceback (most recent call last): - ... - RemoteCommandError: command 'foobar' unknown on server 'http://localhost:8888' + **3002** Raised when received binary data is incorrectly encoded. """ code = 3002 - def get_format(self, _): - return _('command %(name)r unknown on server %(uri)r') - class ArgumentError(InvocationError): """ -- cgit From f130da56c38bafb3c05c2273fbf01e148ddb2d4a Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 6 Jan 2009 11:15:41 -0700 Subject: Additional work on the new error code tree in errors2.py --- ipalib/errors2.py | 101 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 89 insertions(+), 12 deletions(-) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 51b9a02f..8ae2d440 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -18,29 +18,82 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Custom exception classes. +Custom exception classes (some which are RPC transparent). -Certain errors can be returned in RPC response to relay some error condition -to the caller. +`PrivateError` and its subclasses are custom IPA excetions that will *never* be +forwarded in a Remote Procedure Call (RPC) response. + +On the other hand, `PublicError` and its subclasses can be forwarded in an RPC +response. These public errors each carry a unique integer error code as well as +a gettext translated error message (translated a the time the exception is +raised). The purpose of the public errors is to relay information about +*expected* user errors, service availability errors, and so on. They should +*never* be used for *unexpected* programmatic or run-time errors. + +For security reasons it is *extremely* important that arbitrary exceptions *not* +be forwarded in an RPC response. Unexpected exceptions can easily contain +compromising information in their error messages. Any time the server catches +any exception that isn't a `PublicError` subclass, it should raise an +`InternalError`, which itself always has the same, static error message (and +therefore cannot be populated with information about the true exception). + +The public errors are arranging into five main blocks of error code ranges: ============= ======================================== Error codes Exceptions ============= ======================================== - 900 `PublicError` - 901 `VersionError` - 902 `InternalError` - 903 `ServerInternalError` - 904 `CommandError` - 905 `ServerCommandError` - 906 `NetworkError` - 907 `ServerNetworkError` - 908 - 999 *Reserved for future use* 1000 - 1999 `AuthenticationError` and its subclasses 2000 - 2999 `AuthorizationError` and its subclasses 3000 - 3999 `InvocationError` and its subclasses 4000 - 4999 `ExecutionError` and its subclasses 5000 - 5999 `GenericError` and its subclasses ============= ======================================== + +Within these five blocks some sub-ranges are already allocated for certain types +of error messages, while others are reserved for future use. Here are the +current block assignments: + + - **900-5999** `PublicError` and its subclasses + + - **901 - 907** Assigned to special top-level public errors + + - **908 - 999** *Reserved for future use* + + - **1000 - 1999** `AuthenticationError` and its subclasses + + - **1001 - 1099** Open for general authentication errors + + - **1100 - 1199** `KerberosError` and its subclasses + + - **1200 - 1999** *Reserved for future use* + + - **2000 - 2999** `AuthorizationError` and its subclasses + + - **2001 - 2099** Open for general authorization errors + + - **2100 - 2199** `ACIError` and its subclasses + + - **2200 - 2999** *Reserved for future use* + + - **3000 - 3999** `InvocationError` and its subclasses + + - **3001 - 3099** Open for general invocation errors + + - **3100 - 3199** *Reserved for future use* + + - **4000 - 4999** `ExecutionError` and its subclasses + + - **4001 - 4099** Open for general execution errors + + - **4100 - 4299** `LDAPError` and its subclasses + + - **4300 - 4999** *Reserved for future use* + + - **5000 - 5999** `GenericError` and its subclasses + + - **5001 - 5099** Open for generic errors + + - **5100 - 5999** *Reserved for future use* """ from inspect import isclass @@ -330,6 +383,14 @@ class AuthenticationError(PublicError): code = 1000 +class KerberosError(AuthenticationError): + """ + **1100** Base class for Kerberos authorization errors (*1100 - 1199*). + """ + + code = 1100 + + ############################################################################## # 2000 - 2999: Authorization errors @@ -341,6 +402,14 @@ class AuthorizationError(PublicError): code = 2000 +class ACIError(AuthorizationError): + """ + **2100** Base class for ACI authorization errors (*2100 - 2199*). + """ + + code = 2100 + + ############################################################################## # 3000 - 3999: Invocation errors @@ -421,6 +490,14 @@ class ExecutionError(PublicError): code = 4000 +class LDAPError(ExecutionError): + """ + **4100** Base class for LDAP execution errors (*4100 - 4299*). + """ + + code = 4100 + + ############################################################################## # 5000 - 5999: Generic errors -- cgit From ac89267c2f701e56798de0160abd8f81b190c8ef Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 6 Jan 2009 11:54:58 -0700 Subject: Fixed type in KerberosError, droped LDAPError range to just 100 codes for now --- ipalib/errors2.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 8ae2d440..58568244 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -85,7 +85,7 @@ current block assignments: - **4001 - 4099** Open for general execution errors - - **4100 - 4299** `LDAPError` and its subclasses + - **4100 - 4199** `LDAPError` and its subclasses - **4300 - 4999** *Reserved for future use* @@ -385,7 +385,7 @@ class AuthenticationError(PublicError): class KerberosError(AuthenticationError): """ - **1100** Base class for Kerberos authorization errors (*1100 - 1199*). + **1100** Base class for Kerberos authentication errors (*1100 - 1199*). """ code = 1100 @@ -484,7 +484,7 @@ class ValidationError(InvocationError): class ExecutionError(PublicError): """ - **4000** Base class for execution/operation errors (*4000 - 4999*). + **4000** Base class for execution errors (*4000 - 4999*). """ code = 4000 @@ -492,7 +492,7 @@ class ExecutionError(PublicError): class LDAPError(ExecutionError): """ - **4100** Base class for LDAP execution errors (*4100 - 4299*). + **4100** Base class for LDAP execution errors (*4100 - 4199*). """ code = 4100 -- cgit From 9e430755a5cbcd27e2312d5bee1061704a7215bf Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 6 Jan 2009 13:33:22 -0700 Subject: Renamed PublicError.code attribute to PublicError.errno --- ipalib/errors2.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 58568244..660bb1bd 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -25,7 +25,7 @@ forwarded in a Remote Procedure Call (RPC) response. On the other hand, `PublicError` and its subclasses can be forwarded in an RPC response. These public errors each carry a unique integer error code as well as -a gettext translated error message (translated a the time the exception is +a gettext translated error message (translated at the time the exception is raised). The purpose of the public errors is to relay information about *expected* user errors, service availability errors, and so on. They should *never* be used for *unexpected* programmatic or run-time errors. @@ -214,7 +214,7 @@ class PublicError(StandardError): **900** Base class for exceptions that can be forwarded in an RPC response. """ - code = 900 + errno = 900 def __init__(self, message=None, **kw): if message is None: @@ -249,7 +249,7 @@ class VersionError(PublicError): """ - code = 901 + errno = 901 def get_format(self, _): return _( @@ -269,7 +269,7 @@ class InternalError(PublicError): InternalError: an internal error has occured """ - code = 902 + errno = 902 def __init__(self, message=None): """ @@ -293,7 +293,7 @@ class ServerInternalError(PublicError): ServerInternalError: an internal error has occured on server at 'https://localhost' """ - code = 903 + errno = 903 def get_format(self, _): return _('an internal error has occured on server at %(server)r') @@ -311,7 +311,7 @@ class CommandError(PublicError): CommandError: unknown command 'foobar' """ - code = 904 + errno = 904 def get_format(self, _): return _('unknown command %(name)r') @@ -330,7 +330,7 @@ class ServerCommandError(PublicError): ServerCommandError: error on server 'https://localhost': unknown command 'foobar' """ - code = 905 + errno = 905 def get_format(self, _): return _('error on server %(server)r: %(error)s') @@ -348,7 +348,7 @@ class NetworkError(PublicError): NetworkError: cannot connect to 'ldap://localhost:389' """ - code = 906 + errno = 906 def get_format(self, _): return _('cannot connect to %(uri)r') @@ -367,7 +367,7 @@ class ServerNetworkError(PublicError): ServerNetworkError: error on server 'https://localhost': cannot connect to 'ldap://localhost:389' """ - code = 907 + errno = 907 def get_format(self, _): return _('error on server %(server)r: %(error)s') @@ -380,7 +380,7 @@ class AuthenticationError(PublicError): **1000** Base class for authentication errors (*1000 - 1999*). """ - code = 1000 + errno = 1000 class KerberosError(AuthenticationError): @@ -388,7 +388,7 @@ class KerberosError(AuthenticationError): **1100** Base class for Kerberos authentication errors (*1100 - 1199*). """ - code = 1100 + errno = 1100 @@ -399,7 +399,7 @@ class AuthorizationError(PublicError): **2000** Base class for authorization errors (*2000 - 2999*). """ - code = 2000 + errno = 2000 class ACIError(AuthorizationError): @@ -407,7 +407,7 @@ class ACIError(AuthorizationError): **2100** Base class for ACI authorization errors (*2100 - 2199*). """ - code = 2100 + errno = 2100 @@ -419,7 +419,7 @@ class InvocationError(PublicError): **3000** Base class for command invocation errors (*3000 - 3999*). """ - code = 3000 + errno = 3000 class EncodingError(InvocationError): @@ -427,7 +427,7 @@ class EncodingError(InvocationError): **3001** Raised when received text is incorrectly encoded. """ - code = 3001 + errno = 3001 class BinaryEncodingError(InvocationError): @@ -435,7 +435,7 @@ class BinaryEncodingError(InvocationError): **3002** Raised when received binary data is incorrectly encoded. """ - code = 3002 + errno = 3002 class ArgumentError(InvocationError): @@ -443,7 +443,7 @@ class ArgumentError(InvocationError): **3003** Raised when a command is called with wrong number of arguments. """ - code = 3003 + errno = 3003 class OptionError(InvocationError): @@ -451,7 +451,7 @@ class OptionError(InvocationError): **3004** Raised when a command is called with unknown options. """ - code = 3004 + errno = 3004 class RequirementError(InvocationError): @@ -459,7 +459,7 @@ class RequirementError(InvocationError): **3005** Raised when a required parameter is not provided. """ - code = 3005 + errno = 3005 class ConversionError(InvocationError): @@ -467,7 +467,7 @@ class ConversionError(InvocationError): **3006** Raised when parameter value can't be converted to correct type. """ - code = 3006 + errno = 3006 class ValidationError(InvocationError): @@ -475,7 +475,7 @@ class ValidationError(InvocationError): **3007** Raised when a parameter value fails a validation rule. """ - code = 3007 + errno = 3007 @@ -487,7 +487,7 @@ class ExecutionError(PublicError): **4000** Base class for execution errors (*4000 - 4999*). """ - code = 4000 + errno = 4000 class LDAPError(ExecutionError): @@ -495,7 +495,7 @@ class LDAPError(ExecutionError): **4100** Base class for LDAP execution errors (*4100 - 4199*). """ - code = 4100 + errno = 4100 @@ -507,7 +507,7 @@ class GenericError(PublicError): **5000** Base class for errors that don't fit elsewhere (*5000 - 5999*). """ - code = 5000 + errno = 5000 @@ -522,7 +522,7 @@ def __errors_iter(): yield value public_errors = tuple( - sorted(__errors_iter(), key=lambda E: E.code) + sorted(__errors_iter(), key=lambda E: E.errno) ) if __name__ == '__main__': -- cgit From 3e9eb0bda000ef138ff04c677aa9014186f547d3 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 8 Jan 2009 00:07:18 -0700 Subject: Changed PublicError so str(e) is untranslated (for logging) and added format=None kwarg for generic use --- ipalib/errors2.py | 80 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 36 deletions(-) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 660bb1bd..b052882d 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -209,31 +209,53 @@ class PluginMissingOverrideError(PrivateError): ############################################################################## # Public errors: + +__messages = [] + +def _(message): + __messages.append(message) + return message + + class PublicError(StandardError): """ **900** Base class for exceptions that can be forwarded in an RPC response. """ errno = 900 - - def __init__(self, message=None, **kw): - if message is None: - message = self.get_format(ugettext) % kw - assert type(message) is unicode - elif type(message) is not unicode: - raise TypeError( - TYPE_ERROR % ('message', unicode, message, type(message)) + format = None + + def __init__(self, format=None, message=None, **kw): + name = self.__class__.__name__ + if self.format is not None and format is not None: + raise ValueError( + 'non-generic %r needs format=None; got format=%r' % ( + name, format) ) - self.message = message + if message is None: + if self.format is None: + if format is None: + raise ValueError( + '%s.format is None yet format=None, message=None' % name + ) + self.format = format + self.forwarded = False + self.message = self.format % kw + self.strerror = ugettext(self.format) % kw + else: + if type(message) is not unicode: + raise TypeError( + TYPE_ERROR % ('message', unicode, message, type(message)) + ) + self.forwarded = True + self.message = message + self.strerror = message for (key, value) in kw.iteritems(): assert not hasattr(self, key), 'conflicting kwarg %s.%s = %r' % ( - self.__class__.__name__, key, value, + name, key, value, ) setattr(self, key, value) - StandardError.__init__(self, message) - - def get_format(self, _): - return _('') + StandardError.__init__(self, self.message) class VersionError(PublicError): @@ -250,11 +272,8 @@ class VersionError(PublicError): """ errno = 901 + format = _('%(cver)s client incompatible with %(sver)s server at %(server)r') - def get_format(self, _): - return _( - '%(cver)s client incompatible with %(sver)s server at %(server)r' - ) class InternalError(PublicError): @@ -270,15 +289,13 @@ class InternalError(PublicError): """ errno = 902 + format = _('an internal error has occured') def __init__(self, message=None): """ Security issue: ignore any information given to constructor. """ - PublicError.__init__(self, self.get_format(ugettext)) - - def get_format(self, _): - return _('an internal error has occured') + PublicError.__init__(self) class ServerInternalError(PublicError): @@ -294,9 +311,7 @@ class ServerInternalError(PublicError): """ errno = 903 - - def get_format(self, _): - return _('an internal error has occured on server at %(server)r') + format = _('an internal error has occured on server at %(server)r') class CommandError(PublicError): @@ -312,9 +327,7 @@ class CommandError(PublicError): """ errno = 904 - - def get_format(self, _): - return _('unknown command %(name)r') + format = _('unknown command %(name)r') class ServerCommandError(PublicError): @@ -331,9 +344,7 @@ class ServerCommandError(PublicError): """ errno = 905 - - def get_format(self, _): - return _('error on server %(server)r: %(error)s') + format = _('error on server %(server)r: %(error)s') class NetworkError(PublicError): @@ -349,9 +360,7 @@ class NetworkError(PublicError): """ errno = 906 - - def get_format(self, _): - return _('cannot connect to %(uri)r') + format = _('cannot connect to %(uri)r') class ServerNetworkError(PublicError): @@ -368,9 +377,8 @@ class ServerNetworkError(PublicError): """ errno = 907 + format = _('error on server %(server)r: %(error)s') - def get_format(self, _): - return _('error on server %(server)r: %(error)s') ############################################################################## -- cgit From 5c7c0b35bb2484efad2a8776b42fbf4066618706 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 12 Jan 2009 16:14:46 -0700 Subject: New Param: added Param.validate() and Param._validate_scalar() methods; added corresponding unit tests --- ipalib/errors2.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index b052882d..81b1fb2e 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -465,9 +465,17 @@ class OptionError(InvocationError): class RequirementError(InvocationError): """ **3005** Raised when a required parameter is not provided. + + For example: + + >>> raise RequirementError(name='givenname') + Traceback (most recent call last): + ... + RequirementError: 'givenname' is required """ errno = 3005 + format = _('%(name)r is required') class ConversionError(InvocationError): @@ -481,9 +489,17 @@ class ConversionError(InvocationError): class ValidationError(InvocationError): """ **3007** Raised when a parameter value fails a validation rule. + + For example: + + >>> raise ValidationError(name='sn', error='can be at most 128 characters') + Traceback (most recent call last): + ... + ValidationError: invalid 'sn': can be at most 128 characters """ errno = 3007 + format = _('invalid %(name)r: %(error)s') -- cgit From 10747103fa3748677e6e1948977de1313fe25bc9 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Tue, 13 Jan 2009 02:17:16 -0700 Subject: New Param: implemented a base Param._convert_scalar() method; added Param.type_error attribute for ConversionError message --- ipalib/errors2.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 81b1fb2e..4c8acd5d 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -481,9 +481,17 @@ class RequirementError(InvocationError): class ConversionError(InvocationError): """ **3006** Raised when parameter value can't be converted to correct type. + + For example: + + >>> raise ConversionError(name='age', error='must be an integer') + Traceback (most recent call last): + ... + ConversionError: invalid 'age': must be an integer """ errno = 3006 + format = _('invalid %(name)r: %(error)s') class ValidationError(InvocationError): -- cgit From 55fba5420d8ea57931937728102094492ca73d86 Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Mon, 19 Jan 2009 21:10:42 -0700 Subject: Added rpc.xmlclient backend plugin for forwarding; added corresponding unit tests --- ipalib/errors2.py | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) (limited to 'ipalib/errors2.py') diff --git a/ipalib/errors2.py b/ipalib/errors2.py index 4c8acd5d..7e2eea05 100644 --- a/ipalib/errors2.py +++ b/ipalib/errors2.py @@ -275,10 +275,27 @@ class VersionError(PublicError): format = _('%(cver)s client incompatible with %(sver)s server at %(server)r') +class UnknownError(PublicError): + """ + **902** Raised when client does not know error it caught from server. + + For example: + + >>> raise UnknownError(code=57, server='localhost', error=u'a new error') + ... + Traceback (most recent call last): + ... + UnknownError: unknown error 57 from localhost: a new error + + """ + + errno = 902 + format = _('unknown error %(code)d from %(server)s: %(error)s') + class InternalError(PublicError): """ - **902** Raised to conceal a non-public exception. + **903** Raised to conceal a non-public exception. For example: @@ -288,7 +305,7 @@ class InternalError(PublicError): InternalError: an internal error has occured """ - errno = 902 + errno = 903 format = _('an internal error has occured') def __init__(self, message=None): @@ -300,7 +317,7 @@ class InternalError(PublicError): class ServerInternalError(PublicError): """ - **903** Raised when client catches an `InternalError` from server. + **904** Raised when client catches an `InternalError` from server. For example: @@ -310,13 +327,13 @@ class ServerInternalError(PublicError): ServerInternalError: an internal error has occured on server at 'https://localhost' """ - errno = 903 + errno = 904 format = _('an internal error has occured on server at %(server)r') class CommandError(PublicError): """ - **904** Raised when an unknown command is called. + **905** Raised when an unknown command is called. For example: @@ -326,13 +343,13 @@ class CommandError(PublicError): CommandError: unknown command 'foobar' """ - errno = 904 + errno = 905 format = _('unknown command %(name)r') class ServerCommandError(PublicError): """ - **905** Raised when client catches a `CommandError` from server. + **906** Raised when client catches a `CommandError` from server. For example: @@ -343,13 +360,13 @@ class ServerCommandError(PublicError): ServerCommandError: error on server 'https://localhost': unknown command 'foobar' """ - errno = 905 + errno = 906 format = _('error on server %(server)r: %(error)s') class NetworkError(PublicError): """ - **906** Raised when a network connection cannot be created. + **907** Raised when a network connection cannot be created. For example: @@ -359,13 +376,13 @@ class NetworkError(PublicError): NetworkError: cannot connect to 'ldap://localhost:389' """ - errno = 906 + errno = 907 format = _('cannot connect to %(uri)r') class ServerNetworkError(PublicError): """ - **907** Raised when client catches a `NetworkError` from server. + **908** Raised when client catches a `NetworkError` from server. For example: @@ -376,7 +393,7 @@ class ServerNetworkError(PublicError): ServerNetworkError: error on server 'https://localhost': cannot connect to 'ldap://localhost:389' """ - errno = 907 + errno = 908 format = _('error on server %(server)r: %(error)s') -- cgit