LINUX.ORG.RU

Как в питоне спрятать некоторые аттрибуты класса?

 


0

1

Задача примерно такая. Есть система, в ней есть класс. В нем реализована куча методов, многие из которых разрушительные. Есть аттрибуты, через которые можно дойти до самых низов системы. Возникла необходимость предоставить API для пользователей, с помощью которого дать доступ к некоторым методам и аттрибутам объектов класса. Понятное дело, что разрушительные операции нужно запретить. Но как? Сразу в голову пришла идея сделать класс-прослойку, которая будет проверять допустимость аттрибута, что-то типа такого:

class SafeProxy(object):

    def __init__(self, proxied):
        self.__proxied = proxied

    def __getattribute__(self, name):
        if name == '_SafeProxy__proxied':
            return super(SafeProxy, self).__getattribute__(name)
        if name in allowed_attrs.get(self.__proxied.__class__.__name__, []):
            return res

Но все равно __proxied будет доступен, а через него все остальные потроха. Оборачивать все допустимые операции в функции - получится очень некрасивое API. Какие есть варианты решения?

★★★★★

Последнее исправление: maxcom (всего исправлений: 1)

Никаких. Можно придумать защиту от дурака, но только от неизобретательного. В питоне все открыто, даже стек...

Если очень чешется, можно замутить косвенную адресацию - хранить объекты в глобаьной таблице, в качеисве ключа юзать id, таблицу не импортировать. Но если захочут то влезут.

В плюсах кстати private это тоже фикция.

ЗЫ можно конечно все разместить в процессе-сервере и стучаться туда по сокету через тонкий клиент.

AIv ★★★★★
()
Последнее исправление: AIv (всего исправлений: 1)

в питоне нет приватных полей/методов, все на совести вызывающей стороны

marvin_yorke ★★★
()

В нем реализована куча методов, многие из которых разрушительные.

Назови их с __. Если пользователь в них лезет, он ССЗБ.

PolarFox ★★★★★
()
Ответ на: комментарий от provaton

Ты самое главное пропустил, в первом предложении.

true_admin ★★★★★
()
Ответ на: комментарий от AIv

Да уж... Наверное единственный вариант - это конвертировать в дикт и потом обратно. С учетом того, что АПИ на джаваскрипте - это будет вполне себе ничего так.

provaton ★★★★★
() автор топика
Ответ на: комментарий от provaton

Можешь, конечно, написать обёртку на сях чтобы скрыть лишнеее и затруднить интроспекцию... Но если прогеры такие конченые идиоты которые не читают ни док по питону, ни док к твоей софтине то может лучше сменить работу?

true_admin ★★★★★
()
Ответ на: комментарий от PolarFox

Если пользователь в них лезет, он ССЗБ.

Та понятно. Но система многопользовательская, поэтому потенциально он сможет сделать что-то такое, что уже я сам буду ССЗБ.

provaton ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Я не от идиотов предохраняюсь, а от злоумышленников. Система многопользовательская. Если дать влезть глубоко в потроха, то последствия будут не очень хорошие. Получат курсор к базе, и следственно доступ к чужим данным. Я правда не знаю, смогут ли, но потенциально возможности большие.

provaton ★★★★★
() автор топика
Последнее исправление: provaton (всего исправлений: 1)
Ответ на: комментарий от tailgunner

Кстати да, что ты подразумеваешь под злоумышленниками:1. Взломщиков 2. или плохих программистов, которые не читают твои доки и чихали на __funcName ?

blokant ★★
()
Ответ на: комментарий от blokant

Подразумеваю не я, но, судя по этому:

Получат курсор к базе, и следственно доступ к чужим данным

таки взломщиков.

tailgunner ★★★★★
()
Ответ на: комментарий от provaton

Я не от идиотов предохраняюсь, а от злоумышленников.

омг, ну тогда ты делаешь кардинально неправильно. Создай новый топик с вопросом как правильно выстроить архитектуру приложения с описанием задачи и тебе помогут.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

В чем собственно неправильность?

АПИ ведь не на чистом питоне, а через pyv8 идет (я это в стартовом посте не упомянул чтоб не усложнять). V8 должен обеспечить песочницу, моя задача лишь передать минимально необходимый набор функций и объектов, для чего я и ищу простейший способ.

provaton ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Нет, произвольный не позволяет, позволяет получить доступ к любому аттрибуту переданного класса. Это все-таки разные вещи. То есть, передавая одни дикты мы вообще как в танке.

provaton ★★★★★
() автор топика

Использовать модуль на C с геттерами и сеттерами.

/thread

quiet_readonly ★★★★
()
Ответ на: комментарий от provaton

У переданного класса можно вытянуть class.mro() и мучать объекты обёрнутые во враппер.

А функции типа globals или __import__ внутри доступны?

true_admin ★★★★★
()

Facepalm. Чувак, ты делаешь это не правильно. Что это за необходимость такая, произвольный код давать исполнять кому-то? Что ты там делаешь вообще?

ei-grad ★★★★★
()

Считай, что все потроха всегда доступны. Оборачивание тебя не спасёт.

aedeph_ ★★
()
Ответ на: комментарий от VirRaa

Это бич питона.

А где не бич? Даже песочница жабки дырявая. Хотя казалось бы.

anonymous
()

Тут только гальваническая разводка. То бишь, клиентский код выполнять в отдельном процессе, обращаясь к сервису.

anonymous
()
Ответ на: комментарий от provaton

В чем собственно неправильность?

в попытках «защиты» общих данных ограничениями на клиенте. Данность: клиентский код, на чем бы он ни был написан, при желании этого вашего злоумышленника может быть изменен как угодно. Все операции над общими данными должны выполняться на «сервере», а клиентский код - только пинать сервер на предмет выполнения сих операций, причем проверка допустимости оных для клиента - на сервере.

arkhnchul ★★★
()
Ответ на: комментарий от true_admin

А функции типа globals или __import__ внутри доступны?

Нет, глобального ничего нету.

provaton ★★★★★
() автор топика
Ответ на: комментарий от arkhnchul

Код - это тоже данные. Не понимая специфику системы дать хороший совет по архитектуре не выйдет. Я задал лишь один конкретный вопрос и все, жизни меня учить не надо. Все риски я понимаю и осознаю.

provaton ★★★★★
() автор топика
Ответ на: комментарий от true_admin

Вообще неплохо было бы. А то сейчас (даже без АПИ) в случае ошибки в коде юзер потенциально сможет получить доступ к не своим данным. Буду думать над этим вопросом. Хотя здесь все нетривиально, так как аутенфикация проходит в питоновском коде.

Вообще броузеры ведь джаваскрипт выполняют уже десятилетия, и все равно песочница надежна, и никто не переживает, что third-party код что-то на компе недозволенное наделает. Так что, не думаю что мой подход уж настолько совсем неправильный, просто надо убедиться, что песочница надежна.

provaton ★★★★★
() автор топика

Возникла необходимость предоставить API для пользователей, с помощью которого дать доступ к некоторым методам и аттрибутам объектов класса.

Сделай REST API. </thread>

ei-grad ★★★★★
()
Ответ на: комментарий от provaton

когда я делал подобную шнягу, то доступ о еспечивал только к атрибутам не начинающимся с одного _ (проверка шла на уровне сервера). М.б. эта ваша шняга тоже что то подобное умеет?

ну и кстати, если косвенная адресация (т.е. класс-обертка в котором лежит только ключ от реального объекта, а сам объект в гло альной таблице которая не видна), то тоже должно работать.

AIv ★★★★★
()
Последнее исправление: AIv (всего исправлений: 1)

Лучшее, что ты можешь сделать без какой-либо системы авторизации это запихать все в замыкания. Достать можно, но не каждый осилит.

anonymous
()
Ответ на: комментарий от anonymous

У ТС-а судя по всему тонкий клиент на основе интроспекции. Таблицы глобальные могут через клиента и не пробрасываться.

Кстати, тупая перегрузка __getattribute__ тоже может помочь.

AIv ★★★★★
()

Помоему в чужое замыкание влезть нельзя, так что сработает что-то вот такое:

def get_safe_accessor(proxied):
    class SafeProxy(object):
        def __getattribute__(self, name):
           if name == '_SafeProxy__proxied':
               return super(SafeProxy, self).__getattribute__(name)
           if name in allowed_attrs.get(self.__proxied.__class__.__name__, []):
               return res

    return SafeProxy()

в итоге proxied у тебя будет в лексическом контексте, но при этом не доступен через SafeProxy.

maxcom ★★★★★
()
Ответ на: комментарий от maxcom

И оно реально работает! Еще раз спасибо.

provaton ★★★★★
() автор топика
Ответ на: комментарий от true_admin

У тебя не pure-js, в этом проблема.

В бровзере тоже не pure-js, глобальные объекты типа document и window там ведь присутствуют. На pure-js разве что факториалы да числа фибоначчи считать можно.

provaton ★★★★★
() автор топика
Ответ на: комментарий от provaton

браузеры специально проектируются с учётом безопасности.

true_admin ★★★★★
()
Ответ на: комментарий от maxcom

в итоге proxied у тебя будет в лексическом контексте, но при этом не доступен через SafeProxy.

А для обхода защиты нужно будет догадаться, как называется allowed_attrs.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Ы?

In [3]: ctxt.eval('allowed_attrs')
---------------------------------------------------------------------------
ReferenceError                            Traceback (most recent call last)
/home/user/venvs/project/lib/python2.7/site-packages/django/core/management/commands/shell.pyc in <module>()
----> 1 ctxt.eval('allowed_attrs')

ReferenceError: ReferenceError: allowed_attrs is not defined (  @ 1 : 0 )  -> allowed_attrs
provaton ★★★★★
() автор топика
Ответ на: комментарий от tailgunner

А для обхода защиты нужно будет догадаться, как называется allowed_attrs.

Я python не настолько хорошо понимаю чтобы понять что он делает :-) Можно просто делегировать публичные методы

maxcom ★★★★★
()
Ответ на: комментарий от provaton

Ы?

def get_safe_accessor(proxied):
    class SafeProxy(object):
        def __getattribute__(self, name):
           if name == '_SafeProxy__proxied':
               return super(SafeProxy, self).__getattribute__(name)
           if name in allowed_attrs.get(self.__proxied.__class__.__name__, []):
               return res
    return SafeProxy()

o = get_safe_accessor(12345)
o.x

дает

Traceback (most recent call last):
  File "a.py", line 12, in <module>
    o.x
  File "a.py", line 6, in __getattribute__
    if name in allowed_attrs.get(self.__proxied.__class__.__name__, []):
NameError: global name 'allowed_attrs' is not defined

Как этот код у тебя работает без глобальной перменной allowed_attrs - не понимаю.

tailgunner ★★★★★
()
Ответ на: комментарий от provaton

Глобальная переменная есть, но API к ней доступа не имеет.

Если ты умеешь прятать глобальные переменные, твое кун-фу сильнее моего %)

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Вот весь код полностью:

import PyV8

class Test(object):
    a = 5
    b = 6
    c = 7

allowed_attrs = {'Test': ['a', 'b']}

def make_proxy(proxied):
    class SafeProxy(object):
        def __getattribute__(self, name):
            if name in allowed_attrs.get(proxied.__class__.__name__, []):
                return proxied.__getattribute__(name)
    return SafeProxy()

class Global(PyV8.JSClass):
    def __init__(self, obj):
        self.obj = make_proxy(obj)


ctxt = PyV8.JSContext(Global(Test()))
ctxt.enter()
print ctxt.eval('obj.a')
print ctxt.eval('obj.b')
print ctxt.eval('obj.c')
try:
    print ctxt.eval('allowed_attrs')
except Exception, e:
    print e

try:
    print ctxt.eval('obj.__module__.allowed_attrs')
except Exception, e:
    print e

Результаты:

5
6
None
ReferenceError: allowed_attrs is not defined (  @ 1 : 0 )  -> allowed_attrs
TypeError: Cannot read property 'allowed_attrs' of null (  @ 1 : 14 )  -> obj.__module__.allowed_attrs

Если сможешь показать как достать через ctxt.eval allowed_attrs, я буду тебе очень сильно признателен. Без иронии. В таком случае я ее тоже в замыкание засуну.

provaton ★★★★★
() автор топика
Последнее исправление: provaton (всего исправлений: 1)
Ответ на: комментарий от provaton

Я не могу проверить - у меня нет PyV8. Если я правильно понимаю твой код, то ты сам решаешь, что экспортировать в контекст - тогда, возможно, до alllowed_attrs и в самом деле нельзя добраться. Правда, я всё равно бы сделал его параметром make_proxy - так и безопаснее, и код чще.

tailgunner ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.