def get_role_permission(self,
                         role,
                         expectedCount,
                         model_name='Plot',
                         field_name='owner_orig_id'):
     perms = role_field_permissions(role, self.instance, model_name)
     return self.get_permission(perms, field_name, expectedCount)
Esempio n. 2
0
 def get_field_perms(role, Model):
     model_name = Model.__name__
     model_perms = [
         fp for fp in role_field_permissions(role, instance, model_name)
         if fp.field_name not in Model.bypasses_authorization
     ]
     return sorted(model_perms, key=lambda p: p.field_name)
Esempio n. 3
0
def photo_permission_level(role):
    photo_perms = role_field_permissions(role, None, 'TreePhoto')

    if photo_perms:
        perm = min([p.permission_level for p in photo_perms])
    else:
        perm = FieldPermission.READ_ONLY

    label = dict(FieldPermission.choices)[perm]

    return perm, label
Esempio n. 4
0
    def test_adds_udf_to_role_when_created(self):
        body = {"udf.name": "cool udf", "udf.model": "Plot", "udf.type": "string"}

        udf_create(body, self.instance)

        roles_in_instance = Role.objects.filter(instance=self.instance)

        self.assertGreater(len(roles_in_instance), 0)

        for role in roles_in_instance:
            perms = [perm.field_name for perm in role_field_permissions(role, self.instance)]

            self.assertIn("udf:cool udf", perms)
Esempio n. 5
0
def _update_perms_from_object(role_perms, instance):
    RolePermissionModel = Role.instance_permissions.through
    valid_field_model_names = {m.__name__
                               for m in field_perm_models(instance)}
    valid_perm_models_by_name = {
        m.__name__: m for m in model_perm_models(instance)}
    roles_by_id = {role.pk:
                   role for role in Role.objects.filter(instance=instance)}
    field_perms = {(role, perm.full_name): perm
                   for role in roles_by_id.itervalues()
                   for perm in deepcopy(role_field_permissions(role))}

    input_role_ids = [int(role_id) for role_id in role_perms.iterkeys()]
    for role_id in input_role_ids:
        if role_id not in roles_by_id:
            raise ValidationError("Unrecognized role id [%s]" % role_id)
    input_roles = [roles_by_id[role_id] for role_id in input_role_ids]

    input_role_fields = zip(input_roles, [
        role_inputs['fields'] for role_inputs in role_perms.itervalues()])
    input_role_models = zip(input_roles, [
        role_inputs['models'] for role_inputs in role_perms.itervalues()])

    def validate_model_name(model_name, valid_names):
        if model_name not in valid_names:
            raise ValidationError(
                "model_name must be one of [%s], not %s" %
                (", ".join(valid_names), model_name))

    def validate_and_save_field_perm(role, field_perm):
        for model_field_name, perm_type in field_perm.iteritems():
            model_name, field_name = dotted_split(model_field_name, 2)
            validate_model_name(model_name, valid_field_model_names)

            field_perm = field_perms.get((role, model_field_name), None)

            create = field_perm is None
            if create:
                field_perm = FieldPermission.objects.create(
                    field_name=field_name,
                    model_name=model_name,
                    role=role,
                    instance=role.instance)

            perm_type = int(perm_type)
            if create or field_perm.permission_level != perm_type:
                valid_levels = [level
                                for __, level
                                in options_for_permission(field_perm)]

                if perm_type in valid_levels:
                    field_perm.permission_level = perm_type
                    field_perm.save()
                else:
                    raise Exception('Invalid field type '
                                    '(allowed %s, given %s)' %
                                    (valid_levels, perm_type))

                notifications = instance.config.get('udf_notifications', [])
                if field_perm.full_name in notifications:
                    remove_udf_notification(instance, field_perm.full_name)

    def get_and_validate_permission(codename, Model):
        app_config = Model._meta.app_config
        try:
            return Permission.objects.get_by_natural_key(
                codename, app_config.label, Model.__name__.lower())
        except ObjectDoesNotExist:
            raise ValidationError(
                '{} is not a valid codename for {}.{}'.format(
                    codename, app_config.models_module.__name__,
                    app_config.label))

    def validate_permission_assignment(codename, should_be_assigned, Model):
        if not isinstance(should_be_assigned, bool):
            app_config = Model._meta.app_config
            raise ValidationError(
                '{} is not a valid determination of whether to assign'
                '{} to {}.{}'.format(
                    should_be_assigned, codename,
                    app_config.models_module.__name__, app_config.label))

    def validate_and_save_model_perm(role, model_perm):
        unassign = []
        for model_perm_name, should_be_assigned in model_perm.iteritems():
            model_name, codename = dotted_split(model_perm_name, 2)
            validate_model_name(
                model_name, set(valid_perm_models_by_name.keys()))
            Model = valid_perm_models_by_name[model_name]
            permission = get_and_validate_permission(codename, Model)
            validate_permission_assignment(codename, should_be_assigned, Model)
            if should_be_assigned:
                RolePermissionModel.objects.get_or_create(
                    role=role, permission=permission)
            else:
                unassign.append({'role': role, 'permission': permission})
        if unassign:
            unassign_q = reduce(lambda q1, q2: q1 | q2,
                                [Q(**rpm) for rpm in unassign])
            RolePermissionModel.objects.filter(unassign_q).delete()

    for role, field_perm in input_role_fields:
        validate_and_save_field_perm(role, field_perm)

    for role, model_perm in input_role_models:
        validate_and_save_model_perm(role, model_perm)
Esempio n. 6
0
 def get_field_perms(role, Model):
     model_name = Model.__name__
     model_perms = [fp for fp in role_field_permissions(
         role, instance, model_name)
         if fp.field_name not in Model.bypasses_authorization]
     return sorted(model_perms, key=lambda p: p.field_name)
Esempio n. 7
0
def _allows_perm(role_related_obj,
                 model_name,
                 predicate,
                 perm_attr,
                 field=None,
                 fields=None,
                 feature_name=None):
    """
    The main permission testing function. This function should
    be called from exported (non-underscore) helper functions.

    role_related_obj can be a Role or InstanceUser (Add more types as needed)

    model_name is a ProperCamelCase string name of the model to test.

    predicate is the function used to set the minimum requirement for
    present permissions to pass the current test. Should be any, all,
    or a custom value.

    perm_attr is the minimum permission value necessary to consider
    the perm present in this context. Should correspond to an attr
    on the FieldPermission class.

    field/fields is the fields to use in conjunction with predicate and
    perm_attr.
    Together they form a truth statement like:
    "all of {'image', 'thumbnail'} have ALLOWS_WRITES"
    "any of {'id', 'mapfeature'} have ALLOWS_READS"

    feature_name checks if this feature is enabled for this instance. While
    not exactly connected to permissions, it's convenient to check this here
    as well.
    """
    role = _get_role_from_related_object(role_related_obj)
    if role is None:
        return False

    if feature_name and not role.instance.feature_enabled(feature_name):
        return False

    perms = {
        perm
        for perm in role_field_permissions(role, role.instance, model_name)
    }

    # process args
    if field and fields:
        fields = set(fields) | {field}
    elif field and not fields:
        fields = {field}
    elif not fields:
        fields = set()

    # forcibly convert fields to a set of names (strings)
    # if they were passed in as objects.
    fields = {
        field.name if isinstance(field, Field) else field
        for field in fields
    }

    if fields:
        perms = {perm for perm in perms if perm.field_name in fields}

    perm_attrs = {getattr(perm, perm_attr) for perm in perms}

    # TODO: find a better way to support 'all'
    # this is a hack around a quirk, that all([]) == True.
    # Since all is such a common case, it's still nice to
    # support it out of the box.
    if predicate == all and not perm_attrs:
        return False
    else:
        return predicate(perm_attrs)
Esempio n. 8
0
def instance_info(request, instance):
    """
    Get all the info we need about a given instance

    It also includes info about the fields available for the
    instance. If a user has been specified the field info
    will be tailored to that user
    """
    user = request.user
    role = Role.objects.get_role(instance, user)

    collection_udfs = instance.collection_udfs
    collection_udf_dict = {
        "%s.%s" % (udf.model_type.lower(), udf.canonical_name): udf
        for udf in collection_udfs
    }

    def add_perms(field_permissions, perms):
        for fp in field_permissions:
            model = fp.model_name.lower()
            field_key = '%s.%s' % (model, fp.field_name)
            if fp.allows_reads:
                if field_key in collection_udf_dict:
                    choices = []
                    data_type = json.loads(
                        collection_udf_dict[field_key].datatype)
                elif is_json_field_reference(fp.field_name):
                    choices = None
                    data_type = "string"
                else:
                    model_inst = safe_get_model_class(
                        fp.model_name)(instance=instance)
                    data_type, __, __, choices = field_type_label_choices(
                        model_inst, fp.field_name, fp.display_field_name)

                digits = get_digits_if_formattable(instance, model,
                                                   fp.field_name)

                units = get_units_if_convertible(instance, model,
                                                 fp.field_name)

                factor = 1.0

                try:
                    factor = storage_to_instance_units_factor(
                        instance, model, fp.field_name)
                except KeyError:
                    pass

                perms[field_key] = {
                    'data_type': data_type,
                    'choices': choices,
                    'units': units,
                    'digits': digits,
                    'canonical_units_factor': 1.0 / factor,
                    'can_write': fp.allows_writes,
                    'display_name': fp.display_field_name,
                    'field_name': fp.field_name,
                    'field_key': field_key,
                    'is_collection': field_key in collection_udf_dict
                }

    # collect perms for the given role/instance into a serializable
    # dictionary. If a field isn't at least readable, it doesn't
    # get sent over at all.

    field_perms = role_field_permissions(role, instance)
    always_writable = [
        FieldPermission(model_name=Model.__name__,
                        field_name=field_name,
                        permission_level=FieldPermission.WRITE_DIRECTLY)
        for Model in {Tree, Plot} for field_name in Model.always_writable
    ]
    perms = {}
    add_perms(field_perms, perms)
    add_perms(always_writable, perms)
    # Legacy iOS app versions (<= 2.6.0) enforce photo permissions using
    # the `treephoto.image` field, so set it. This can be removed when we
    # think all users are on a later version.
    perms['treephoto.image'] = {
        'can_write': perms_lib.photo_is_addable(role, Plot),
        'data_type': 'string'
    }

    # These identifiers are not included in `perms` for instances
    # created after the transtion to model-level permissions, but the
    # identifiers are referenced in the default mobile search
    # configuration. Versions of the iOS application depend on all the
    # identifiers in the search config being respresented in the
    # `perms` dictionary if the user is allowed to see the field
    # value. Species and photos are always visible, so we can hard
    # code a non-None value for these identifiers.
    for identifier in ('species.id', 'mapFeaturePhoto.id'):
        if identifier not in perms:
            perms[identifier] = {'data_type': 'string'}

    def get_key_for_group(field_group):
        for key in ('collection_udf_keys', 'field_keys'):
            if key in field_group:
                return key
        return None

    # Remove fields from mobile_api_fields if they are not present in perms
    # (Generally because the user doesn't have read permissions)
    # If no fields are left in a group, remove the group
    mobile_api_fields = copy.deepcopy(instance.mobile_api_fields)

    for field_group in mobile_api_fields:
        # Field group headers are stored in English, and translated when they
        # are sent out to the client
        field_group['header'] = _(field_group['header'])
        key = get_key_for_group(field_group)
        if key:
            field_group[key] = [
                field for field in field_group[key] if field in perms
            ]

    readable_mobile_api_fields = [
        group for group in mobile_api_fields
        if group.get(get_key_for_group(group), None)
    ]

    info = _instance_info_dict(instance)
    info['fields'] = perms
    info['field_key_groups'] = readable_mobile_api_fields
    info['search'] = mobile_search_fields(instance)
    info['date_format'] = _unicode_dateformat(instance.date_format)
    info['short_date_format'] = _unicode_dateformat(instance.short_date_format)

    info['meta_perms'] = {
        'can_add_tree':
        perms_lib.plot_is_creatable(role),
        'can_edit_tree': (perms_lib.plot_is_writable(role)
                          or perms_lib.tree_is_writable(role)),
        'can_edit_tree_photo':
        perms_lib.photo_is_addable(role, Plot),
    }

    public_config_keys = ['scss_variables']

    info['config'] = {
        x: instance.config[x]
        for x in instance.config if x in public_config_keys
    }

    if instance.logo:
        info['logoUrl'] = instance.logo.url

    return info
Esempio n. 9
0
def _allows_perm(role_related_obj, model_name,
                 predicate, perm_attr,
                 field=None,
                 fields=None,
                 feature_name=None):
    """
    The main permission testing function. This function should
    be called from exported (non-underscore) helper functions.

    role_related_obj can be a Role or InstanceUser (Add more types as needed)

    model_name is a ProperCamelCase string name of the model to test.

    predicate is the function used to set the minimum requirement for
    present permissions to pass the current test. Should be any, all,
    or a custom value.

    perm_attr is the minimum permission value necessary to consider
    the perm present in this context. Should correspond to an attr
    on the FieldPermission class.

    field/fields is the fields to use in conjunction with predicate and
    perm_attr.
    Together they form a truth statement like:
    "all of {'image', 'thumbnail'} have ALLOWS_WRITES"
    "any of {'id', 'mapfeature'} have ALLOWS_READS"

    feature_name checks if this feature is enabled for this instance. While
    not exactly connected to permissions, it's convenient to check this here
    as well.
    """
    role = _get_role_from_related_object(role_related_obj)
    if role is None:
        return False

    if feature_name and not role.instance.feature_enabled(feature_name):
        return False

    perms = {perm for perm in
             role_field_permissions(role, role.instance, model_name)}

    # process args
    if field and fields:
        fields = set(fields) | {field}
    elif field and not fields:
        fields = {field}
    elif not fields:
        fields = set()

    # forcibly convert fields to a set of names (strings)
    # if they were passed in as objects.
    fields = {field.name if isinstance(field, Field) else field
              for field in fields}

    if fields:
        perms = {perm for perm in perms if perm.field_name in fields}

    perm_attrs = {getattr(perm, perm_attr) for perm in perms}

    # TODO: find a better way to support 'all'
    # this is a hack around a quirk, that all([]) == True.
    # Since all is such a common case, it's still nice to
    # support it out of the box.
    if predicate == all and not perm_attrs:
        return False
    else:
        return predicate(perm_attrs)
Esempio n. 10
0
 def get_role_permission(self, role, expectedCount, model_name='Plot',
                         field_name='owner_orig_id'):
     perms = role_field_permissions(role, self.instance, model_name)
     return self.get_permission(perms, field_name, expectedCount)
Esempio n. 11
0
def _update_perms_from_object(role_perms, instance):
    RolePermissionModel = Role.instance_permissions.through
    valid_field_model_names = {m.__name__ for m in field_perm_models(instance)}
    valid_perm_models_by_name = {
        m.__name__: m
        for m in model_perm_models(instance)
    }
    roles_by_id = {
        role.pk: role
        for role in Role.objects.filter(instance=instance)
    }
    field_perms = {(role, perm.full_name): perm
                   for role in roles_by_id.itervalues()
                   for perm in deepcopy(role_field_permissions(role))}

    input_role_ids = [int(role_id) for role_id in role_perms.iterkeys()]
    for role_id in input_role_ids:
        if role_id not in roles_by_id:
            raise ValidationError("Unrecognized role id [%s]" % role_id)
    input_roles = [roles_by_id[role_id] for role_id in input_role_ids]

    input_role_fields = zip(
        input_roles,
        [role_inputs['fields'] for role_inputs in role_perms.itervalues()])
    input_role_models = zip(
        input_roles,
        [role_inputs['models'] for role_inputs in role_perms.itervalues()])

    def validate_model_name(model_name, valid_names):
        if model_name not in valid_names:
            raise ValidationError("model_name must be one of [%s], not %s" %
                                  (", ".join(valid_names), model_name))

    def validate_and_save_field_perm(role, field_perm):
        for model_field_name, perm_type in field_perm.iteritems():
            model_name, field_name = dotted_split(model_field_name, 2)
            validate_model_name(model_name, valid_field_model_names)

            field_perm = field_perms.get((role, model_field_name), None)

            create = field_perm is None
            if create:
                field_perm = FieldPermission.objects.create(
                    field_name=field_name,
                    model_name=model_name,
                    role=role,
                    instance=role.instance)

            perm_type = int(perm_type)
            if create or field_perm.permission_level != perm_type:
                valid_levels = [
                    level for __, level in options_for_permission(field_perm)
                ]

                if perm_type in valid_levels:
                    field_perm.permission_level = perm_type
                    field_perm.save()
                else:
                    raise Exception('Invalid field type '
                                    '(allowed %s, given %s)' %
                                    (valid_levels, perm_type))

                notifications = instance.config.get('udf_notifications', [])
                if field_perm.full_name in notifications:
                    remove_udf_notification(instance, field_perm.full_name)

    def get_and_validate_permission(codename, Model):
        app_config = Model._meta.app_config
        try:
            return Permission.objects.get_by_natural_key(
                codename, app_config.label, Model.__name__.lower())
        except ObjectDoesNotExist:
            raise ValidationError(
                '{} is not a valid codename for {}.{}'.format(
                    codename, app_config.models_module.__name__,
                    app_config.label))

    def validate_permission_assignment(codename, should_be_assigned, Model):
        if not isinstance(should_be_assigned, bool):
            app_config = Model._meta.app_config
            raise ValidationError(
                '{} is not a valid determination of whether to assign'
                '{} to {}.{}'.format(should_be_assigned, codename,
                                     app_config.models_module.__name__,
                                     app_config.label))

    def validate_and_save_model_perm(role, model_perm):
        unassign = []
        for model_perm_name, should_be_assigned in model_perm.iteritems():
            model_name, codename = dotted_split(model_perm_name, 2)
            validate_model_name(model_name,
                                set(valid_perm_models_by_name.keys()))
            Model = valid_perm_models_by_name[model_name]
            permission = get_and_validate_permission(codename, Model)
            validate_permission_assignment(codename, should_be_assigned, Model)
            if should_be_assigned:
                RolePermissionModel.objects.get_or_create(
                    role=role, permission=permission)
            else:
                unassign.append({'role': role, 'permission': permission})
        if unassign:
            unassign_q = reduce(lambda q1, q2: q1 | q2,
                                [Q(**rpm) for rpm in unassign])
            RolePermissionModel.objects.filter(unassign_q).delete()

    for role, field_perm in input_role_fields:
        validate_and_save_field_perm(role, field_perm)

    for role, model_perm in input_role_models:
        validate_and_save_model_perm(role, model_perm)
Esempio n. 12
0
def instance_info(request, instance):
    """
    Get all the info we need about a given instance

    It also includes info about the fields available for the
    instance. If a user has been specified the field info
    will be tailored to that user
    """
    user = request.user
    role = Role.objects.get_role(instance, user)

    collection_udfs = instance.collection_udfs
    collection_udf_dict = {"%s.%s" % (udf.model_type.lower(),
                                      udf.canonical_name): udf
                           for udf in collection_udfs}

    def add_perms(field_permissions, perms):
        for fp in field_permissions:
            model = fp.model_name.lower()
            field_key = '%s.%s' % (model, fp.field_name)
            if fp.allows_reads:
                if field_key in collection_udf_dict:
                    choices = []
                    data_type = json.loads(
                        collection_udf_dict[field_key].datatype)
                elif is_json_field_reference(fp.field_name):
                    choices = None
                    data_type = "string"
                else:
                    model_inst = safe_get_model_class(fp.model_name)(
                        instance=instance)
                    data_type, __, __, choices = field_type_label_choices(
                        model_inst, fp.field_name, fp.display_field_name)

                digits = get_digits_if_formattable(
                    instance, model, fp.field_name)

                units = get_units_if_convertible(
                    instance, model, fp.field_name)

                factor = 1.0

                try:
                    factor = storage_to_instance_units_factor(
                        instance, model, fp.field_name)
                except KeyError:
                    pass

                perms[field_key] = {
                    'data_type': data_type,
                    'choices': choices,
                    'units': units,
                    'digits': digits,
                    'canonical_units_factor': 1.0 / factor,
                    'can_write': fp.allows_writes,
                    'display_name': fp.display_field_name,
                    'field_name': fp.field_name,
                    'field_key': field_key,
                    'is_collection': field_key in collection_udf_dict
                }

    # collect perms for the given role/instance into a serializable
    # dictionary. If a field isn't at least readable, it doesn't
    # get sent over at all.

    field_perms = role_field_permissions(role, instance)
    always_writable = [
        FieldPermission(
            model_name=Model.__name__,
            field_name=field_name,
            permission_level=FieldPermission.WRITE_DIRECTLY
        )
        for Model in {Tree, Plot}
        for field_name in Model.always_writable]
    perms = {}
    add_perms(field_perms, perms)
    add_perms(always_writable, perms)
    # Legacy iOS app versions (<= 2.6.0) enforce photo permissions using
    # the `treephoto.image` field, so set it. This can be removed when we
    # think all users are on a later version.
    perms['treephoto.image'] = {
        'can_write': perms_lib.photo_is_addable(role, Plot),
        'data_type': 'string'
    }

    # These identifiers are not included in `perms` for instances
    # created after the transtion to model-level permissions, but the
    # identifiers are referenced in the default mobile search
    # configuration. Versions of the iOS application depend on all the
    # identifiers in the search config being respresented in the
    # `perms` dictionary if the user is allowed to see the field
    # value. Species and photos are always visible, so we can hard
    # code a non-None value for these identifiers.
    for identifier in ('species.id', 'mapFeaturePhoto.id'):
        if identifier not in perms:
            perms[identifier] = {
                'data_type': 'string'
            }

    def get_key_for_group(field_group):
        for key in ('collection_udf_keys', 'field_keys'):
            if key in field_group:
                return key
        return None

    # Remove fields from mobile_api_fields if they are not present in perms
    # (Generally because the user doesn't have read permissions)
    # If no fields are left in a group, remove the group
    mobile_api_fields = copy.deepcopy(instance.mobile_api_fields)

    for field_group in mobile_api_fields:
        # Field group headers are stored in English, and translated when they
        # are sent out to the client
        field_group['header'] = _(field_group['header'])
        key = get_key_for_group(field_group)
        if key:
            field_group[key] = [field for field in field_group[key]
                                if field in perms]

    readable_mobile_api_fields = [group for group in mobile_api_fields
                                  if group.get(get_key_for_group(group), None)]

    info = _instance_info_dict(instance)
    info['fields'] = perms
    info['field_key_groups'] = readable_mobile_api_fields
    info['search'] = mobile_search_fields(instance)
    info['date_format'] = _unicode_dateformat(instance.date_format)
    info['short_date_format'] = _unicode_dateformat(instance.short_date_format)

    info['meta_perms'] = {
        'can_add_tree': perms_lib.plot_is_creatable(role),
        'can_edit_tree': (perms_lib.plot_is_writable(role) or
                          perms_lib.tree_is_writable(role)),
        'can_edit_tree_photo': perms_lib.photo_is_addable(role, Plot),
    }

    public_config_keys = ['scss_variables']

    info['config'] = {x: instance.config[x]
                      for x in instance.config
                      if x in public_config_keys}

    if instance.logo:
        info['logoUrl'] = instance.logo.url

    return info
Esempio n. 13
0
def instance_info(request, instance):
    """
    Get all the info we need about a given instance

    It also includes info about the fields available for the
    instance. If a user has been specified the field info
    will be tailored to that user
    """
    user = request.user
    role = Role.objects.get_role(instance, user)

    collection_udfs = instance.collection_udfs
    collection_udf_dict = {"%s.%s" % (udf.model_type.lower(),
                                      udf.canonical_name): udf
                           for udf in collection_udfs}

    # collect perms for the given role/instance into a serializable
    # dictionary. If a field isn't at least readable, it doesn't
    # get sent over at all.
    perms = {}
    for fp in role_field_permissions(role, instance):
        model = fp.model_name.lower()
        field_key = '%s.%s' % (model, fp.field_name)
        if fp.allows_reads:
            if field_key in collection_udf_dict:
                choices = []
                data_type = json.loads(collection_udf_dict[field_key].datatype)
            elif is_json_field_reference(fp.field_name):
                choices = None
                data_type = "string"
            else:
                model_inst = safe_get_model_class(fp.model_name)(
                    instance=instance)
                data_type, __, __, choices = field_type_label_choices(
                    model_inst, fp.field_name, fp.display_field_name)

            digits = get_digits_if_formattable(
                instance, model, fp.field_name)

            units = get_units_if_convertible(
                instance, model, fp.field_name)

            factor = 1.0

            try:
                factor = storage_to_instance_units_factor(
                    instance, model, fp.field_name)
            except KeyError:
                pass

            perms[field_key] = {
                'data_type': data_type,
                'choices': choices,
                'units': units,
                'digits': digits,
                'canonical_units_factor': 1.0 / factor,
                'can_write': fp.allows_writes,
                'display_name': fp.display_field_name,
                'field_name': fp.field_name,
                'field_key': field_key,
                'is_collection': field_key in collection_udf_dict
            }

    def get_key_for_group(field_group):
        for key in ('collection_udf_keys', 'field_keys'):
            if key in field_group:
                return key
        return None

    # Remove fields from mobile_api_fields if they are not present in perms
    # (Generally because the user doesn't have read permissions)
    # If no fields are left in a group, remove the group
    mobile_api_fields = copy.deepcopy(instance.mobile_api_fields)

    for field_group in mobile_api_fields:
        # Field group headers are stored in English, and translated when they
        # are sent out to the client
        field_group['header'] = _(field_group['header'])
        key = get_key_for_group(field_group)
        if key:
            field_group[key] = [field for field in field_group[key]
                                if field in perms]

    readable_mobile_api_fields = [group for group in mobile_api_fields
                                  if group.get(get_key_for_group(group), None)]

    info = _instance_info_dict(instance)
    info['fields'] = perms
    info['field_key_groups'] = readable_mobile_api_fields
    info['search'] = mobile_search_fields(instance)
    info['date_format'] = _unicode_dateformat(instance.date_format)
    info['short_date_format'] = _unicode_dateformat(instance.short_date_format)

    info['meta_perms'] = {
        'can_add_tree': perms_lib.plot_is_creatable(role),
        'can_edit_tree': perms_lib.plot_is_writable(role),
        'can_edit_tree_photo': perms_lib.photo_is_addable(role, Plot),
    }

    public_config_keys = ['scss_variables']

    info['config'] = {x: instance.config[x]
                      for x in instance.config
                      if x in public_config_keys}

    if instance.logo:
        info['logoUrl'] = instance.logo.url

    return info