This file is indexed.

/usr/lib/python2.7/dist-packages/maasserver/fields.py is in python-django-maas 1.5.4+bzr2294-0ubuntu1.2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# Copyright 2012-2014 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Custom model fields."""

from __future__ import (
    absolute_import,
    print_function,
    unicode_literals,
    )

str = None

__metaclass__ = type
__all__ = [
    "MAC",
    "MACAddressField",
    "MACAddressFormField",
    "register_mac_type",
    ]

from copy import deepcopy
from json import (
    dumps,
    loads,
    )
import re

from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db.models import (
    Field,
    SubfieldBase,
    )
from django.forms import (
    ModelChoiceField,
    RegexField,
    )
from maasserver.utils.orm import get_one
import psycopg2.extensions
from south.modelsinspector import add_introspection_rules


mac_re = re.compile(r'^\s*([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}\s*$')


mac_error_msg = "Enter a valid MAC address (e.g. AA:BB:CC:DD:EE:FF)."

mac_validator = RegexValidator(regex=mac_re, message=mac_error_msg)


def validate_mac(value):
    """Django validator for a MAC."""
    if isinstance(value, MAC):
        value = value.get_raw()
    mac_validator(value)


# The MACAddressField, JSONObjectField and XMLField don't introduce any new
# parameters compared to their parent's constructors so South will handle
# them just fine.
# See http://south.aeracode.org/docs/customfields.html#extending-introspection
# for details.
add_introspection_rules(
    [], [
        "^maasserver\.fields\.MACAddressField",
        "^maasserver\.fields\.JSONObjectField",
        "^maasserver\.fields\.XMLField",
    ])


class NodeGroupFormField(ModelChoiceField):
    """Form field: reference to a :class:`NodeGroup`.

    Node groups are identified by their subnets.  More precisely: this
    field will accept any IP as an identifier for the nodegroup whose subnet
    contains the IP address.

    Unless `queryset` is explicitly given, this field covers all NodeGroup
    objects.
    """

    def __init__(self, **kwargs):
        # Avoid circular imports.
        from maasserver.models import NodeGroup

        kwargs.setdefault('queryset', NodeGroup.objects.all())
        super(NodeGroupFormField, self).__init__(**kwargs)

    def label_from_instance(self, nodegroup):
        """Django method: get human-readable choice label for nodegroup."""
        interfaces = sorted(
            interface.ip for interface in nodegroup.get_managed_interfaces())
        if len(interfaces) > 0:
            return "%s: %s" % (nodegroup.name, ', '.join(interfaces))
        else:
            return nodegroup.name

    def _get_nodegroup_from_string(self, value=None):
        """Identify a `NodeGroup` ID from a text value.

        :param value: A `unicode` that identifies a `NodeGroup` somehow: as a
            numerical ID in text form, as a UUID, as a cluster name, or as the
            empty string to denote the master nodegroup.  `None` also gets the
            master nodegroup.
        :return: Matching `NodeGroup`, or `None`.
        """
        # Avoid circular imports.
        from maasserver.models import NodeGroup

        if value is None or value == '':
            # No identification given.  Default to the master.
            return NodeGroup.objects.ensure_master()

        if value.isnumeric():
            # Try value as an ID.
            nodegroup = get_one(NodeGroup.objects.filter(id=int(value)))
            if nodegroup is not None:
                return nodegroup

        # Try value as a UUID.
        nodegroup = get_one(NodeGroup.objects.filter(uuid=value))
        if nodegroup is not None:
            return nodegroup

        # Try value as a cluster name.
        return get_one(NodeGroup.objects.filter(cluster_name=value))

    def clean(self, value):
        """Django method: provide expected output for various inputs.

        There seems to be no clear specification on what `value` can be.
        This method accepts the types that we see in practice:
         * :class:`NodeGroup`
         * the nodegroup's numerical id in text form
         * the nodegroup's uuid
         * the nodegroup's cluster_name

        If no nodegroup is indicated, it defaults to the master.
        """
        # Avoid circular imports.
        from maasserver.models import NodeGroup

        if isinstance(value, bytes):
            value = value.decode('utf-8')

        if value is None or isinstance(value, unicode):
            nodegroup = self._get_nodegroup_from_string(value)
        elif isinstance(value, NodeGroup):
            nodegroup = value
        else:
            nodegroup = None

        if nodegroup is None:
            raise ValidationError("Invalid nodegroup: %s." % value)

        return nodegroup


class MACAddressFormField(RegexField):
    """Form field type: MAC address."""

    def __init__(self, *args, **kwargs):
        super(MACAddressFormField, self).__init__(
            regex=mac_re, error_message=mac_error_msg, *args, **kwargs)


class MACAddressField(Field):
    """Model field type: MAC address."""

    __metaclass__ = SubfieldBase

    description = "MAC address"

    default_validators = [validate_mac]

    def db_type(self, *args, **kwargs):
        return "macaddr"


class MAC:
    """A MAC address represented as a database value.

    PostgreSQL supports MAC addresses as a native type.  They show up
    client-side as this class.  It is essentially a wrapper for either a
    string, or None.
    """

    def __init__(self, value):
        """Wrap a MAC address, or None, into a `MAC`.

        :param value: A MAC address, in the form of a string or a `MAC`;
            or None.
        """
        if isinstance(value, MAC):
            # Avoid double-wrapping.  It's the value that matters, not the
            # MAC object that wraps it.
            value = value.get_raw()
        elif isinstance(value, bytes):
            value = value.decode("ascii")
        else:
            # TODO bug=1215447: Remove this assertion.
            assert value is None or isinstance(value, unicode)
        # The wrapped attribute is stored as self._wrapped, following
        # ISQLQuote's example.
        self._wrapped = value

    def __conform__(self, protocol):
        """Tell psycopg2 that this type implements the adapter protocol."""
        # The psychopg2 docs say to check that the protocol is ISQLQuote,
        # but not what to do if it isn't.
        assert protocol == psycopg2.extensions.ISQLQuote, (
            "Unsupported psycopg2 adapter protocol: %s" % protocol)
        return self

    def getquoted(self):
        """Render this object in SQL.

        This is part of psycopg2's adapter protocol.
        """
        value = self.get_raw()
        if value is None:
            return 'NULL'
        else:
            return "'%s'::macaddr" % value

    def get_raw(self):
        """Return the wrapped value."""
        return self._wrapped

    @staticmethod
    def parse(value, cur):
        """Turn a value as received from the database into a MAC."""
        return MAC(value)

    def __repr__(self):
        """Represent the MAC as a string.
        """
        return self.get_raw()

    def __eq__(self, other):
        # Two MACs are equal if they wrap the same value.
        #
        # Also, a MAC is equal to the value it wraps.  This is non-commutative,
        # but it supports Django code that compares input values to various
        # kinds of "null" or "empty."
        if isinstance(other, MAC):
            other = other.get_raw()
        return self.get_raw() == other

    def __ne__(self, other):
        return not (self == other)

    def __hash__(self):
        return self.get_raw().__hash__()


def register_mac_type(cursor):
    """Register our `MAC` type with psycopg2 and Django."""

    # This is standard, but not built-in, magic to register a type in
    # psycopg2: execute a query that returns a field of the corresponding
    # database type, then get its oid out of the cursor, use that to create
    # a "typecaster" in psycopg (by calling new_type(), confusingly!), then
    # register that type in psycopg.
    cursor.execute("SELECT NULL::macaddr")
    oid = cursor.description[0][1]
    mac_caster = psycopg2.extensions.new_type((oid, ), b"macaddr", MAC.parse)
    psycopg2.extensions.register_type(mac_caster)

    # Now do the same for the type array-of-MACs.  The "typecaster" created
    # for MAC is passed in; it gets used for parsing an individual element
    # of an array's text representation as received from the database.
    cursor.execute("SELECT '{}'::macaddr[]")
    oid = cursor.description[0][1]
    psycopg2.extensions.register_type(psycopg2.extensions.new_array_type(
        (oid, ), b"macaddr", mac_caster))


class JSONObjectField(Field):
    """A field that will store any jsonizable python object."""

    __metaclass__ = SubfieldBase

    def to_python(self, value):
        """db -> python: json load."""
        assert not isinstance(value, bytes)
        if value is not None:
            if isinstance(value, unicode):
                try:
                    return loads(value)
                except ValueError:
                    pass
            return value
        else:
            return None

    def get_db_prep_value(self, value, connection=None, prepared=False):
        """python -> db: json dump."""
        if value is not None:
            return dumps(deepcopy(value))
        else:
            return None

    def get_internal_type(self):
        return 'TextField'

    def get_prep_lookup(self, lookup_type, value):
        if lookup_type not in ['exact', 'isnull']:
            raise TypeError("Lookup type %s is not supported." % lookup_type)
        return super(JSONObjectField, self).get_prep_lookup(
            lookup_type, value)


class XMLField(Field):
    """A field for storing xml natively.

    This is not like the removed Django XMLField which just added basic python
    level checking on top of a text column.

    Really inserts should be wrapped like `XMLPARSE(DOCUMENT value)` but it's
    hard to do from django so rely on postgres supporting casting from char.
    """

    description = "XML document or fragment"

    def db_type(self, connection):
        return "xml"

    def get_db_prep_lookup(self, lookup_type, value, **kwargs):
        """Limit lookup types to those that work on xml.

        Unlike character fields the xml type is non-comparible, see:
        <http://www.postgresql.org/docs/devel/static/datatype-xml.html>
        """
        if lookup_type != 'isnull':
            raise TypeError("Lookup type %s is not supported." % lookup_type)
        return super(XMLField, self).get_db_prep_lookup(
            lookup_type, value, **kwargs)