controleren van rechten - idee?

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Front-End Developer – Junior/Medior/Senior

Onze opdrachtgever Onze opdrachtgever maakt kassa’s, personeelsplanning bar-/keukenmanagement, tafelreserveringssoftware, websites en webshops. Van horeca tot retail, van leisure tot zorg: elke ondernemer mag bij hun aankloppen. 24/7 spelen ze proactief in op de markt. Met softwareontwikkeling, projectmanagement, systeemimplementatie, helpdesk en technische dienst in eigen beheer bieden ze zo zekerheid voor haar klanten. Standplaats Hengelo Waar we jou voor nodig hebben? Van sterrenrestaurant tot vakantiepark: de klanten van onze opdrachtgever zijn heel divers. Een intuïtieve orderwebsite voor een grote cateraar of een sieradenplatform voor een juwelier, je draait er je hand niet voor om. Je communiceert helder en staat klanten graag

Bekijk vacature »

Thomas van den Heuvel

Thomas van den Heuvel

22/02/2015 01:34:58
Quote Anchor link
Om resources in mijn website af te schermen maak ik gebruik van geserialiseerde rechten-expressies, een soort van predikaat in string-vorm dus, opgesteld in een (semi) Poolse (of prefix) notatie. Het voordeel hiervan is dat ik rechtstreeks rechten kan koppelen aan een resource, en hier ook direct bij opslaan (een expressie die aangeeft of iets leesbaar is, een expressie die aangeeft of iets schrijbaar is et cetera).

Ik heb het in PHP al voor elkaar, alleen soms zal er waarschijnlijk de behoefte ontstaan om alleen die resources op te halen uit mijn database waarvoor de gebruiker die ze opvraagt voldoende rechten heeft. Ik wil dus de selectie al in mijn database maken en niet achteraf filteren.

Hiertoe heb ik de volgende MySQL functie gemaakt (zowel deze code als de PHP variant volgt in grote lijnen de abstracte implementatie op de WIKI-pagina):
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
DELIMITER ;;
DROP FUNCTION IF EXISTS has_rights;;
CREATE FUNCTION has_rights(expression VARCHAR(255), rights VARCHAR(255)) RETURNS BOOL DETERMINISTIC
BEGIN
    DECLARE stack VARCHAR(255);
    DECLARE token VARCHAR(5);
    DECLARE val1 VARCHAR(5);
    DECLARE val2 VARCHAR(5);
    IF (expression = '') THEN
        RETURN TRUE;
    END IF;
    SET stack = '1,';
    SET expression = REVERSE(CONCAT(',&,', expression));
    WHILE (LOCATE(',', expression) > 0) DO
        SET token      = SUBSTRING_INDEX(expression, ',', 1);
        SET expression = SUBSTRING(expression, LENGTH(token) + 2);
        IF (LENGTH(token) > 0) THEN
            IF (token = '|' OR token = '&') THEN
                SET val1 = SUBSTRING_INDEX(stack, ',', 1);
                SET stack = SUBSTRING(stack, LENGTH(val1) + 2);
                SET val2 = SUBSTRING_INDEX(stack, ',', 1);
                SET stack = SUBSTRING(stack, LENGTH(val2) + 2);
                IF (token = '|') THEN
                    SET stack = CONCAT((val1 OR val2), ',', stack);
                ELSE
                    SET stack = CONCAT((val1 AND val2), ',', stack);
                END IF;
            ELSEIF (token = '!') THEN
                SET val1 = SUBSTRING_INDEX(stack, ',', 1);
                SET stack = CONCAT(NOT(val1), ',', SUBSTRING(stack, LENGTH(val1) + 2));
            ELSE
                SET val1 = LOCATE(CONCAT(',', token, ','), CONCAT(',', rights, ',')) > 0;
                SET stack = CONCAT(val1, ',', stack);
            END IF;
        END IF;
    END WHILE;
    SET val1 = SUBSTRING_INDEX(stack, ',', 1);
    RETURN (val1 = 1);
END;;
DELIMITER ;

Hierbij heb ik wel wat truuks uit moeten halen en moet ik bepaalde randgevallen correct afhandelen. Bovenstaande code kan desgewenst worden toegelicht.

Omdat dit wellicht nogal abstract is een voorbeeld.

Stel ik heb een resource die als volgt is ingesteld: de resource is toegankelijk als iemand recht 1 heeft, OF recht 2, maar dan mag die persoon het recht 3 niet hebben. De "rechtenboom" ziet er dan als volgt uit:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
OR--+--1
    |
    +--AND--2
         |
         +--NOT--3

Deze kun je ook op de bovenstaande pagina nabouwen (give it a try).

De geserialiseerde expressie (in prefix notatie) wordt dan (| is de logische OR, & de logische AND, ! de logische NOT):
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
|,1,&,2,!,3


Als we vervolgens wat SELECTs doen:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
mysql> SELECT has_rights('|,1,&,2,!,3', '1');
+--------------------------------+
| has_rights('|,1,&,2,!,3', '1') |
+--------------------------------+
|                              1 |
+--------------------------------+
1 row in set (0.00 sec)

(gebruiker heeft recht 1 - toegang verleend)

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
mysql> SELECT has_rights('|,1,&,2,!,3', '2');
+--------------------------------+
| has_rights('|,1,&,2,!,3', '2') |
+--------------------------------+
|                              1 |
+--------------------------------+
1 row in set (0.00 sec)

(gebruiker heeft recht 2 - toegang verleend)

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
mysql> SELECT has_rights('|,1,&,2,!,3', '2,3');
+----------------------------------+
| has_rights('|,1,&,2,!,3', '2,3') |
+----------------------------------+
|                                0 |
+----------------------------------+
1 row in set (0.00 sec)

(gebruiker heeft recht 2 en 3 - toegang geweigerd)

Nu heb ik wat vragen:
- wat vind je van dit idee (een geserialiseerd predikaat opslaan bij een resource die aangeeft wat (moet blijken uit de kolom waar je dit in opslaat) en onder welke condities (moet blijken uit de validatie middels has_rights()) hier iets mee gedaan mag worden)
- MySQL kent geen arrays, een alternatief hiervoor zijn tijdelijke tabellen ofzo (dan moet ik er waarschijnlijk een PROCEDURE van maken); bovenstaande code is een beetje wollig, maar lijkt te werken; zijn er ergens nog dingen aan te verbeteren / te veranderen?
 
PHP hulp

PHP hulp

25/04/2019 04:55:15
Honeypot
 
Thomas van den Heuvel

Thomas van den Heuvel

28/03/2015 16:36:15
Quote Anchor link
Meh, kon het oorspronkelijke bericht niet meer aanpassen :(.

Voor wie het interesseert:
Regel 15 moet vervangen worden door het volgende:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
SET token      = REVERSE(SUBSTRING_INDEX(expression, ',', 1));

Dit omdat expression initieel omgedraaid wordt. Tokens die uit meer dan 1 karakter bestaan (rechten-id's groter dan 9) moeten dus opnieuw omgedraaid worden anders staan deze achterstevoren :).

Werkt dus nu ook voor rechten-id's groter dan 9 :D.
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
mysql> SELECT has_rights('&,96,|,98,97', '98,96');
+-------------------------------------+
| has_rights('&,96,|,98,97', '98,96') |
+-------------------------------------+
|                                   1 |
+-------------------------------------+
1 row in set (0.00 sec)

Sorry voor de bump maar kon helaas niet anders.
Gewijzigd op 28/03/2015 16:37:10 door Thomas van den Heuvel
 
Thomas van den Heuvel

Thomas van den Heuvel

23/05/2018 16:59:48
Quote Anchor link
* shameless bump *

Voor de geïnteresserde(n): ik heb inmiddels een nieuwe variant in elkaar gezet.

En als je wat minder interesse hebt in alle technische mumbo jumbo maar wilt zien wat je er nu eigenlijk mee kunt doen: het stelt je in staat om eenvoudig rechten-bomen in elkaar te klikken die later weer geëvalueerd kunnen worden zodat bepaald kan worden of je voldoende rechten hebt om bepaalde acties uit te voeren of dingen in te mogen zien.

Op- en aanmerkingen welkom, vooral als je denkt te weten hoe er nog wat milliseconden van de executietijd afgeschaafd kunnen worden, al is het volgens mij al redelijk snel ;).
 
Ben van Velzen

Ben van Velzen

23/05/2018 18:26:30
Quote Anchor link
Grappig, dit lijkt heel erg op de menubomen die we op het werk gebruiken in elkaar te draaien. Selecties gebeuren op eigenschappen, dus bijvoorbeeld type product = telescoop AND merk = Meade.
 
Rob Doemaarwat

Rob Doemaarwat

23/05/2018 20:10:14
Quote Anchor link
Ik vraag me meer af of je gebruikers dit gaan snappen, om zo'n "boom" in elkaar te klikken (voor elke resource). Zal het niet meestal zo zijn dat Truus alleen documenten van type I in mag zien, Miep alleen van type II, en Kees allebei (en ook nog die van type III)? Dat kan ook gewoon met een algemene rechten+profielen structuur.

Maw: ik zie de meerwaarde hier niet zo van (een oplossing op zoek naar een probleem?).
 
Thomas van den Heuvel

Thomas van den Heuvel

23/05/2018 22:26:04
Quote Anchor link
Dit is niet voor iedere gebruiker uiteraard maar meer voor beheerders. Het nut staat misschien beter uitgelegd in het eerste deel. De expressies die je bouwt zijn in wezen geserialiseerde predicaten (in Poolse Notatie). Dit stelt je in staat om een soort van if-statement met rechtenchecks dynamisch te koppelen aan willekeurige content / resources, deze is niet langer gebonden aan de code / hard-coded.
 
Rob Doemaarwat

Rob Doemaarwat

23/05/2018 22:59:18
Quote Anchor link
Dat je het niet hard-coded wilt doen snap ik. Maar ik ben bang dat je uiteindelijk toch een beperkte aantal "predicaten" gaat krijgen, die je net zo goed in een rechtenstructuur uit kunt werken (eenmalig, dan hoef je het ook niet elke keer te parsen). Dat recht noem je dan bijvoorbeeld (letterlijk) "|,1,&,2,!,3" en via profielen zorg je d'r voor dat gebruikers type "1" dit recht sowieso hebben, en gebruikers type "2" alleen als ze niet al recht "3" hebben.

Ik vind het een leuke oplossing, en ik zie ook wel de voordelen als je het heel dynamisch wilt doen, maar je bent hier gewoon "programmeerwerk" naar de gebruiker aan het verplaatsen, en dat gaat meestal maar beperkt goed ("ze snappen het niet", of in ieder geval niet goed genoeg en gaan fouten maken). Een rechten-/profielenstructuur snapt "iedereen". Misschien niet zo flexibel, dus om de flexibiliteit van dit systeem op te vangen heb je heel veel profielen nodig, maar dat slaan ze gewoon op in een Excel sheet, en desnoods krijgt elke gebruiker z'n eigen profiel, en dan is iedereen weer tevreden.

En wat je dan dus eigenlijk gedaan hebt is je hele flexibele systeem eenmalig "plat slaan" naar aparte/unieke rechten. Voordeel is dan dat je niet elke keer je Poolse notatie uit hoeft te werken (en dat is dus altijd sneller ;-) ).

Maarrr, mocht je de boel toch willen versnellen is het misschien een idee om de resultaten te cachen in een (RAM) tabel. Kolommen: expression, rights, en result. Bij meer dan een x aantal rijen truncaten (en opnieuw beginnen). Voorkomt dat als je voor dezelfde gebruiker (steeds zelfde rights) een hele waslijst aan resources moet doorlopen (met dus naar mijn idee veelal dezelfde expressions) je steeds dezelfde riedel moet uitvoeren.
 
Thomas van den Heuvel

Thomas van den Heuvel

24/05/2018 00:08:23
Quote Anchor link
Maar als je queries uitvoert om bijvoorbeeld, ik noem maar wat, rijen gegevens op te halen als aan bepaalde voorwaarden voldaan is, en je daarmee gepagineerde lijsten wilt genereren, dan kan het handig zijn om al aan de database-kant te filteren. Daarnaast is de MySQL function deterministisch, dus dit houdt in dat dit intern al enorm geoptimaliseerd kan worden als je meermalig eenzelfde predicaat-rechten combinatie aanroept (wat wss vaak aan de hand zal zijn, niet elk item zal een eigen unieke combinatie hebben).

Het hangt natuurlijk van de toepassing af of dit handig en efficient is, en voor andere varianten zijn CRUD-aanpakken of (simpele) profielen of wat dan ook wellicht handiger. Maar deze opzet geeft vooralsnog volledige vrijheid.

Nogmaals, normale gebruikers zullen hier niet aankomen, dit is echt voor de inrichting/beheerkant van backends.
 
Thomas van den Heuvel

Thomas van den Heuvel

01/06/2018 21:42:04
Quote Anchor link
* shameless bump *
Code wat aangepast en validatiefunctie herschreven. Deze zit nu ook ingebouwd in de demo, dus je krijgt nu ook foutmeldingen over wat er eventueel niet klopt aan de expressie.
 
Rob Doemaarwat

Rob Doemaarwat

01/06/2018 22:20:26
Quote Anchor link
Ik zat nog even te denken ... Als je die Poolse notatie nou (eenmalig!) in een regex zou "compileren"?
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
//dit:
OR--+--1
    |
    +--AND--2
         |
         +--NOT--3
//wordt dan (ik omsluit de "rechten" met punthaken ivm afbakening):
/<1>|<2>(?!.*<3>)/

De gebruikersrechten moet je dan wel (op de een of andere manier - hier numeriek) sorteren:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
$regex = '/<1>|<2>(?!.*<3>)/';
print((preg_match($regex,'<1>') ? 'OK' : 'no') . "\n");
print((preg_match($regex,'<2>') ? 'OK' : 'no') . "\n");
print((preg_match($regex,'<2><3>') ? 'OK' : 'no') . "\n");

Zelfde resultaat. Ik vermoed sneller dan nu.

In MySQL kun je dan:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
select * from `resource` r where :rights regexp r.expression

In r.expression staat dan dus de regex, rights zijn de gesorteerde rechten (incl punthaken). Ik vermoed ook sneller dan nu.

Moet je dus nog wel een "regex compiler" toevoegen. Maakt het wel weer complexer. Of beter: een invoerhulp die meteen een regex in elkaar knutselt :-)
 
Thomas van den Heuvel

Thomas van den Heuvel

01/06/2018 23:13:03
Quote Anchor link
Weet niet of een regex sneller is dan een DETERMINISTIC functie.
Of dat de regex variant makkelijk uitbreidbaar is op het moment dat je extra operatoren introduceert (Joost mag weten waarom, maar ik ken rechtenconstructies met XOR operatoren).
Of hoe de lengte van de expressie zich verhoudt tot performance. Of de vorm (duik je met groeperingshaken de recursie in? De Poolse notatie blijft lineair).

Ik denk dat je i.i.g. wat aan transparantie verliest op het moment dat je regexes introduceert. Dus zelfs als dit (mogelijk marginaal) sneller is dan de huidige opzet, zou ik daar uit overwegingen van simpliciteit misschien toch niet aan beginnen.

Vraag is ook, kun je het resultaat van een REGEXP vergelijking in MySQL cachen zonder extra handelingen? Volgens mij doet een DETERMINISTIC functie dat automatisch. Als de invoer hetzelfde is als een reeds eerdere ingevoerde combinatie van rechten en een expressie, dan is het resultaat al bekend.

Je zou met benchmarks moeten kijken of dit echt sneller is. Meten = weten.
 



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.