İzinleri Doğrulama (Yetkilendirme)
Yetkilendirme, bir kullanıcının örneğin belirli bir kaynağa erişmek veya belirli bir eylemi gerçekleştirmek için yeterli izinlere sahip olup olmadığını belirler. Yetkilendirme, önceki başarılı kimlik doğrulamasını, yani kullanıcının giriş yapmış olmasını varsayar.
Örneklerde, mevcut kullanıcıyı temsil eden Nette\Security\User sınıfının nesnesini
kullanacağız ve buna dependency injection
kullanarak erişebilirsiniz. Presenter'larda sadece $user = $this->getUser()
çağırmanız yeterlidir.
Kullanıcı izinlerinin ayırt edilmediği, yönetimi olan çok basit web sitelerinde, yetkilendirme kriteri olarak zaten
bilinen isLoggedIn()
metodu kullanılabilir. Başka bir deyişle: kullanıcı giriş yaptığında, tüm izinlere
sahiptir ve tersi de geçerlidir.
if ($user->isLoggedIn()) { // kullanıcı giriş yapmış mı?
deleteItem(); // o zaman operasyon için izni var
}
Roller
Rollerin amacı, daha hassas izin kontrolü sunmak ve kullanıcı adından bağımsız kalmaktır. Her kullanıcıya giriş
yaparken, içinde hareket edeceği bir veya daha fazla rol atanır. Roller, örneğin admin
, member
,
guest
gibi basit dizeler olabilir. SimpleIdentity
yapıcısının ikinci parametresi olarak, ya bir dize
ya da bir dize dizisi – roller – olarak belirtilirler.
Yetkilendirme kriteri olarak şimdi, kullanıcının belirli bir rolde olup olmadığını söyleyen isInRole()
metodunu kullanacağız:
if ($user->isInRole('admin')) { // kullanıcı admin rolünde mi?
deleteItem(); // o zaman operasyon için izni var
}
Bildiğiniz gibi, kullanıcı oturumdan çıktıktan sonra kimliği silinmeyebilir. Yani, getIdentity()
metodu
hala verilen tüm roller dahil olmak üzere SimpleIdentity
nesnesini döndürür. Nette Framework, “daha az kod,
daha fazla güvenlik” ilkesini benimser, burada daha az yazmak daha güvenli koda yol açar, bu nedenle rolleri kontrol ederken
kullanıcının giriş yapıp yapmadığını da kontrol etmeniz gerekmez. isInRole()
metodu etkin rollerle
çalışır, yani kullanıcı giriş yapmışsa, kimlikte belirtilen rollere dayanır, giriş yapmamışsa otomatik olarak özel
guest
rolüne sahiptir.
Yetkilendirici
Rollere ek olarak, kaynak ve operasyon kavramlarını da tanıtacağız:
- rol kullanıcının bir özelliğidir – örn. moderatör, editör, ziyaretçi, kayıtlı kullanıcı, yönetici…
- kaynak (resource) web sitesinin mantıksal bir öğesidir – makale, sayfa, kullanıcı, menü öğesi, anket, presenter, …
- operasyon (operation) kullanıcının kaynakla yapabileceği veya yapamayacağı belirli bir faaliyettir – örneğin silme, düzenleme, oluşturma, oy verme, …
Yetkilendirici, belirli bir rolün belirli bir kaynakla belirli bir operasyonu gerçekleştirme iznine
sahip olup olmadığına karar veren bir nesnedir. Tek bir isAllowed()
metoduna sahip Nette\Security\Authorizator arayüzünü
uygulayan bir nesnedir:
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 ekleyeceğiz:
services:
- MyAuthorizator
Ve kullanım örneği aşağıdadır. Dikkat, bu sefer Nette\Security\User::isAllowed()
metodunu çağırıyoruz,
yetkilendiriciyi değil, bu yüzden ilk parametre $role
yok. Bu metot, MyAuthorizator::isAllowed()
'ı
kullanıcının tüm rolleri için sırayla çağırır ve en az birinin izni varsa true döndürür.
if ($user->isAllowed('file')) { // kullanıcı 'file' kaynağıyla herhangi bir şey yapabilir mi?
useFile();
}
if ($user->isAllowed('file', 'delete')) { // 'file' kaynağı üzerinde 'delete' gerçekleştirebilir mi?
deleteFile();
}
Her iki parametre de isteğe bağlıdır, varsayılan null
değeri herhangi bir şey
anlamına gelir.
Permission ACL
Nette, izinleri ve erişimleri yönetmek için programcıya hafif ve esnek bir ACL (Erişim Kontrol Listesi) katmanı sağlayan yerleşik bir yetkilendirici uygulaması olan Nette\Security\Permission sınıfıyla birlikte gelir. Onunla çalışmak, rolleri, kaynakları ve bireysel izinleri tanımlamaktan ibarettir. Roller ve kaynaklar hiyerarşiler oluşturmaya izin verir. Açıklamak için bir web uygulaması örneği göstereceğiz:
guest
: web sitesinin genel bölümünü okuyabilen ve gezebilen, yani makaleleri, yorumları okuyabilen ve anketlerde oy kullanabilen giriş yapmamış ziyaretçiregistered
: ayrıca yorum yapabilen giriş yapmış kayıtlı kullanıcıadmin
: makaleleri, yorumları ve anketleri yönetebilir
Böylece belirli roller (guest
, registered
ve admin
) tanımladık ve kullanıcıların
belirli bir rolle erişebileceği veya belirli operasyonları (view
, vote
, add
,
edit
) gerçekleştirebileceği kaynakları (article
, comment
, poll
)
belirttik.
Permission sınıfının bir örneğini oluşturacağız ve rolleri tanımlayacağız. Rollerin kalıtımını
kullanabiliriz, bu da örneğin yönetici (admin
) rolüne sahip bir kullanıcının sıradan bir web sitesi
ziyaretçisinin yapabildiği şeyleri (ve tabii ki daha fazlasını) yapabilmesini sağlar.
$acl = new Nette\Security\Permission;
$acl->addRole('guest');
$acl->addRole('registered', 'guest'); // 'registered', 'guest'ten miras alır
$acl->addRole('admin', 'registered'); // ve ondan 'admin' miras alır
Şimdi kullanıcıların erişebileceği kaynakların listesini de tanımlayacağız.
$acl->addResource('article');
$acl->addResource('comment');
$acl->addResource('poll');
Kaynaklar da kalıtım kullanabilir, örneğin $acl->addResource('perex', 'article')
belirtmek mümkün
olurdu.
Ve şimdi en önemli kısım. Kimin neyi neyle yapabileceğini belirleyen kuralları aralarında tanımlayacağız:
// başlangıçta kimse hiçbir şey yapamaz
// guest'in makaleleri, yorumları ve anketleri görüntülemesine izin ver
$acl->allow('guest', ['article', 'comment', 'poll'], 'view');
// ve anketlerde ayrıca oy kullanmasına izin ver
$acl->allow('guest', 'poll', 'vote');
// kayıtlı, guest'ten hakları miras alır, ona ayrıca yorum yapma hakkı verelim
$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 belirli bir kaynağa erişimini engellemek istersek ne olur?
// yönetici anketleri düzenleyemez, bu demokratik olmazdı
$acl->deny('admin', 'poll', 'edit');
Şimdi kurallar listesini oluşturduğumuza göre, yetkilendirme sorgularını kolayca sorabiliriz:
// guest makaleleri görüntüleyebilir mi?
$acl->isAllowed('guest', 'article', 'view'); // true
// guest makaleleri düzenleyebilir mi?
$acl->isAllowed('guest', 'article', 'edit'); // false
// guest anketlerde oy kullanabilir mi?
$acl->isAllowed('guest', 'poll', 'vote'); // true
// guest yorum yapabilir mi?
$acl->isAllowed('guest', 'comment', 'add'); // false
Aynısı kayıtlı kullanıcı için de geçerlidir, ancak o yorum da 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 parametreleri alan kendi geri aramamıza bırakabiliriz:
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
return /* ... */;
};
$acl->allow('registered', 'comment', null, $assertion);
Ancak, örneğin rol ve kaynak adlarının yeterli olmadığı, ancak örneğin registered
rolünün
article
kaynağını yalnızca yazarıysa düzenleyebileceğini tanımlamak istediğimiz bir durumu nasıl ele
alırız? Dizeler yerine nesneler kullanacağız, rol Nette\Security\Role nesnesi ve kaynak Nette\Security\Resource nesnesi olacaktır.
getRoleId()
ve getResourceId()
metotları orijinal dizeleri 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 kuralı oluşturacağız:
$assertion = function (Permission $acl, string $role, string $resource, string $privilege): bool {
$role = $acl->getQueriedRole(); // Registered nesnesi
$resource = $acl->getQueriedResource(); // Article nesnesi
return $role->id === $resource->authorId;
};
$acl->allow('registered', 'article', 'edit', $assertion);
Ve ACL sorgusu nesneleri aktararak gerçekleştirilir:
$user = new Registered(/* ... */);
$article = new Article(/* ... */);
$acl->isAllowed($user, $article, 'edit');
Bir rol, başka bir rolden veya birden fazla rolden miras alabilir. Ancak bir atanın eylemi yasaklanmış ve diğerinin izin verilmişse ne olur? Torunun hakları ne olacak? Rolün ağırlığına göre belirlenir – ata listesinde son belirtilen rol en büyük ağırlığa sahiptir, ilk belirtilen rol en küçük ağırlığa sahiptir. Örnekten daha açıklayıcıdır:
$acl = new Nette\Security\Permission;
$acl->addRole('admin');
$acl->addRole('guest');
$acl->addResource('backend');
$acl->allow('admin', 'backend');
$acl->deny('guest', 'backend');
// durum A: admin rolü guest rolünden daha az ağırlığa sahip
$acl->addRole('john', ['admin', 'guest']);
$acl->isAllowed('john', 'backend'); // false
// durum B: admin rolü guest'ten daha fazla ağırlığa sahip
$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 ebeveyn rollerinin dizisini
getRoleParents()
döndürür, iki varlığın birbirinden miras alıp almadığını roleInheritsFrom()
ve resourceInheritsFrom()
döndürür.
Servis Olarak Ekleme
Oluşturduğumuz ACL'yi bir servis olarak yapılandırmaya aktarmamız gerekiyor, böylece $user
nesnesi onu
kullanmaya başlar, yani kodda örneğin $user->isAllowed('article', 'view')
kullanmak mümkün olur. Bu amaçla
onun 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 onu yapılandırmaya ekleyeceğiz:
services:
- App\Model\AuthorizatorFactory::create
Presenter'larda daha sonra izinleri örneğin startup()
metodunda doğrulayabilirsiniz:
protected function startup()
{
parent::startup();
if (!$this->getUser()->isAllowed('backend')) {
$this->error('Yasak', 403);
}
}