Prawa dostępu oparte o adnotacje - PHP

24 Apr 2008

Adnotacje - to ciekawe podejście do zarządzania metadanymi z poziomu kodu źródłowego. Adnotacje pozwalają na opisanie klas, metod i własności dodatkowymi informacjami, które mogą być wykorzystane w dalszym przetwarzaniu. Najprostszym możliwym przykładem jest rozszerzenie obiektu wartości zwracanego przez DAO o informacje dotyczące schematu tabeli (m.in. typ pola, dozwolona wartość null, klucze obce, itp.).

class Account extends ValueObject {

	/**
	 * @Column int
	 * @NotNull
	 */
	public $id;

	/**
	 * @Column varchar
	 * @NotNull
	 */
	public $login;

	...
	...

}

Wyposażając obiekt DAO w zdolność odczytywania adnotacji możemy zautomatyzować elementarną walidację poszczególnych pól obiektu wartości, a nawet tworzyć brakujące tabele i pola w locie. Ponieważ ostatnio dużo zajmowałem się systemami praw dostępu w aplikacjach webowych, przyszła mi do głowy możliwość ożenienia adnotacji z usługą kontroli dostępu do metod kontrolerów. Zerknijmy na przykładowy kontroler i adnotacje @Auth przy jego metodach:

class Users extends Controller {

	/** @Auth users-browse */
	public function index() { ... }

	/** @Auth users-add */
	public function addUser() { ... }

	/** @Auth users-delete */
	public function deleteUser() { ...}

}

Przykład przedstawia klasę kontrolera z metodami zaszeregowanymi do różnych obszarów dostępu (users-browse, users-add, users-delete). Taki sposób oznaczania obszarów aplikacji ma szereg zalet. Pierwszą z nich jest oddzielenie kodu określającego prawa dostępu od kodu logiki biznesowej. Drugą jest duża czytelność i łatwość wprowadzania zmian. Kolejna zaleta to możliwość automatyzacji procesu zbierania informacji o obszarach aplikacji - wystarczy prosty grep by uzyskać listę takich obszarów.

Pora teraz przygotować usługę weryfikacji dostępu. Na potrzeby przykładu ograniczę się do absolutnego minimum. Przede wszystkim schemat tabeli, w której zapisane będą prawa wygląda następująco:

CREATE TABLE app_access (
	user_id		INT,
	role_id		INT,
	area		VARCHAR(200)
);

Usługa sprawdzania dostępu ma m.in. metodę isUserAuthorized:

class AppAccessService {

	public static function isUserAuthorized(User $user, $area) {
		if ($user->isAdmin()) {
			return true;
		}
		$userId = $user->getId();
		$roleId = $user->getRoleId();
		$sql = "
			SELECT * FROM app_access
			WHERE (user_id = $userId OR role_id = $roleId)
				AND area = '$area'
		";
		// reszta obsługi bazy danych...
	}

}

Mamy już informację o obszarach aplikacji oraz usługę sprawdzania dostępu. Brakuje tylko automatyki, która będzie dokonywała tego sprawdzenia przed każdym wywołaniem metody kontrolera. Jeśli Twój framework nie wspiera filtrów wejścia/wyjścia to pozostaje Ci implementacja w konstruktorze kontrolera i odgadywanie metody na podstawie URLa. Jeżeli wspiera i pozwala dowiedzieć się jaki kontroler i jaka metoda zostaną wywołane zanim się to stanie, to fantastycznie. Na potrzeby przykładu zakładam, że zmienne $className i $methodName zawierają nazwę klasy i metody kontrolera.

$r = new ReflectionMethod($className, $methodName);
$doc = $r->getDocComment();
foreach (explode("\n", $doc) as $ln) {
	$ln = trim($ln, " /*\n\t");
	$c = explode(' ', $ln);
	if ($c[0] == '@Auth') {
		if (!AppAccessService::isUserAuthorized($user, $c[1])) {
			throw new AppAccessException("Access to {$c[1]} denied");
		}
	}
}

Jak widać implementacja jest króciutka, ale niestety mało czytelna. To wina braku natywnej obsługi introspekcji dla adnotacji (Annotation Reflection API ma się pojawić w PHP 5.3). Taki lub podobny kod powinien znaleść się w jednym z filtrów, natomiast cała aplikacja powinna zostać ujęta w blok try-catch chwytający wyjątki typu AppAccessException. W razie wystąpienia wyjątku należy przekierować użytkownika na stronę z informacją o braku dostępu i innymi groźbami.

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

permalink | trackback | rss

 
 
Stanisław 'dozzie' Klekot

LOL. Brawo. Właśnie odkryłeś statyczne typowanie z paroma aspektami programowania obiektowego. Wielki plus dla PHP :>

occulkot

nie wyglada to zle ;) - ale problem z czytelnoscia spory - chociaz to chyba bardziej problem skladni jezyka.

Przydalaby sie funkcja typu get_doc_param(klasa, metoda) ktora zwraca slownik z wartosciami dokumentacji

str()

dozzie, czy ja widzę w Twoim komentarzu coś na kształ drwiny? Wielki plus dla PHP to będzie jak pojawi się późne wiązanie dla metod i własności statycznych.

occulkot, na szczęście ten nieczytelny kod występuje tylko raz. Wiele z niego będzie się dało usunąć po wprowadzeniu klas Reflection_Annotation_* w PHP 5.3.

Khorne

Dozzie rzadko kiedy nie drwi ;)

Radarek

O jakim wy typowaniu statycznym mówicie? PHP to język dynamiczny więc nie ma szans na to (tylko nie mówcie mi że fun(Klasa klasa) to jest typowanie statyczne w PHP bo nie jest).

Co do tematu. Muszę przyznać, że jak PHP nie lubię, tak jestem mile zaskoczony, że umożliwia programowy dostęp do dołączonego opisu metody (zapewne klas też). Faktycznie w ten sposób tworzenie takich dodatków AOP jest proste.

str()

Radarek, oczywiście że PHP jest językiem dynamicznie typowanym, a to co opisałeś to zaledwie type hinting. Używam tego bo tak jest bezpieczniej - szybciej wychodzą błędy popełnione przez nieuwagę, jak przekazanie prymitywa albo nulla zamiast obiektu. Dozzie prawdopodobnie miał na myśli statyczne wywołanie, nie typowanie.
Ja rozumiem, że PHP można nie lubić przez niewyobrażalną masę gównianych aplikacji w nim napisanych. Trzeba jednak oddać sprawiedliwość jeśli chodzi o jego możliwości - w tej chwili jest to całkiem przyzwoity język programowania.

Hoppke

To prawda. Mrużąc trochę oczy nawet pomyliłbym kod w tym wpisie z kawałkami Javy, którą codziennie widuję :)

Annotations to fajna sprawa. Ustawianie praw dostępu, mapowanie DTO na tabele i kolumny i oznaczanie relacji, czy w ogóle jakiekolwiek rzeczy człowiek chce wpisać...

Nawet na malutkim podwórku się przydają. Właśnie sobie przepisuję swojego bloga aktualizując przy okazji wykorzystywane technologie, no i wychodzi na to, że kontrolery mogę mieć zapisane np. tak (kontroler wywoływany przez formularz dodawania nowych komentarzy):

<snip>
@Controller
@RequestMapping("/newComment")
public class CommentFormController {

@Resource
private BlogEntryDao entryDao;

@Resource
private BlogEntryCommentDao commentDao;

@Resource(name="commentFormValidator")
private CommentFormValidator validator;

@RequestMapping(method=RequestMethod.POST)
public ModelAndView processFormAndAddComment(...
</snip>

Bardzo lubię taki styl programowania :)

Your turn:

nick:
and?:
www (if any):
Wpisz kod:code
Prawa dostępu oparte o adnotacje - PHP php prawa dostepu programowanie