Så här använder du CakePHP s åtkomstkontrolllistor

Om du bygger ett CMS behöver du förmodligen olika användarroller-superuser, administratörer, användare - med olika behörighetsnivåer. För komplicerat att koda? Ange CakePHP: s ACL (Access Control Lists). Med rätt inställning kontrollerar du användarbehörigheter med bara en rad.

Introduktion: Vad är åtkomstkontrolllistor?

I ACL kan du skapa en hierarki för användare med respektive roller. Här är ett snabbt exempel.

  • Super användare
    • Användare # 1
  • admins
    • Användare # 2
    • Användare # 3
  • användare
    • Användare # 4
    • Användare # 5
    • Användare # 6
    • ...

I denna handledning kommer vi att sätta upp en ACL för en enkel blogg. Om du inte har checkat ut Komma igång med CakePHP (och del 2) här på Nettuts +, vänligen gör så och återvända, eftersom vi kommer att ta för givet ramverkets grunder.

Med denna hierarki kan vi tilldela flera behörigheter för varje roll:

  • De Super användare kan skapa, läsa, uppdatera och ta bort inlägg och användare.
  • De admins kan skapa, läsa, uppdatera och ta bort inlägg.
  • De användare kan skapa och läsa inlägg.
  • Alla andra kan bara läsa inlägg.

Varje tillåtelse kommer att ges till gruppen, inte till användaren. så om användaren # 6 marknadsförs till Admin, kommer han att kontrolleras mot grupptillståndet - inte hans. Dessa roller och barnnoder (användare) heter Access Requests Objects, eller AROs.

Nu har vi åtkomstkontrollobjekten eller ACO: erna. Dessa är de objekt som ska kontrolleras. Ovan nämnde jag inlägg och användare. Normalt är dessa objekt direkt kopplade till modellerna, så om vi har en postmodell behöver vi en ACO för denna modell.

Varje ACO har fyra grundläggande behörigheter: skapa, läs, uppdatera och ta bort. Du kan komma ihåg dem med sökordet CRUD. Det finns ett femte tillstånd, asterisken, det är en genväg för fullständig åtkomst.

Vi använder bara två ACO: er för denna handledning: Post och användare, men du kan skapa så många som du behöver.

ACL-tabellerna

Låt oss fortsätta att skapa databastabellerna. Du kan hitta denna kod i db_acl.sql inuti din app config / SQL katalog.

 CREATE TABLE acos (id INTEGER (10) UNSIGNED NULL AUTO_INCREMENT, parent_id INTEGER (10) DEFAULT NULL, modell VARCHAR (255) DEFAULT ", foreign_key INTEGER (10) UNSIGNED DEFAULT NULL, alias VARCHAR (255) DEFAULT", lft INTEGER 10) DEFAULT NULL, rght INTEGER (10) DEFAULT NULL, PRIMARY KEY (id)); CREATE TABLE aros_acos (id INTEGER (10) UNSIGNED NOT NULL AUTO_INCREMENT, aro_id INTEGER (10) UNSIGNED NOT NULL, aco_id INTEGER (10) UNSIGNED NOT NULL, _create CHAR (2) INTE NULL DEFAULT 0, _read CHAR (2) INTE NULL DEFAULT 0, _update CHAR (2) INTE NULLSTÄLLNING 0, _delete CHAR (2) INTE NULLSTÄLLNING 0, PRIMÄR KEY (id)); CREATE TABLE aros (id INTEGER (10) UNSIGNED NOT NULL AUTO_INCREMENT, parent_id INTEGER (10) DEFAULT NULL, modell VARCHAR (255) DEFAULT ", foreign_key INTEGER (10) UNSIGNED DEFAULT NULL, alias VARCHAR (255) DEFAULT", lft INTEGER 10) DEFAULT NULL, rght INTEGER (10) DEFAULT NULL, PRIMARY KEY (id));

Vi kan börja nu skapa ARO- och ACO-noder, men hej, vi har inga användare! Vi måste skapa ett grundläggande autentiseringssystem.


Steg 1: Ett grundläggande autentiseringssystem

Eftersom denna handledning är avsedd för CakePHP-utvecklare med en grundläggande till måttlig kunskap om ramverket, kommer jag att tillhandahålla koden och en kort förklaring. Autentiseringssystemet är dock inte målet för denna handledning.

MySQL-tabellen:

 CREATE TABLE-användare (ID INTEGER (10) UNSIGNED AUTO_INCREMENT KEY, användarnamn TEXT, lösenord TEXT);

Användarmodellen (modeller / user.php)

 

Användarens kontroller (controllers / users_controller.php)

 Auth-> userModel = 'User'; $ This-> myndig-> tillåta ( '*');  funktionsregister () if (! tomt ($ this-> data)) // Här bör du validera användarnamnet (min längd, max längd, att inte inkludera speciella karaktärer, inte redan existerande etc) som lösenord om ($ this-> User-> validates ()) $ this-> User-> save ($ this-> data); // Låt oss läsa de data som vi just har satt in $ data = $ this-> User-> read (); // Använd den för att verifiera användaren $ this-> Auth-> login ($ data); // Omdirigera sedan $ this-> omdirigering ('/');  funktionsinloggning () om ! tom ($ this-> data)) // Om användarnamnet / lösenordet matchar om ($ this-> Auth-> login ($ this-> data)) $ this -> omdirigering ( '/');  else $ this-> User-> invalidate ('användarnamn', 'Användarnamn och lösenordskombinationen är fel!');  funktion logout () $ this-> Auth-> logout (); $ This-> omdirigera ( '/'); ?>

Eftersom vi har nya element, låt oss granska dem. Först ställer vi in ​​en $ komponenter variabel. Denna variabel innehåller alla komponenter i arrayen. Vi kommer att behöva Auth komponent, som är en kärnkomponent, liksom HTML- och formulärhjälpmedel, men eftersom den inte är inkluderad som standard av Cake, måste vi inkludera den manuellt.

Auth-komponenten hanterar en del grundläggande autentiseringsmekanik: det hjälper oss att logga in på en användare och hanterar en autentiserad användares session för oss, samt hantera utloggningen och grundläggande behörighet för gästerna. Det hakar också lösenordet automatiskt. Jag förklarar hur man ringer varje funktion i följande stycken.

Därefter skapar vi en funktion som heter beforeFilter. Detta är en återuppringningsfunktion och det låter oss ställa in några åtgärder innan alla kontrollerlogiken behandlas. Auth-komponenten kräver att vi anger en modell som ska användas, i det här fallet användaren. Då kommer den som standard att neka all tillgång till användare som inte är inloggade. Vi måste skriva över detta beteende med tillåta() vilket kräver en parameter. Den parametern kan vara en asterisk som anger att alla metoder inom kontrollenheten kan nås av oautentiserade användare. Eller det kan gå igenom en array med funktioner som kan nås av oautentiserade användare. I det här fallet, eftersom vi bara har tre funktioner, är följande rader samma sak.

 $ This-> myndig-> tillåta ( '*'); $ this-> Auth-> allow (array ('register', 'login', 'logout'));

För logga in() funktionen, kommer Auth-komponenten att hantera alla inloggningsmekanismer för oss. Vi måste bara leverera funktionen med en matris med två nycklar: användarnamnet och lösenordet. Dessa nycklar kan ändras, men som standard båda Användarnamn och Lösenord Fält kommer att matchas mot databasen och kommer att returneras sant om användaren har verifierats.

Slutligen försöker kontrollantens inloggningsfunktion matcha ett användarnamn / lösenordskombination mot databasen.

Observera att denna kod är väldigt grundläggande. Du måste validera användarnamnskaraktärerna, om användarnamnet existerar, minsta längd för lösenordet och så vidare.

Registret (visningar / användare / register.ctp)

 

Registrera ditt konto

Användarnamn text ( 'User.username'); ?>

Lösenord lösenord ( 'user.password'); ?>

skicka ( 'Register'); ?>

Inloggningsvyn (visningar / användare / login.ctp)

 

Logga in på ditt konto

fel ( 'User.username'); ?>

Användarnamn text ( 'User.username'); ?>

Lösenord lösenord ( 'user.password'); ?>

skicka in ('Logga in'); ?>

Öppna / Användare / register i din webbläsare och registrera ett nytt konto. jag föreslår administration som användarnamn och 123 som lösenord och om din session löper ut går du till / Användare / login och ange det korrekta användarnamnet / lösenordskombinationen du just skapat.


Steg 2: Neka åtkomst till obehöriga användare

Vi arbetar inte ens med ACL, men vi kan redan neka publicering, redigering och radering av inlägg. Öppna din Inläggskontroller och lägg till Auth-komponenten.

 var $ components = array ('auth');

Gå nu till / inlägg i din webbläsare. Om du är inloggad ska du se inläggen, men om du inte är det, kommer du vidarebefordras till / Användare / login. Genom att helt enkelt inkludera Auth-komponenten är alla åtgärder som standard, nekade för gäster. Vi måste förneka tre åtgärder för obehöriga användare: skapa, redigera och ta bort. Med andra ord måste vi tillåta index och visa.

 funktion beforeFilter () $ this-> Auth-> userModel = 'User'; $ this-> Auth-> allow (array ('index', 'view')); 

Gå till redigera eller skapa ett inlägg; Om du inte är inloggad ska du vidarebefordras till / Användare / login. Allt verkar fungera ganska bra, men vad sägs om åsikterna? Redigera och ta bort länkar visas för alla. Vi borde göra en villkorlig.

Men innan vi går in i det, låt oss se hur Auths användarfunktion () fungerar. Kopiera och klistra in dessa rader i indexfunktionen.

 $ user = $ this-> Auth-> user (); pr ($ user);

Öppna din / inlägg i din webbläsare och, om du är inloggad, pr () kommer att kasta något så här.

 Array ([User] => Array ([id] => 1 [användarnamn] => admin))

De användare() funktionen returnerar en array precis som en modell skulle göra. Om vi ​​hade mer än tre fält (lösenord ingår ej) visas de i arrayen. Om du inte är inloggad kommer matrisen vara tom, så du kan veta att en användare är inloggad om Auths användare() array är inte tomt.

Nu är Auth en komponent som är avsedd att användas i en kontroller. Vi behöver veta från en vy om en användare är inloggad, helst via en hjälpare. Hur kan vi använda en komponent inuti en hjälpare? CakePHP är så fantastisk och flexibel att det är möjligt.

 Session = $ this-> Session; $ user = $ auth-> user (); returnera! tomt ($ användare); ?>

Spara den här siten i views / hjälpare som access.php. Nu får vi se koden, linje för rad. Först lägger vi upp en $ hjälpare var. Hjälpare kan inkludera andra hjälpare, precis som $ komponenter kan. Session-komponenten krävs för Auth-komponenten, men vi har ingen åtkomst till den här komponenten i hjälpen. Lyckligtvis har vi en Sessionshjälpare som hjälper oss.

Därefter skapar vi en funktion och användning App :: import vilket låter oss importera ett element som vi normalt inte skulle ha tillgång till. Nästa rad skapar Auth-komponenten i en $ auth variabel och nu lite smutsig hack; Eftersom Auth-komponenten läser sessionen om du vill veta om vi är inloggade eller inte, krävs det Session-komponenten, men när vi importerar den från en plats som den inte borde tillhöra måste vi ge det ett nytt Sessionsobjekt. Slutligen använder vi användare() och ställa in det $ användare och returnerar sant om variabeln inte är tom, annars felaktig.

Låt oss komma tillbaka till inläggskontrollen och fortsätt för att lägga till hjälpen.

 var $ helpers = array ('Access');

Åtkomsthjälpen är nu tillgänglig från vyn. Öppna index.ctp i visningar / inlägg och ersätt den här raden.

 "> redigera | länk ('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Är du säker?'); ?>

Med den här.

 isLoggedin ()):?>"> redigera | länk ('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Är du säker?'); ?>

Gå tillbaka till din webbläsare, ladda om indexsidan och om du är inloggad ser du redigeringen och raderar länkarna för varje inlägg. Annars ser du ingenting.

Även om detta skulle räcka om du har en app med en eller två användare, räcker det inte om du har registreringar öppna.


Steg 3: Installera ACL

Öppna användarkontrollern och lägg till ACL-komponenten.

 var $ components = array ('Auth', 'Acl');

Låt oss sedan skapa en funktion för att installera ACOs och AROs noder.

 funktionsinstallation () if ($ this-> Acl-> Aro-> findByAlias ​​("Admin")) $ this-> omdirigera ('/');  $ aro = new aro (); $ ArO-> skapa (); $ aro-> spara (array ('model' => 'Användare', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Super')); $ ArO-> skapa (); $ aro-> spara (array ('model' => 'Användare', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Admin')); $ ArO-> skapa (); $ aro-> spara (array ('model' => 'Användare', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Användare')); $ ArO-> skapa (); $ aro-> spara (array ('model' => 'Användare', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Suspended')); $ aco = ny Aco (); $ ACO> skapa (); $ aco-> spara (array ('model' => 'Användare', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Användare')); $ ACO> skapa (); $ aco-> spara (array ('model' => 'Post', 'foreign_key' => null, 'parent_id' => null, 'alias' => 'Post')); $ this-> Acl-> allow ('Super', 'Post', '*'); $ this-> Acl-> allow ('Super', 'User', '*'); $ this-> Acl-> allow ('Admin', 'Post', '*'); $ this-> Acl-> allow ('User', 'Post', array ('skapa')); 

Genom att importera ACL-komponenten kan vi komma åt ACOs och AROs-modellen. Vi börjar med att skapa en ARO som är den som söker efter objekten. med andra ord, vem kommer att komma åt objekten. Det skulle vara användaren (därmed användarsträngen i modellen). Vi skapar olika roller; Super, Admin, Användare och Suspended.

Därefter gör vi detsamma med ACO, eftersom vi bara har två objekt att hantera (Inlägg och Användare) skapar vi två, en för varje.

Nu, en snabb notering. Arrayet som vi sparar för både ACO och ARO har fyra fält: modellnamnet, främmande nyckel (vilket är användbart om du vill ge tillgång till en ARO, som ett inlägg han skapade), föräldra id (som kommer att användas senare och är grundförhållandet mellan parent-child noder, vi kommer att skapa användare under dessa roller) och aliaset (vilket är en snabb form för att hitta objekten).

Slutligen använder vi ACLs tillåta() fungera. Den första parametern är ACO-aliasen; För det andra, ARO-aliaset, och tredje, tillstånden som ges till nämnda ARO. En Superuser har full tillgång till Post och Användar-modellerna, en Admin har full tillgång till Post-modellen, och en användare kan bara skapa inlägg.

Vid funktionens början förklarade jag att det var villkorligt att kontrollera om administratörsrollen finns i ACOs. Du vill inte installera samma sak mer än en gång, gör du? Det skulle rota databasen ganska illa.

Öppna / användare / install i din webbläsare, och eftersom vi inte har en vy, kommer CakePHP att kasta ett fel, men bara kolla MySQL-dumpan. Alla relationer har framgångsrikt skapats, det är dags att arbeta med barnnoden.

Ställer in användare till roller

Låt oss städa användare tabell. Öppna phpMyAdmin, välj din databas, användare bord och klicka tomt. Vi kommer tillbaka till Registrera() funktionen på användarkontrollern. Precis under denna rad:

 $ This-> myndig-> inloggnings ($ data);

Klistra in den här koden:

 // Ställ in användarrollerna $ aro = nytt Aro (); $ parent = $ aro-> findByAlias ​​($ this-> User-> find ('count')> 1? 'Användare': 'Super'); $ ArO-> skapa (); $ aro-> save (array ('model' => 'Användare', 'foreign_key' => $ this-> Användare-> id, 'parent_id' => $ förälder ['Aro'] ['id'], alias '=>' Användare :: '. $ this-> User-> id));

I första raden skapar vi ett nytt ARO-objekt. Då får vi den föräldraknapp där användaren kommer att skapas. Om det finns en post i databasen ställer vi den till User ARO, annars borde den första användaren vara Super.

Då sparar vi en ny ARO; Modellen är den vi jobbar för närvarande, Användare, de främmande nyckel är den sista postens id som vi just skapat. De PARENT_ID är noden som vi började med; Vi skickar det bara id och slutligen aliaset. Det är en bra idé att ringa det Modellnamn, sedan en separator ::, och sedan identifieraren. Det blir mycket lättare att hitta den.

Nu är vi färdiga. Skapa fyra användare: superuser, adminuser, normaluser och suspendeduser. Jag föreslår samma användarnamn och lösenord för teständamål. Glöm inte att tills denna punkt har bara superanvändaren en roll som Super; alla resterande kommer att vara användare!


Steg 4: Läser tillstånd

Eftersom ACL är en komponent är den endast tillgänglig inom kontrollenheten. Senare kommer det också att vara i sikten; men första saker först. Inkludera ACL-komponenten i Inläggskontrollen, som vi gjorde i User Controller. Nu kan du börja kolla behörigheter. Låt oss gå till redigeringsfunktionen och göra ett snabbtest. Lägg till det här i den första raden av metoden.

 $ user = $ this-> Auth-> user (); om (! $ this-> Acl-> check ('Användare' '. $ användare [' Användare '] [' id '],' Post ',' uppdatering ')) dö (' du är inte behörig ');

ACL: s kontrollera() funktionen behöver tre parametrar: ARO, ACO och åtgärden. Gå och rediger ett inlägg, och om du inte är inloggad som superanvändare, kommer manuskriptet att dö. Gå till / Användare / login och få tillgång till Super-användaren och gå tillbaka till redigering. Du borde kunna redigera inlägget. Kolla under MySQL-dumpen för att se magiken. Fyra databasfrågor: det är säkert inte skalbart.

Vi har två frågor. Först, två linjer för behörighetskontrollen. För det andra caches inte ACL: erna. Och glöm inte alla loggade användare kan se redigeringslänken, även om bara Super-användaren kan använda den.

Låt oss skapa en ny komponent. Låt oss kalla det Access.

 user = $ this-> Auth-> user (); ?>

Spara den i regulatorer / komponenter som access.php. Klassen är $ användare Var kommer att initieras när komponenten är laddad, och börja() är en återuppringningsfunktion, så den är nu inställd i klassen. Låt oss nu skapa vår kontrollera() fungera.

 funktionskontroll ($ aco, $ action = '*') om (! tom ($ this-> användare) && $ this-> Acl-> check ('Användare ::'. $ this-> user ['User' ] ['id'], $ aco, $ action)) return true;  annars return false; 

Vår kontrollera() Metoden behöver bara två parametrar: ACO och åtgärden (som är valfritt). ARO kommer att vara den aktuella användaren för varje session. Åtgärdsparametern kommer som standard till *, som är full tillgång till ARO. Funktionen börjar med att kontrollera om $ This-> användare är inte tomt (vilket verkligen berättar om en användare är inloggad) och sedan går vi till ACL. Vi har redan täckt detta.

Vi kan nu inkludera Access-komponenten i vår Inlägg-kontroller och kontrollera behörigheterna med bara en rad.

 om (! $ this-> Access-> check ('Post', 'update')) dö ('du är inte behörig');

Samma resultat uppnås med mindre kod, men felmeddelandet är fult. Du skulle bättre ersätta dö() med CakePHP-felhanteraren:

 $ This-> cakeError (error404 ');

Tillstånd i synpunkter

Det kan vara fult, men det fungerar. Vi måste skapa en hjälpare som laddar en komponent med en anpassad metod för användning i hjälpen.

Lägg till den här funktionen i Access-komponenten (controllers / komponenter / access.php).

 funktion checkHelper ($ aro, $ aco, $ action = "*") App :: import ('Component', 'Acl'); $ acl = ny AclComponent (); returnera $ acl-> checka ($ aro, $ aco, $ action); 

Låt oss nu skriva om åtkomsthjälpen (visningar / hjälpare / access.php).

 Access = ny AccessComponent (); App :: import ('Komponent', 'Auth'); $ this-> Auth = ny AuthComponent (); $ this-> Auth-> Session = $ this-> Session; $ this-> user = $ this-> Auth-> user ();  funktionskontroll ($ aco, $ action = '*') om (tomt ($ this-> user)) returnerar false; returnera $ this-> Access-> checkHelper ('Användare ::'. $ this-> user ['User'] ['id'], $ aco, $ action);  funktion isLoggedin () return! empty ($ this-> användare); ?>

De beforeRender () Metoden är en återuppringning, som liknar komponentens börja(). Vi laddar två komponenter och eftersom dessa kommer att användas i de flesta funktioner är det en bra idé att starta allt på en gång, än att manuellt starta dem varje gång metoden heter.

Nu på din index.ctp visa i visningar / inlägg du kan ersätta den här raden.

 "> redigera | länk ('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Är du säker?'); ?>

Med den här.

 kontrollera ('Post')):?>"> redigera | länk ('delete', '/posts/delete/'.$post['Post']['id'], NULL, 'Är du säker?'); ?>

Glöm inte att kolla behörigheter, både i vyerna och kontrollerna!

Användardata

Du kan komma åt användardata för en loggad användare med användare() metod i Auth-komponenten. Då kan du komma åt arrayen och få den information du vill ha. Men det måste finnas ett bättre sätt. Låt oss lägga till följande funktion i Access-komponenten.

 funktion getmy ($ vad) return! empty ($ this-> user) && isset ($ this-> user ['User'] [$ what])? $ this-> user ['User'] [$ what]: false; 

Det här är ganska användbart när du behöver spara ett inlägg med en användar ID relation.

 $ this-> data ['Post'] ['user_id'] = $ this-> Access-> getmy ('id');

Och i vyn kan vi göra något liknande med hjälpen.

 funktion getmy ($ vad) return! empty ($ this-> user) && isset ($ this-> user ['User'] [$ what])? $ this-> user ['User'] [$ what]: false; 

I din mallfil kan du göra något som nedan för att hälsa en användare av sitt användarnamn.

 Välkommen isLoggedIn ()? $ access-> getmy ('användarnamn'): 'Gäst'; ?>

Steg 5: Ändra tillstånd

Låt oss säga att vi måste byta roller för användaren # 4: han måste vara en superanvändare. Så, användarens id är 4 och Aros ID är 1.

 $ user_id = 4; $ user_new_group = 1;

Nu måste vi hitta användarens Aro för att kunna ändra sin moder Aro.

 $ aro_user = $ this-> Acl-> Aro-> hitta ('första', array ('conditions' => array ('Aro.parent_id! =' => NULL, 'Aro.model' => 'Användare' 'Aro.foreign_key' => $ user_id)));

Slutligen, om $ aro_user variabeln är inte tom, låt oss uppdatera Aro.parent_id fält.

 om (! tom ($ aro_user)) $ data ['id'] = $ aro_user ['Aro'] ['id']; $ data ['parent_id'] = $ user_new_group; $ This-> Acl-> ArO-> Spara ($ data); 

Observera att du måste validera både användarens id och den nya aro-iden. Om en av dessa inte finns kommer det att skapa en röra i dina ACL-tabeller.


Steg 6: Optimering

Även om komponenten vi just skapat är både enkel och användbar, frågas databasen med varje kontroll. Det är inte klart för produktion än. Först bör databasen fråga så lite som möjligt. För att optimera detta borde vi dra nytta av CakePHPs Cache.

Att använda Cache kommer att minska belastningen ganska lite, men om vi har tio tjänster som visas i indexet, och vi kontrollerar om användaren har uppdateringsbehörigheter för Post Aco, kommer ramverket att läsa och analysera en fil för att returnera samma resultat ... tio gånger för varje sida.

Det är den andra punkten: En variabel inuti klassen och vissa villkor kommer att göra arbetet lättare, så att en upprepad begäran kommer att checka mot Cache bara en gång.

Båda dessa förändringar återspeglas i access_cache.php, inuti regulatorer / komponenter katalogen. Så se till att du laddar ner källan!


Slutsatser

Access Control Lists är en grundläggande funktion som de flesta appar behöver. CakePHP har en bra implementering, men saknar både bra dokumentation och exempel. Jag hoppas att med dessa handledning kommer dessa två frågor nu att täckas. Tack för att du läser!