Erişim Kontrolü (Yetkilendirme)
Yetkilendirme, bir kullanıcının örneğin belirli bir kaynağa erişmek veya bir eylemi gerçekleştirmek için yeterli ayrıcalıklara sahip olup olmadığını belirler. Yetkilendirme, daha önce başarılı bir kimlik doğrulaması yapıldığını, yani kullanıcının oturum açtığını varsayar.
Örneklerde, mevcut kullanıcıyı temsil eden ve bağımlılık
enjeksiyonu kullanarak geçirerek elde ettiğiniz Nette\Security\User sınıfından bir nesne kullanacağız.
Sunucularda $user = $this->getUser()
adresini çağırmanız yeterlidir.
Kullanıcı haklarının ayırt edilmediği çok basit yönetimli web siteleri için, zaten bilinen yöntemi bir yetkilendirme
kriteri olarak kullanmak mümkündür isLoggedIn()
. Başka bir deyişle: bir kullanıcı oturum açtığında, tüm
eylemler için izinlere sahip olur ve bunun tersi de geçerlidir.
if ($user->isLoggedIn()) { // kullanıcı giriş yaptı mı?
deleteItem(); // eğer öyleyse, bir öğeyi silebilir
}
Roller
Rollerin amacı daha hassas bir izin yönetimi sunmak ve kullanıcı adından bağımsız kalmaktır. Kullanıcı oturum açar
açmaz, kendisine bir veya daha fazla rol atanır. Rollerin kendileri basit dizeler olabilir, örneğin, admin
,
member
, guest
, vb. Bunlar SimpleIdentity
kurucusunun ikinci argümanında bir dize ya da
dizi olarak belirtilir.
Bir yetkilendirme kriteri olarak, şimdi kullanıcının verilen rolde olup olmadığını kontrol eden isInRole()
yöntemini kullanacağız:
if ($user->isInRole('admin')) { // kullanıcıya yönetici rolü atanmış mı?
deleteItem(); // eğer öyleyse, bir öğeyi silebilir
}
Bildiğiniz gibi, kullanıcının oturumu kapatması kimliğini silmemektedir. Bu nedenle, getIdentity()
yöntemi,
verilen tüm roller de dahil olmak üzere SimpleIdentity
nesnesini döndürmeye devam eder. Nette Framework „daha
az kod, daha fazla güvenlik“ ilkesine bağlıdır, bu nedenle rolleri kontrol ederken kullanıcının oturum açıp
açmadığını da kontrol etmeniz gerekmez. isInRole()
yöntemi etkili roller ile çalışır, yani
kullanıcı oturum açmışsa, kimliğe atanan roller kullanılır, oturum açmamışsa, bunun yerine otomatik bir özel rol
guest
kullanılır.
Yetkilendirici
Rollere ek olarak, kaynak ve operasyon terimlerini de tanıtacağız:
- rol** bir kullanıcı niteliğidir – örneğin moderatör, editör, ziyaretçi, kayıtlı kullanıcı, yönetici, …
- kaynak** uygulamanın mantıksal bir birimidir – makale, sayfa, kullanıcı, menü öğesi, anket, sunucu, …
- işlem** kullanıcının kaynak ile yapabileceği veya yapamayacağı belirli bir faaliyettir – görüntüleme, düzenleme, silme, oylama, …
Yetkilendirici, belirli bir rolün belirli bir kaynak ile belirli bir işlem gerçekleştirme iznine sahip
olup olmadığına karar veren bir nesnedir. Sadece bir yöntemle Nette\Security\Authorizator arayüzünü uygulayan
bir nesnedir isAllowed()
:
class MyAuthorizator implements Nette\Security\Authorizator
{
public function isAllowed($role, $resource, $operation): bool
{
if ($role === 'admin') {
return true;
}
if ($role === 'user' && $resource === 'article') {
return true;
}
// ...
return false;
}
}
Yetkilendiriciyi DI konteynerinin bir servisi olarak yapılandırmaya ekliyoruz:
services:
- MyAuthorizator
Ve aşağıda bir kullanım örneği verilmiştir. Bu kez yetkilendiricinin değil
Nette\Security\User::isAllowed()
yöntemini çağırdığımıza dikkat edin, bu nedenle ilk parametre
$role
değildir. Bu yöntem MyAuthorizator::isAllowed()
adresini tüm kullanıcı rolleri için sırayla
çağırır ve en az birinin izni varsa true değerini döndürür.
if ($user->isAllowed('file')) { // kullanıcının 'file' kaynağı ile her şeyi yapmasına izin veriliyor mu?
useFile();
}
if ($user->isAllowed('file', 'delete')) { // kullanıcının bir 'dosya' kaynağını silmesine izin veriliyor mu?
deleteFile();
}
Her iki argüman da isteğe bağlıdır ve varsayılan değerleri her şey anlamına gelir.
İzin ACL
Nette, izin ve erişim kontrolü için hafif ve esnek bir ACL (Erişim Kontrol Listesi) katmanı sunan Nette\Security\Permission sınıfı olan yerleşik bir yetkilendirici uygulaması ile birlikte gelir. Bu sınıfla çalıştığımızda, roller, kaynaklar ve bireysel izinler tanımlarız. Ve roller ve kaynaklar hiyerarşiler oluşturabilir. Açıklamak için bir web uygulaması örneği göstereceğiz:
guest
: oturum açmamış, web'in herkese açık bölümünü okumasına ve taramasına izin verilen ziyaretçi, yani makaleleri okumak, yorum yapmak ve anketlerde oy kullanmakregistered
: oturum açmış kullanıcı, bunun üzerine yorum gönderebiliradmin
: makaleleri, yorumları ve anketleri yönetebilir
Bu nedenle, belirli roller tanımladık (guest
, registered
ve admin
) ve
kullanıcıların erişebileceği veya üzerinde işlem yapabileceği kaynakları (article
, comments
,
poll
) belirttik (view
, vote
, add
, edit
).
Permission sınıfının bir örneğini oluşturur ve rolleri tanımlarız. Rollerin kalıtımını kullanmak
mümkündür, bu da örneğin admin
rolüne sahip bir kullanıcının sıradan bir web sitesi ziyaretçisinin
yapabildiklerini (ve tabii ki daha fazlasını) yapabilmesini sağlar.
$acl = new Nette\Security\Permission;
$acl->addRole('guest');
$acl->addRole('registered', 'guest'); // 'registered', 'guest'den miras alınır
$acl->addRole('admin', 'registered'); // ve 'admin' 'registered'dan miras alır
Şimdi kullanıcıların erişebileceği kaynakların bir listesini tanımlayacağız:
$acl->addResource('article');
$acl->addResource('comment');
$acl->addResource('poll');
Kaynaklar kalıtım da kullanabilir, örneğin $acl->addResource('perex', 'article')
adresini
ekleyebiliriz.
Ve şimdi en önemli şey. Kimin ne yapabileceğini belirleyen kuralları aralarında tanımlayacağız:
// şimdi her şey reddedildi
// misafirin makaleleri, yorumları ve anketleri görüntülemesine izin verin
$acl->allow('guest', ['article', 'comment', 'poll'], 'view');
// ve ayrıca anketlerde oy kullanabilir
$acl->allow('guest', 'poll', 'vote');
// registered guesta'dan izinleri miras alır, ayrıca yorum yapmasına da izin vereceğiz
$acl->allow('registered', 'comment', 'add');
// yönetici her şeyi görüntüleyebilir ve düzenleyebilir
$acl->allow('admin', $acl::All, ['view', 'edit', 'add']);
Birinin bir kaynağa erişmesini engellemek istersek ne olur?
// administrator anketleri düzenleyemez, bu antidemokratik olur.
$acl->deny('admin', 'poll', 'edit');
Şimdi kurallar setini oluşturduğumuzda, basitçe yetkilendirme sorgularını sorabiliriz:
// misafir makaleleri görüntüleyebilir mi?
$acl->isAllowed('guest', 'article', 'view'); // true
// misafir bir makaleyi düzenleyebilir mi?
$acl->isAllowed('guest', 'article', 'edit'); // false
// misafir anketlerde oy kullanabilir mi?
$acl->isAllowed('guest', 'poll', 'vote'); // true
// misafir yorum ekleyebilir mi?
$acl->isAllowed('guest', 'comment', 'add'); // false
Aynı şey kayıtlı bir kullanıcı için de geçerlidir, ancak o da yorum yapabilir:
$acl->isAllowed('registered', 'article', 'view'); // true
$acl->isAllowed('registered', 'comment', 'add'); // true
$acl->isAllowed('registered', 'comment', 'edit'); // false
Yönetici, anketler hariç her şeyi düzenleyebilir:
$acl->isAllowed('admin', 'poll', 'vote'); // true
$acl->isAllowed('admin', 'poll', 'edit'); // false
$acl->isAllowed('admin', 'comment', 'edit'); // true
İzinler dinamik olarak da değerlendirilebilir ve kararı tüm parametrelerin aktarıldığı kendi geri çağrımıza bırakabiliriz:
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
return /* ... */;
};
$acl->allow('registered', 'comment', null, $assertion);
Ancak, rollerin ve kaynakların adlarının yeterli olmadığı bir durumu nasıl çözebiliriz, yani örneğin, bir rolün
registered
bir kaynağı article
yalnızca yazarı ise düzenleyebileceğini tanımlamak istiyoruz?
Dizeler yerine nesneler kullanacağız, rol Nette\Security\Role nesnesi ve kaynak Nette\Security\Resource olacak. Metotları
getRoleId()
resp. getResourceId()
orijinal dizgileri döndürecektir:
class Registered implements Nette\Security\Role
{
public $id;
public function getRoleId(): string
{
return 'registered';
}
}
class Article implements Nette\Security\Resource
{
public $authorId;
public function getResourceId(): string
{
return 'article';
}
}
Ve şimdi bir kural oluşturalım:
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
$role = $acl->getQueriedRole(); // nesne Kayıtlı
$resource = $acl->getQueriedResource(); // object Article
return $role->id === $resource->authorId;
};
$acl->allow('registered', 'article', 'edit', $assertion);
ACL, nesneler geçirilerek sorgulanır:
$user = new Registered(/* ... */);
$article = new Article(/* ... */);
$acl->isAllowed($user, $article, 'edit');
Bir rol, bir veya daha fazla başka rolden miras alabilir. Ancak, bir atanın belirli bir eyleme izin vermesi ve diğerinin bunu reddetmesi durumunda ne olur? O zaman rol ağırlığı devreye girer – miras alınacak roller dizisindeki son rol en büyük, ilk rol ise en düşük ağırlığa sahiptir:
$acl = new Nette\Security\Permission;
$acl->addRole('admin');
$acl->addRole('guest');
$acl->addResource('backend');
$acl->allow('admin', 'backend');
$acl->deny('guest', 'backend');
// örnek A: admin rolü guest rolünden daha düşük ağırlığa sahiptir
$acl->addRole('john', ['admin', 'guest']);
$acl->isAllowed('john', 'backend'); // false
// örnek B: admin rolü guest rolünden daha fazla ağırlığa sahiptir
$acl->addRole('mary', ['guest', 'admin']);
$acl->isAllowed('mary', 'backend'); // true
Roller ve kaynaklar da kaldırılabilir (removeRole()
, removeResource()
), kurallar da geri
alınabilir (removeAllow()
, removeDeny()
). Tüm doğrudan üst rollerin dizisi
getRoleParents()
döndürür. İki varlığın birbirinden miras alıp almadığı roleInheritsFrom()
ve
resourceInheritsFrom()
döndürür.
Hizmet Olarak Ekle
Oluşturduğumuz ACL'yi $user
nesnesi tarafından kullanılabilmesi için yani örneğin
$user->isAllowed('article', 'view')
kodunda kullanabilmemiz için yapılandırmaya bir servis olarak eklememiz
gerekiyor. Bu amaçla bunun için bir fabrika yazacağız:
namespace App\Model;
class AuthorizatorFactory
{
public static function create(): Nette\Security\Permission
{
$acl = new Nette\Security\Permission;
$acl->addRole(/* ... */);
$acl->addResource(/* ... */);
$acl->allow(/* ... */);
return $acl;
}
}
Ve bunu yapılandırmaya ekleyeceğiz:
services:
- App\Model\AuthorizatorFactory::create
Sunucularda, örneğin startup()
yönteminde izinleri doğrulayabilirsiniz:
protected function startup()
{
parent::startup();
if (!$this->getUser()->isAllowed('backend')) {
$this->error('Forbidden', 403);
}
}