Après avoir écrit l'article précédent sur le langage PERM et la bibliothèque Casbin , des questions se sont posées . Et pas seulement une personne, et je voulais d'abord répondre dans un commentaire, mais j'ai réalisé que le volume du matériel va au-delà du commentaire habituel, je vais donc présenter cette réponse sous la forme d'un article séparé.
Pendant longtemps, je n'ai pas pu comprendre la construction spécifique qui se trouvait dans la tête des questionneurs, mais à la fin, après avoir clarifié les questions, j'ai reçu des réponses, dont je vais donner la compilation dans la citation.
Et comment avec de tels DSL le problème est-il résolu? Afficher la liste des objets que je peux voir? Il est nécessaire de traduire cela en une requête SQL, et non de supprimer tous les enregistrements de la base de données.
Il y a une interface sur le site montrant une liste de quelque chose. Disons - articles dans la zone d'administration du CMS. Il existe des dizaines de milliers d'articles dans la base de données, mais en général, l'utilisateur n'a accès qu'à une douzaine. Comment obtenir des articles de la base de données visibles par un utilisateur spécifique? Eh bien, si nous avons toutes les règles que tout le monde peut voir - retirées du code dans une sorte de DSL?
En d'autres termes - comment écrire une requête comme
select * from articles a
join roles r on r.userId = currentUserId
where article.owner = currentUserId
OR (r.role in ['admin', 'supevisor']) - administrateur du total
OR (r.domain = currentDomainId AND r.role in ['domain-admin', 'domain-supervisor']) - administrateur du domaine
J'ai de telles règles dans le code, sous la forme d'expressions LINQ, et je peux résoudre ce problème. Et une telle tâche survient encore plus souvent que "vérifier s'il y a accès à un objet déchargé de la mémoire"
J'espère avoir bien compris cette construction et lors de la rétro-ingénierie, j'ai pu extraire les données initiales pour résoudre ce problème. Pour commencer, nous nous passerons de l'utilisation de la multi-location (domaines), car ils compliquent la tâche et, par conséquent, la compréhension. J'ai donné un exemple de leur utilisation dans le dernier article.
, , , 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 .
J'ai posté un exemple de code entièrement fonctionnel et autonome que j'ai utilisé pour écrire cet article sur mon Github , vous pouvez télécharger et jouer si vous êtes intéressé et que vous le souhaitez.