İ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.

Kurulum ve Gereksinimler

Ö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çi
  • registered: 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);
	}
}
versiyon: 4.0