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


Backend для мессенджера написан на Go, поэтому и примеры будут на этом языке. Не желая изобретать велосипед, мы решили взять за основу XACML — стандарт для ABAC (Attribute-Based Access Control) — и максимально упростили его, чтобы он подходил для нашей задачи. Хотим отметить, что мы не ставили перед собой цель написать собственную реализацию XACML. Он был взят как пример работающей системы, из которого мы могли бы извлечь нужный для нас опыт.

Для знакомства с XACML и ABAC есть отличные статьи:

> Знакомство с XACML — стандартом для Attribute-Based Access Control
> Подходы к контролю доступа: RBAC vs. ABAC

Базовые объекты системы и интерфейс AttributeCalculater


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

type AttributeCalculater interface {
   Counter() (AttributeCalculater, error)
   Equally(Getter) bool
   Belong(Getter) bool
   GetType() string
   GetValue() (AttributeCalculater, error)
   GetValueField(string) (AttributeCalculater, error)
   GetInt() (int, error)
   GetBool() (bool, error)
   GetString() (string, error)
}

В системе используются два основных типа объектов: Правила и Политики. Для нашей задачи этого достаточно, поэтому группы политик мы не стали использовать за ненадобностью.

Правило (rule)


Правило — это самый простой объект в системе, который используется для описания бизнес-правил. Вот его структура:

type rule struct {
   Name      string
   Condition condition
   Effect    ruleEffect
}

func (r *rule) calculate(cntx *Context) calculateResult {}

Метод calculate вычисляет правило, то есть возвращает значение, которое говорит, может запрос быть выполненным или нет, или сообщает о возникшей ошибке. Для этого в метод передается контекст, в котором содержится вся необходимая информация для вычисления.

type Context struct {
       Object     AttributeCalculater
   Subject    AttributeCalculater
   Target     Action
}

Контекст состоит из объекта (object) — сущности, которая реализует интерфейс AttributeCalculater и с которой система доступа выполняет какие-то действия, субъекта (subject) — сущности, которая так же поддерживает интерфейс AttributeCalculater и обычно является пользователем, желающим выполнить определенное действие в приложении. Цель (target) является enum'ом, в котором перечислены все возможные действия, поддерживающиеся системой доступа. Например: добавить пользователя в переписку, написать сообщение, назначить админа переписки и т.д.

Name (имя) правила нужно прежде всего для человека, который с ним работает — на сам процесс вычисления оно никак не влияет.

Effect (эффект) показывает, как будет обрабатываться результат вычисления условия. Эффект может быть разрешающим (permitEffect) или запрещающим (denyEffect). Скажем, если результат вычисления условия — true, а эффект задан как permitEffect, то результат вычисления правила также будет true. Если же эффект прописан как denyEffect, то результатом правила будет false.

Condition (условие) — самая главная часть правила, в которой, собственно, описывается и высчитывается условие для него.

type condition struct {
       FirstOperand  Attribute
   SecondOperand Attribute
   Operator      conditionOperator
}

func (c *condition) calculate(cntx *Context) (bool, error) {}

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

Operator (оператор) отвечает за то, как сравниваются два операнда. Принимает следующие значения:

  • equally - проверяет, равны ли операнды.
  • notEqually — проверяет, не равны ли операнды.
  • belong — проверяет, есть ли между операндами какое-то отношение. Конкретная логика зависит от реализации интерфейса AttributeCalculater, который должны реализовать объекты, работающие с системой доступа. Подробнее об этом расскажем ниже.
  • notBelong — противоположность belong.

Как видно из структуры, условие может быть вычислено только для двух операндов, то есть в одном правиле не предполагается рассмотрение сложных use case'ов, которые возникают при работе с системой доступа. Для этого используются комбинации правил.

Тип данных операндов — это Attribute (атрибут). В нем находится информация вычисления условий.

type Attribute struct {
       NameObject string
   Field      string
   Type       TypeAttribute
   Object     AttributeCalculater
}

func (a *Attribute) getValue(c *Context) (AttributeCalculater, error) {}

Метод getValue возвращает значение атрибута. Возвращается оно в виде объекта, у которого реализован интерфейс AttributeCalculater.

NameObject (имя объекта), у которого при вычислении атрибута берется значение из поля Field. Сам же объект находится в поле Object.

Политика (politic)


Политики используются для описания бизнес-правил с одним или несколькими правилами.

type politic struct {
   Name             string
   Target           Action
   Rules            []rule
   CombineAlgorithm combineAlgorithm
}

func (p *politic) calculate(cntx *Context) calculateResult {}

Name (имя) у политики, как и у правила, нужно только для человека, который с ней работает.

Targer (цель) используется для поиска политик. Для простоты в нашей системе у каждой цели своя политика.

Rules (правила) — это набор простых правил, которые дополняя друг друга могут описывать сложные бизнес-правила.

CombineAlgorithm (алгоритм комбинирования) правил указывает на то, как обрабатывать результаты вычисления правил политики. В XACML эти алгоритмы могут быть достаточно сложными, но в нашем случае все эти изыски не нужны. Поэтому у нас пока всего два простых алгоритма permitIfAllPermitted (разрешить, если все разрешили) и permitIfOnePermitted (разрешить, если один разрешил). Например, если стоит алгоритм permitIfAllPermitted, то результат вычисления политики будет положительным только при том условии, что все правила в этой политике также имели положительный результат.

Вот основные части, из которых состоит наша система доступа. В следующей статье мы подробно рассмотрим, как это все работает.
Поделиться с друзьями
-->

Комментарии (0)