tests/krb5: Encode KerberosString objects as UTF‐8
authorJoseph Sutton <josephsutton@catalyst.net.nz>
Thu, 26 Oct 2023 03:43:09 +0000 (16:43 +1300)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 9 Nov 2023 08:00:30 +0000 (08:00 +0000)
Windows treats Kerberos strings as UTF‐8, but by default, pyasn1 encodes
strings as ISO-8859-1. (There is a UTF8String type that gets encoded as
UTF‐8, but it has a different ASN.1 encoding from GeneralString, and so
can’t be used). asn1ate provides no way to override the encoding.
Except…

It turns out we can force UTF‐8 encoding by cunningly overriding
KerberosString.__getattribute__().

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/tests/krb5/rfc4120_pyasn1.py

index da38d3f93096784fcdf3bfb40a802e1a3b10b26a..ad8a6e7b6dbb3a0ac9b0a71abb46b9b66b0678cf 100644 (file)
 #
 
 from samba.tests.krb5.rfc4120_pyasn1_generated import *
+
+# Kerberos strings should generally be treated as UTF‐8 encoded, but asn1ate
+# (the tool which generates Python definitions from our ASN.1 modules) provides
+# no way to specify the encoding to use. By the time we’ve imported
+# ‘rfc4120_pyasn1_generated’, KerberosString in the process having been
+# instantiated as part of several schema objects, it’s too late to change the
+# existing objects. But by overriding the __getattribute__() method on
+# KerberosString, we can have objects of that type, or a subtype thereof,
+# encoded as UTF‐8 strings instead of as ISO-8859-1 strings (the default).
+
+class ReadOnlyUtf8EncodingDict(dict):
+    # Don’t allow any attributes to be set.
+    __slots__ = []
+
+    def __getitem__(self, key):
+        # Get the original item. This will raise KeyError if it’s not present.
+        val = super().__getitem__(key)
+
+        # If anyone wants to know our encoding, say it’s UTF‐8.
+        if key == 'encoding':
+            return 'utf-8'
+
+        return val
+
+    # Python’s default implementations of the following methods don’t call
+    # __getitem__(), so we’ll need to override them with our own replacements.
+    # In behaviour, they are close enough to the originals for our purposes.
+
+    def get(self, key, default=None):
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
+    def items(self):
+        for key in self:
+            yield key, self[key]
+
+    def values(self):
+        for key in self:
+            yield self[key]
+
+    # Don’t let anyone modify the dict’s contents.
+
+    def __setitem__(self, key, val):
+        raise TypeError('item assignment not supported')
+
+    def __delitem__(self, key):
+        raise TypeError('item deletion not supported')
+
+
+KerberosString_get_attribute = KerberosString.__getattribute__
+
+def get_attribute_override(self, attr):
+    # Get the original attribute. This will raise AttributeError if it’s not
+    # present.
+    val = KerberosString_get_attribute(self, attr)
+
+    # If anyone wants to know our encoding, say it’s UTF‐8.
+    if attr == 'encoding':
+        return 'utf-8'
+
+    if attr == '_readOnly':
+        # Return a copy of the read‐only attributes with the encoding overridden
+        # to be UTF-8. To avoid the possibility of changes being made to the
+        # original dict that do not propagate to its copies, the returned dict
+        # does not allow modification of its contents. Besides, this is supposed
+        # to be read‐only.
+        return ReadOnlyUtf8EncodingDict(val)
+
+    return val
+
+# Override the __getattribute__() method on KerberosString.
+KerberosString.__getattribute__ = get_attribute_override