Nach dem Schreiben des vorherigen Artikels über die PERM-Sprache und die Casbin-Bibliothek stellten sich Fragen . Und nicht nur eine Person, und ich wollte zuerst in einem Kommentar antworten, sondern mir wurde klar, dass das Volumen des Materials über den üblichen Kommentar hinausgeht. Deshalb werde ich diese Antwort in Form eines separaten Artikels präsentieren.
Lange Zeit konnte ich die spezifische Konstruktion, die sich im Kopf der Fragesteller befand, nicht verstehen, aber am Ende erhielt ich nach Klärung der Fragen Antworten, deren Zusammenstellung ich im Zitat geben werde.
Und wie wird mit solchen DSLs das Problem gelöst? Zeigen Sie die Liste der Objekte, die ich sehen kann? Es ist notwendig, dies irgendwie in eine SQL-Abfrage zu übersetzen, um nicht alle Datensätze aus der Datenbank zu entfernen.
Es gibt eine Oberfläche auf der Site, die eine Liste von etwas zeigt. Angenommen, Artikel im CMS-Administrationsbereich. Es gibt Zehntausende von Artikeln aus der Datenbank, aber normalerweise hat der Benutzer nur Zugriff auf ein Dutzend. Wie erhalte ich Artikel aus der Datenbank, die für einen bestimmten Benutzer sichtbar sind? Nun, wenn wir alle Regeln haben, die jeder sehen kann - aus dem Code in eine Art DSL herausgenommen?
Mit anderen Worten - wie schreibe ich eine Abfrage wie
select * aus Artikeln und
Join-Rollen r auf r.userId = currentUserId,
wobei article.owner = currentUserId
OR (r.role in ['admin', 'supevisor']) - admin des gesamten
OR (r.domain = currentDomainId AND r.role in ['domain-admin', 'domain-Supervisor']) - admin des Domäne
Ich habe solche Regeln im Code in Form von LINQ-Ausdrücken, und ich kann dieses Problem lösen. Und eine solche Aufgabe tritt noch häufiger auf als "Überprüfen, ob Zugriff auf ein aus dem Speicher entladenes Objekt besteht".
Ich hoffe, ich habe diese Konstruktion richtig verstanden und konnte während des Reverse Reverse Engineering die ersten Daten zur Lösung dieses Problems abrufen. Zunächst werden wir auf die Verwendung von Mandantenfähigkeit (Domains) verzichten, da diese die Aufgabe und dementsprechend das Verständnis erschweren. Ich habe im letzten Artikel ein Beispiel für ihre Verwendung gegeben.
, , , 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 .
Ich habe einen voll funktionsfähigen und autarken Beispielcode veröffentlicht, mit dem ich diesen Artikel auf meinem Github geschrieben habe . Sie können ihn herunterladen und spielen, wenn Sie interessiert und bereit sind.