Depois de escrever o artigo anterior sobre a linguagem PERM e a biblioteca Casbin , surgiram questões . E não apenas uma pessoa, e eu queria responder primeiro em um comentário, mas percebi que o volume do material vai além do comentário usual, então apresentarei esta resposta na forma de um artigo separado.
Por muito tempo não pude entender a construção específica que se assentava na cabeça dos questionadores, mas no final, após esclarecer dúvidas, recebi respostas, cuja compilação darei na citação.
E como com essas DSLs se resolve o problema “mostrar a lista de objetos que posso ver? É necessário traduzir isso de alguma forma em uma consulta SQL, não para retirar todos os registros do banco de dados.
Existe uma interface no site que mostra uma lista de algo. Digamos - artigos na área de administração do CMS. Existem dezenas de milhares de artigos no banco de dados, mas geralmente o usuário tem acesso a apenas uma dúzia. Como obter artigos do banco de dados que são visíveis para um usuário específico? Bem, se tivermos todas as regras que qualquer um pode ver - retiradas do código para algum tipo de DSL?
Em outras palavras - como escrever uma consulta como
selecionar * de artigos e
funções de junção r em r.userId = currentUserId
onde article.owner = currentUserId
OU (r.role in ['admin', 'supevisor']) - admin do total
OU (r.domain = currentDomainId AND r.role in ['domain-admin', 'domain-supervisor']) - admin do domínio
Eu tenho essas regras no código, na forma de expressões LINQ, e posso resolver esse problema. E tal tarefa surge com ainda mais frequência do que "verificar se há acesso a um objeto descarregado da memória"
Espero ter entendido esta construção corretamente e, durante a engenharia reversa, consegui extrair os dados iniciais para resolver esse problema. Para começar, dispensaremos o uso de multilocação (domínios), uma vez que complicam a tarefa e, consequentemente, o entendimento. Dei um exemplo de seu uso no último artigo.
, , , Casbin.
CMS, . user
. , admin
supervisor
. supervisor
, admin
supervisor
, , .
, :
CMS:

— Users:

:
— Roles:

, Piter , Bob — . Alice , , .
— Articles:

, (Piter, id=3) :
select * from articles a
left join roles r on r.userId = 3
where a.owner = 3
OR (r.role in ('admin', 'supevisor'))

(Bob, id=2) :
select * from articles a
left join roles r on r.userId = 2
where a.owner = 2
OR (r.role in ('admin', 'supevisor'))

(Alice, id=1) :
select * from articles a
left join roles r on r.userId = 1
where a.owner = 1
OR (r.role in ('admin', 'supevisor'))

, Casbin.
Casbin
PERM — , .
.. , () . ( Id=1 ).
, , — RBAC.
RBAC , . RBAC user
, author
user
(.. ), , admin
.
, , . user
supervisor
admin
, — , . , user
, . admin
supervisor
, .
RBAC, , -, , .
: RBAC vs. ABAC
, (user
, supervisor
,admin
) — —
. , . , , — .
" "
[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
, Roles. . *.csv
, . cvs rbac_policy.csv
:
p, user, article, read p, user, article, modify p, user, article, create p, user, article, delete g, supervisor, user g, admin, supervisor g, 1, user g, 2, supervisor g, 3, admin
, user
, , . supervisor
user.
admin
supervisor
.
alice(1) user
, bob(2) supervisor
, piter(3) — admin
.
, , .
, . cross-cutting concern CQRS+MediatR
public IList<Article> GetArticlesForAdminPanel(int currentUserId)
{
var e = new Enforcer("CasbinConfig/rbac_model.conf", "CasbinConfig/rbac_policy.csv");
var obj = "article";
var act = "read";
// ,
if (e.Enforce(currentUserId.ToString(), obj, act))
{
//
var currentUserRoles = e.GetRolesForUser(currentUserId.ToString());
//,
var isAdmin = currentUserRoles.Any(x => x == "admin" || x == "supervisor");
// , , ,
if (!isAdmin) return _context.Articles.Where(x => x.OwnerId == currentUserId).ToList();
else return _context.Articles.ToList();
}
else
{
// ,
throw new Exception("403. ");
}
}
! , .
" "
. , , - . , , user
, supervisor
admin
.
, rbac_with_abac_model.conf
:
[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = (r.sub == r.obj.OwnerId.ToString() || g(r.sub, "supervisor")) && g(r.sub, p.sub) && r.act == p.act
, [matchers]
, r.obj == p.obj
(r.sub == r.obj.OwnerId.ToString() || g(r.sub, "supervisor"))
. r.sub (id ) r.obj.OwnerId (id ) r.sub "supervisor". admin
supervisor
admin
.
, . :
public void UpdateArticle(int currentUserId, Article newArticle)
{
var e = new Enforcer("CasbinConfig/rbac_with_abac_model.conf", "CasbinConfig/rbac_policy.csv");
var act = "modify";
//,
if (e.Enforce(currentUserId.ToString(), newArticle, act))
{
//,
_context.Articles.Update(newArticle);
_context.SaveChanges();
}
else
{
// ,
throw new Exception("403. ");
}
}
, e.Enforce
, Article
.
— .
- , user
, supervisor
— , admin
.
- PERM, delete_model.conf
:
[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = (r.sub == r.obj.OwnerId.ToString() || g(r.sub, "admin")) && g(r.sub, p.sub) && r.act == p.act
, , admin
. supervisor
, .
, :
public void DeleteArticle(int currentUserId, Article deleteArticle)
{
var e = new Enforcer("CasbinConfig/delete_model.conf", "CasbinConfig/rbac_policy.csv");
var act = "delete";
//,
if (e.Enforce(currentUserId.ToString(), deleteArticle, act))
{
//
_context.Articles.Remove(deleteArticle);
_context.SaveChanges();
}
else
{
// ,
throw new Exception("403. ");
}
}
, , Casbin PERM .
Casbin DynamicExpresso.Core C# , Casbin .
, Casbin , , API. UI .
Eu postei um código de exemplo totalmente funcional e autossuficiente que usei para escrever este artigo no meu Github , você pode baixar e jogar se estiver interessado e quiser.