wtorek, 12 lutego 2008

Obsługa złożonych uprawnień użytkowników

W artykule "Definiowanie złożonych uprawnień użytkowników" opisałem koncepcję struktury danych służącej do przechowywania informacji o uprawnieniach użytkowników. Dalszą częścią pracy nad uprawnieniami było zaprojektowanie komponentu .netowego do wyliczania uprawnienień użytkownika do określonych komponentów.

Wymagania:

a. szybkie wyszukiwanie danych, tak by narzut określania uprawnień nie był zbyt duży,

b. stosunkowo małe wymagania pamięciowe, tak by możliwe było przechowywanie w pamięci (stanie Sesji) kilkuset profili zabezpieczeń (na razie nie przewiduję większego obciążenia systemu:).

Pierwszym pomysłem, na który zwróciłem uwagę, było załadowanie danych z bazy danych do pliku xml, a następnie przeszukiwanie tegoż pliku za pomocą zapytań XPath. Niewątpliwą zaletą takiego rozwiązania byłoby mała zajętość pamięci - w stanie sesji można byłoby przechowwywać tylko ten xml. Jednak przyjęta koncepcja uprawnień oznaczałaby konieczność wielokrotnego przeszukiwania wszystkich węzłów, co niewątpliwie odbiłoby się na wydajności.

W końcu zdecydowałem się na wykorzystanie struktury drzewiastej:

SecurityDiagram

 

Na najniższym poziomie jest obiekt typu CRUD, który zawiera informację o uprawnieniach użytkownika do poszczególnych czynności (create, read, update i delete). Uprawnienia są przechowywane w formie trójwartościowych enumów AuthorizationType, którę we właściwościach publicznych typu CanCreate…CanDelete są konwertowane na wartości true/false (grant => true, pozostałe => false).

Obiekty AuthorizationNode przechowują informację o poszczególnych węzłach, każdy z nich ma kolekcję obiektów typu Condition, które definiują warunki i posiadają określone uprawnienia jeśli warunki są spełnione. Dla określonych danych wejściowych można określić które warunki są spełnione i używając arytmetyki uprawnień zaprezentowanej w poprzednim artykule można wyliczyć uprawnienia sumaryczne. Do wyliczania uprawnień użyłem biblioteki ecalc z http://www.codeproject.com/KB/cs/ExpressionEval.aspx.

Dla poprawy wydajności uprawnienia wyliczane są raz, poza tym uprawnienia wyliczane są tylko dla tych gałęzi, dla których taka informacja jest pobierana (zazwyczaj też z gałęzi nadrzędnych). Oczywiście wiąże to się z konicznością kasowane poprzednich wyliczeń przy zmianie danych (metoda SetCurrentData ustawia wszystkie uprawnienia rekurencyjnie w dół dla całego drzewa na null).

Na początku jako dane źródłowe wykorzystywałem obiekty typu DataRow, jednak by umożliwić wykorzystywanie innych typów danych utworzyłem interfejs IDataContainer, który zawiera jedynie indeksator:

    public interface IDataContainer
{
object this[string fieldName] {get; }
}

Dodałem dwie jego implementacje SimpleDataContainer korzystająca z obiektu Dictionary<string, object> oraz DataRowDataContainer wykorzystującą DataRow, kod tego ostatniego:

    internal class DataRowDataContainer : IDataContainer
{
private DataRow dataRow;

public DataRowDataContainer(DataRow row)
{
this.dataRow = row;
}

public DataRow DataRow
{
get { return dataRow; }
set { dataRow = value; }
}

public object this[string fieldName]
{
get
{ return dataRow[fieldName]; }
}
}

Dla uproszczenia najczęściej wykonywanych zadań rozszerzyłem klasę AuthorizationNode o kilka dodatkowych metod.

TestClassDiagram

Np. metoda GetNonInsertableChilds zwraca kolekcję nazw wszystkich dzieci, które nie są dostępne przy dodawaniu nowych danych.

Przykłady wykorzystania uprawnień:

        [Test]
public void CheckJamesBondsRightsToOffers()
{
// additional method that set up current
// user
Identity.SetCurrentIdentiy("james bond");

// componente that load privileages data
// for current user
ComponentAuthorizationBL logic =
new ComponentAuthorizationBL();

ComponentAuthorization authorization =
logic.GetComponentAuthorization();

// load some data
Dictionary<string, object> dictionary =
new Dictionary<string, object>();
dictionary.Add("OwnOffer", false);
dictionary.Add("ReferenceBookId", 1);
dictionary.Add("OfferType", (short)OfferType.Standard);

authorization.SetCurrentData(dictionary);

// evaluate privelages for one node
CRUD crud = authorization.GetCRUD("LearningOffers/MainData");

Assert.IsTrue(crud.CanRead);
Assert.IsFalse(crud.CanCreate);
}

 

cdn :).

0 komentarze: