Uwierzytelniony widzi jedynie swoje dane (posty, newsy, userów)

Witam. To mój pierwszy post na forum.

Od kilku tygodni przyglądam się Yii i bardzo mi się spodobał.

Przeszedłem przez lekturę ‘Przewodnika’ i ‘zaimplementowałem’ bloga :wink:

W ramach dalszej nauki chciałbym dać możliwość ‘zakładania’ bloga wielu

użytkownikom w ramach tej samej aplikacji, wiec np. każdy po pomyślnym

uwierzytelnieniu widziałby np. jedynie swoje posty. Jednak napotkałem na problem. Jak łatwo zdeterminować jakie

dane mogę danemu, uwierzytelnionemu użytkownikowi pokazać?

Sam gii generuje nam wiele plikow i faktycznie aplikacja ‘dziala’ niemal od

razu. Gdy wpisze url /post/1 otrzymam dane posta 1, jak 2 to dane posta 2 itp.

Gdy wpisze samo /post dostane liste postow - ale wszystkich!

W kontrolerze pobierającym dane moge ograniczyc dane do np. user_id===Yii::app()->user->id,

ale skolei tych akcji kontrolera też jest niemało, więc

zmiana tego w każdym miejscu jest niewygodna. Inna rzecz, że nawet jak

powyrzucam z widoku linki do tych akcji, ktoś znający budowę standardowej

aplikacji w yii, może zacząć ‘preparować’ linki - i co wtedy?

Więc jak uniemożliwić podejrzenie posta o ID=2 osobie, która nie jest jego

autorem?

Jak to ugryźć?

Najchętniej bym filtrował dane już na poziomie samego modelu tak żeby

odwołując się do niego z akcji kontrolera miał pewność, że ograniczy mi

serwowane dane do tych przypisanych danemu użytkownikowi.

Czytałem już trochę o ‘scopes’, ‘filtrach’ i ‘behaviors’, ale nie wiem jeszcze

jak się do nich zabrać i które rozwiązanie jest najlepsze.

A jakie Wy stosujecie?

Kolejny bardziej złożony przypadek (bardziej mnie interesuje) to np.

osoba przypisana do danego wydziału (department) np. na uczelni. Wydział oferuje nauke na

wielu kierunkach, zatrudnia wielu pracowników i zrzesza wielu studentów itd.

Załóżmy, że do ‘panelu’ loguje się taki pracownik. Na podstawie jego ID chcemy

odczytać do jakiego wydziału jest przypisany (department_id), chcemy mu wylistować pracowników

w tym wydziale itd.

Jak najlepiej zrealizować coś takiego? Po uwierzytelnieniu znamy ID

użytkownika. Po relacji (deparment_id) możemy odczytać do jakiego wydziału

jest przypisany itd. Jak realizując pobieranie danych zabezpieczyć się przed

‘preparowaniem’ linków - tak żeby mieć pewność, że zalogowany zobaczy jedynie

te dane, które powinien?

Trochę się rozpisałem… ale chciałem mieć pewność, że dobrze opiszę istote

problemu. Za wszelkie sugestie, rozwiązania, liki z góry dziękuję.

Witam,

nie mam w tej chwili za dużo czasu (za 15 minut start 10km klasykiem ;) ), ale postaram się rozwinąć temat później. Mówiąc krótko, zapoznaj się z RBAC.

Koniecznie przeczytaj:

dział o autoryzacji w podręczniku Yii: http://www.yiiframew.../en/topics.auth a szczególnie od punktu 6.

wpis na wiki yii: http://www.yiiframew…cal-rbac-scheme.

wpis na wikipedii: http://en.wikipedia…_access_control

W podręczniku yii zwróć uwagę na reguły biznesowe, bo kilkoma prostymi testami logicznymi możesz odwalić kawał dobrej roboty i ułatwić sobie życie.

Uważam się za osobę o przeciętnej inteligencji, a czytać to wszystko musiałem kilka razy, żeby do mnie dotarło w 100% jak działa RBAC. W razie czego więc nie zniechęcaj się i czytaj raz jeszcze:).

Cześć,

jeśli blog jest prywatny i posty w nim ma widzieć tylko właściciel, to do każdego wybierania z bazy dodaj ograniczenie dla konkretnego author_id (czy jak tam się nazywa kolumna w bazie). Możesz to zrobić za pomocą ‘scopes’,np. tak:




class Post extends CActiveRecord

{

    ......

    public function scopes()

    {

        return array(

            'own'=>array(

                'condition'=>'author_id='.Yii::app()->getUser()->getId(),

            ),

        );

    }

}

Post::model()->own()->findAll();

$model=Post::model()->findByPk(2); // zwróci post o pk=2, author_id nie ma znaczenia

$model=Post::model()->own()->findByPk(2); // zwróci null, jeśli author_id != Yii::app()->getUser()->getId()



jeśli każdy może przeglądać wszystkie blogi i chcesz wyświetlać tylko posty właścicieli, to Twój url musi zawierać informację o użytkowniku, którego blog jest aktualnie przeglądany (http://www.yiiframework.com/doc/guide/1.1/en/topics.url#using-named-parameters), np. jego username, albo id. Wtedy korzystasz z np. innego scope’a z parametrem:




class Post extends CActiveRecord

{

    ......

    public function author($id)

    {

        $this->getDbCriteria()->mergeWith(array(

            'condition'=>'author_id='.$id,

        ));

        return $this;

    }

}

Post::model()->author(2)->findAll(); // wszystkie posty autora o id=2

$model=Post::model()->author(2)->findByPk(2); // zwróci post o pk=2, jeśli autorem jest user o id=2, bądź null w przeciwnym wypadku



To samo, co wyżej.

d

Witam.

Cieszę się tak szybkiego odzewu. Dziękuję za sugestie i rozwiązania. RBAC’em się zainteresuję. Czytałem o nim wcześniej. Implementowałem ACL’a w Zendzie, a to wydaje mi się podobne, więc nie powinno być problemu. Dzięki za sugestię!

Co do scopów to fajnie, że są i tak jak podejrzewałem łatwo je można zaadoptować jako rozwiązanie opisanego przeze mnie problemu, jednak, może to zabrzmi naiwnie, spodziewałem się (oczekiwałem) czegoś innego. Dlaczego? Dlatego, że aby użyć tych zdefiniowanych scopów i tak muszę we wszystkich miejscach w kontrolerach czy w klasach innych modeli wymusić użycie scope’a, a to już jest błędogenne (gii wygenerował mi ileś tam odwołań do postów - teraz muszę odszukiwać każde z nich i wpisywać czy author czy nie author?).

Spodziewam się, że bez mojej implementacji się nie obejdzie jednak zanim w yii zaczne pisać coś większego niż blog wolałbym znać odpowiedź na większość nasuwających się wątpliwości.

Dziękuję raz jeszcze za odpowiedzi i liczę na kolejne. :wink:

Pozdrawiam.

edit.

Teraz w sumie pomyślałem, że to bardziej akcje kontrolerów wygenerowane przez gii mnie tak stresują. Gdybym utworzyl na ich bazie swoje np. /user/profile zamiast /user/view/ID, a takie akcje jak search czy admin zaremował, to wiele by mi to pomogło.

Cześć,

to tylko framework, a scaffolding nie zrobi za Ciebie gotowego projektu. Jednak jest jeszcze jeden rodzaj scope’ów: http://www.yiiframework.com/doc/guide/1.1/en/database.ar#default-scope, więc może to rozwiąże Twój problem modyfikacji kodu w każdej akcji, która tego wymaga.

Jeśli chcesz, by Gii za każdym razem tworzyło szablon z jakiego chciałbyś korzystać rzuć okiem na ten temat: http://www.yiiframework.com/doc/guide/1.1/en/topics.gii#extending-gii

d

Witaj!

I to jest właściwa odpowiedź! Wczoraj już wyłączyłem komputer, ale niedługo po tym olśniło mnie, że przecież czytałem o czymś takim jak default scope’s, i pewnie tego spróbuję!

Ważniejsze jest jednak to co napisałeś na samym początku - scaffolding nie zrobi za mnie projektu. Faktycznie zachłysnąłem się ‘gotowością’ wygenerowanej przez gii aplikacji. Pocieszające jest to, że niewielkim kosztem można to dostosować do swoich potrzeb.Dopiero po pewnym czasie zdałem sobie sprawę, że ja tak naprawdę wiem jak zrealizować to zadanie, a bardziej skupiłem się nad ‘dostosowaniem’ akcji wygenerowanych przez gii. Przy okazji natknąłem się na (krytyczny?) artykuł dotyczący gii:

weavora.com/blog/2012/02/11/scaffolding-in-yii-thoughts-about-gii

Jeszcze jedno pytanie pośrednio związane z moim problemem. Czy Waszym zdaniem bezpiecznie i wydajnie jest aby w klasie WebUser zapisać przez $this->setState np. wspomniany przeze mnie departmentId (zalogowany użytkownik przypisany jest do konkretnego wydziału)? W aplikacji będzie wiele rzeczy połączonych właśnie przez deparmentID. Czy lepiej jest zapamiętać jedynie user->id a później już w aplikacji normalnie User::model()->findByPk(ID)->department->id?

I co myślicie o zastosowaniu metody model w klasie WebUser?




public function getModel() 

{

    $user = $this->loadUser(Yii::app()->user->id);

    return $user;

}


protected function loadUser($id=null)

{

    if($this->_model===null)

    {

        if($id!==null)

            $this->_model=User::model()->findByPk($id);

    }

    return $this->_model;

}



Dzięki czemu wywołujemy jedynie przez:

Yii::app()->user->model->department->id;

Pozdrawiam.

Cześć,

stosuje podobne rozwiązanie (getModel) do podanego przez Ciebie, z tą różnicą, że cache’uje cały model użytkownika.

Jeśli opierasz na jakiejś danej pobieranej z bazy działanie aplikacji to nie ma sensu pobierać jej przy każdym odświeżeniu strony. Jak najbardziej stosuj takie metody jak set- i getState, właśnie po to są.

d

Dziękuje wszystkim za odpowiedzi, pozdrawiam

Chciałbym się dołączyć do zainstniałej sytuacji.

Jak napisał kolega wyżej, udało mi się wyświetlać zadania i projekty użytkownikowi o danym id, który jest przypisany do danego zadania .

Problem w tym , że gdy administrator jest zalogowany również widzi tylko zadania przypisane tylko jemu.

Co zrobić by widział wszystkie zadania, nie tylko te do których jest przypisany .

funkcja task.php:


public function defaultScope()

  {

    return array(

      'condition'=>"user_id='".Yii::app()->user->id."'",

    );

  }

Jeżeli przypisałeś użytkownikowi "Rolę" to można to zrobić tak:




public function defaultScope(){

  if(Yii::app()->user->checkAccess('administrator')){

    return array();

  }else{

    return array(

      'condition'=>"user_id='".Yii::app()->user->id."'",

    );

  }

}



Dzieki, wrzuciłem od razu moduł uprawnień użytkowników , tego mi brakowało.

Witam, kolejny raz mam problem z uwierzytelnieniem.

Sprawa wygląda tak, posiadam model Task i Project. W projektach wyświetlam od razu zadania. W kontrolerze Tasku posiadam właśnie funkcje :


public function defaultScope() {

        if (Yii::app()->user->checkAccess('administrator')) {

            return array();

        } else {

            return array(

                'condition' => "user_id='" . Yii::app()->user->id . "'",

            );

        }

    }

W tabeli TASK istnieje user_id, a w projekcie nie , i gdy wyświetla się od razu zadanie w projektach to dostaje error z Joinem :


CDbCommand failed to execute the SQL statement: SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'author_id' in where clause is ambiguous. The SQL statement executed was: SELECT COUNT(DISTINCT `t`.`id`) FROM `project` `t` LEFT OUTER JOIN `user_project` `users_users` ON (`t`.`id`=`users_users`.`project_id`) LEFT OUTER JOIN `user` `users` ON (`users`.`id`=`users_users`.`user_id`) LEFT OUTER JOIN `user` `author` ON (`t`.`author_id`=`author`.`id`) LEFT OUTER JOIN `task` `tasks` ON (`tasks`.`project_id`=`t`.`id`) WHERE (author_id='79') 

Dla tego pewnie ponieważ w projekcie nie posiadam user_id i tutaj problem. Jak to zrobić?

Nazwa kolumny author_id w WHERE nie jest jednoznaczna. Czyli masz ją w task i pewnie w project. System nie wie o którą chodzi. Powinieneś poprzedzić ją nazwą tabeli lub aliasem.

Tak dokładnie .

Ale tu jednak chodzi o "user_id" a nie "author_id" <-błąd występował gdy zamiast "user_id" było wpisane "user_id".

Więc teraz wyrzuca mi :


CDbCommand failed to execute the SQL statement: SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'user_id' in on clause is ambiguous. The SQL statement executed was: SELECT COUNT(DISTINCT `t`.`id`) FROM `project` `t` LEFT OUTER JOIN `user_project` `users_users` ON (`t`.`id`=`users_users`.`project_id`) LEFT OUTER JOIN `user` `users` ON (`users`.`id`=`users_users`.`user_id`) LEFT OUTER JOIN `user` `author` ON (`t`.`author_id`=`author`.`id`) LEFT OUTER JOIN `task` `tasks` ON (`tasks`.`project_id`=`t`.`id`) AND (user_id='49') 

W tym przypadku user_id nie istnieje w "project" , ale istnieje w "task" więc dla czego kolumna "user_id" nie jest jednoznaczna?

Ale jest w "user_project". Używaj aliasów - dobry zwyczaj.

W default scope przy user_id brakuje aliasu. Poczytaj tutaj: http://www.yiiframework.com/forum/index.php/topic/17595-column-disambiguation-on-named-scopes/

według tego co podesłałeś :


public function defaultScope() {

        if (Yii::app()->user->checkAccess('administrator')) {

            return array();

        } else {

            return array(

                      'condition' => "$this.'.user_id='" . Yii::app()->user->id . "'",

            );

        }

    }

Wypluwa:


Object of class Project could not be converted to string 

Raczej coś takiego:




    'condition' => "{$this->tableAlias}.user_id = '" . Yii::app()->user->id . "'",




ublic function defaultScope() {

        if (Yii::app()->user->checkAccess('administrator')) {

            return array();

        } else {

            return array(

                      'condition' => "{$this->users}.user_id = '" . Yii::app()->user->id . "'",

            );

        }

    }

Zrobiłem tak , jako alias dałem relacje z "projektu" i otrzymałem :


Array to string conversion 

Masz podać alias tabeli (string), a podajesz relację (tablicę). Ogarnij się!

Jak już, to coś w tym rodzaju:


public function defaultScope() {

        if (Yii::app()->user->checkAccess('administrator')) {

            return array();

        } else {

            $tabAlias = ... //Wyciągnij alias z właściwego modelu

            /*

            Coś w tym rodzaju:

            $tabAlias = Task::model()->getTableAlias();

            */

            return array(

                      'condition' => $tabAlias . ".user_id = '" . Yii::app()->user->id . "'",

            );

        }

    }



robiłem tak już wcześniej ale za każdym razem wyrzucało mi ze nie ma kolumny user_id w taskach.


Column not found: 1054 Unknown column 't.user_id' in 'where clause'.