[轉貼] Access Control List (ACL) with Code Igniter

From: http://jorgealbaladejo.com/2009/04/24/access-control-list-acl-with-code-igniter/

Access Control List (ACL) with Code Igniter

In the last project I’ve worked on, I needed to install any kind of ACL to allow certain methods to be accessed only by some user roles, like website administration, etc. I’m building this website on Code Igniter, so I missed some related features that are available in other PHP frameworks like CakePHP or Zend.

After googling a bit, I found our several methods to get an ACL. Zend framework can be integrated into Code Igniter to use its library, but doesn’t seem really natural to me. I prefer having an previously constructed list, than building it ‘on the fly’ inside my code.

Then I fell into phpGACL, a free software project which allows to solve this problem on an easy way. It uses an API to connect to a database, where tuples of ‘controller’ – ‘method’ – ‘user’ can be stored (actually, AXO – ACO – ARO, acronyms of Access eXtended Object, Access Control Object, Access Request Object). And I developed my first version over this tool, on the most transparent way I was able to find:

  • There is a MY_Controller class where all other controllers inherit from
  • Constructor in this class launches a method, also in the parent controller, like $this->checkAccess(), so ACL is always evaluated before launching any other controller.
  • Method checkAccess gets user ID and role from session values (typically, in database) and calls phpGACL passing it three parameters: the user role, and current method and controller (both taken from the url string)

Note that I used the parent controller constructor to launch the user access control, but I could have used a hook. However, I preferred this for the sake of visibility: I’ll never forget what’s exactly running on every call to my controllers. But, this is only a matter of personal style.

The code would have looked like this:

class MY_Controller extends Controller
{
  var $gacl = NULL;
  function MY_Controller()
  {
    parent::Controller();

    // acl initialization
    include_once('path_to_phpgacl_library');
    $this->gacl = new gacl(array('db_host'     => 'acl_host',
                                 'db_user'     => 'acl_user',
                                 'db_password' => 'acl_pass',
                                 'db_name'     => 'acl_base',
                                 'db_type'     => 'acl_type' ) );
    // does the user have access?
    if (!$this->checkAccess())
    {
      redirect('','refresh');
    }
  }

  /**
     * Uses phpGACL library to determine user access.
     * Checks if a user has access to a method on a controller.
     * Takes user role from session.
     * Takes method and controller from $this->uri attribute.
     *
     * @return boolean if access is granted
     *
     */
  function checkAccess()
  {
    // variables
    $method     = $this->uri->rsegments[2];
    $controller = $this->uri->rsegments[1];
    $role       = $this->rdauth->getUserRole();
    //

    // I've created sections 'methods' for ACO's,
    //  'users' for ARO's and 'controllers' for AXO's
    return ( $this->gacl->acl_check( 'methods', $method,
                                     'users', $role,
                                     'controllers', $controller ) );
  }
}

Note also that this method runs automagically, converting url strings (controller and method) to phpGACL calls. Even though loading the default method for a controller (/index is omitted), internally CI auto fills this value as if it was written in url.

User role, as stated before, is returned by a custom library called rdauth, which will check session cookie to determine if there is a user logged in or not.

I used this system along several weeks, but finally I realized that – for my particular case – having all ACL definition in a database was not a good idea. Although phpGACL has an easy to use web interface to create these lists, and access control was separated from code, I found out these potential problems:

  1. Of course, ACL’s are out of code. That means that it database fails, or any careless user changes something there, all sites whose permissions depend on this database will allow the wrong user roles to access the undesired forbidden controller / methods. Not a good piece of news for paranoid administrators.
  2. Unless I create several databases, all historic branches, develop version and production version will share the same permissions. This is a bit messy to work with.
  3. When I’m coding I don’t have a clear idea of what methods can be accessed by the user role that I’m testing. If I develop a new method to test, I need to go to web admin tool to add it.

So there are benefits of using phpGACL, but also derived problems. Was at this point, when talking to a friend about this, that we realized that actually doesn’t matter if ACL’s are in databases or just arrays.

So the approach of the final solution implemented is:

  • Let’s change phpGACL with an access array in every controller
  • Let’s change checkAccess method to look into this array instead of calling phpGACL API

The main flow for the users access control remains unchanged, and it is still transparent from a coder’s view. Even we gain control over what’s happening with every controller, since $access array is very descriptive:

var $access = array('login'                  => array('visitors') ,
                    'resetPassword'          => array('visitors',
                                                      'registered',
                                                      'editors',
                                                      'managers',
                                                      'administrators') ,
                    'resetPasswordConfirm'   => array('visitors',
                                                      'registered',
                                                      'editors',
                                                      'managers',
                                                      'administrators') ,
                    'changePassword'         => array('registered',
                                                      'editors',
                                                      'managers',
                                                      'administrators') ,
                    'logout'                 => array('registered',
                                                      'editors',
                                                      'managers',
                                                      'administrators') );

Each method in the current controller has an entry in $access array. This entry is an array with all the roles that can load every method. Simple, isn’t it?

MY_Controller class then would look like:

class MY_Controller extends Controller
{
  var $gacl = NULL;
  function MY_Controller()
  {
    parent::Controller();

    // does the user have access?
    if (!$this->checkAccess())
    {
      redirect('','refresh');
    }
  }

  /**
     * Uses every controller access array to determine user permissions.
     * Checks if a user has access to a method on a controller.
     * Takes user role from session.
     * Takes method and controller from $this->uri attribute
     *
     * @return boolean if access is granted
     * @see phpGACL
     *
     */
    function checkAccess()
    {
        // variables
        $method = $this->uri->rsegments[2];
        $role    = $this->rdauth->getUserRole();
        //

        // check access
        if ($method)
        {
            if (array_key_exists($method, $this->access))
            {
                if (in_array($role, $this->access[$method]))
                {
                    return(true);
                }
            }
        }
        return (false);
    }

  }

So now, instead of just calling phpGACL API, I’m going over the access array, checking if there is defined an array of permissions for current method, and then if this array contains the current user role.

I haven’t finished defining users access control yet. There are further checks to forbid, for instance, avoiding that some user edited other users than itself, but these kind of ID checking will be implemented later (and perhaps explained on the following post).

What do you think? Do you use other approaches to get a good users access control list?

廣告

為什麼 php 類別中成員變數前面需要冠上 var 字串 What does PHP keyword ‘var’ do?

From: http://stackoverflow.com/questions/1206105/what-does-php-keyword-var-do

這是用在 php4 的類別成員變數的宣告,也適用於 php5 。但因為 php5 配置較為嚴僅(E_STRICT)的關係 冠上 var 會被提出警告。

It’s for declaring class member variables in PHP4, and is no longer needed. It will work in PHP5, but will raise an E_STRICT warning in PHP from version 5.0.0 up to version 5.1.2, as of which it has been deprecated.

Example usage:

class foo {
    var $x = ‘y’; // or you can use public like…
    public $x = ‘y’; //this is also a class member variables.
    function bar() {}
}

var keyword 是在 PHP4 中宣告類別成員變數用的,而在 PHP5 中則改用 public、protext、private, 當然還是可以使用var宣告成員變數,但是不建議這樣做。

The var keyword is used to declare variables in a class in PHP 4:

class Foo {
    var $bar;
}

With PHP 5 property and method visibility (public, protected and private) was introduced and thus var is deprecated.

Check for Ajax request in Code Igniter

From: http://stackoverflow.com/questions/11687180/check-for-ajax-request-in-code-igniter

Q:

I’m in a PHP script and I want to check whether the request is an Ajax request. (basically so as NOT to allow direct script access, other than Ajax call that is…)

So, I’m defining IS_AJAX somewhere in the main index.php file :

define('IS_AJAX', 
       isset($_SERVER['HTTP_X_REQUESTED_WITH']) && 
       strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest');

And then checking it at the top of my script :

if (!IS_AJAX) exit('No direct script access allowed');

Since I’m new to CodeIgniter, I’m not really sure…

  • Is there any such built-in functionality?
  • Is there a more… elegant way to do it?

A:

You can use $this->input->is_ajax_request() from the input class:

if (!$this->input->is_ajax_request()) {
    exit(‘No direct script access allowed’);
}

jsonp 測試頁面 前端 html 及後端 php 範例

HTML:

<html>
<head>
<META HTTP-EQUIV="CONTENT-TYPE" 
CONTENT="text/html; charset=UTF-8">
<script type='text/javascript' src='http://code.jquery.com/jquery-1.10.2.js'></script>
<script type='text/javascript'>
function show_msg(msg){
	alert(msg);
}
$(function(){
	$.ajax({
		type:'get',
		async:false,
		crossDamain:true,
		cache:false,
		url:'http://carbonfootprint.toyoink-ct.com.tw/carbonfootprint/login_valid',
		dataType:'jsonp',
		jsonCallback:'show_msg',
		data:{account:'jerry',passwd:'justdoit'}
	});
});
</script>
</head>
<body>
</body>
</html>

PHP:

    public function login_valid(){
        $account=$this->input->get('account');
        $passwd=$this->input->get('passwd');
        $query=$this->db->query("select * from carbon_passwd where id='{$account}' and passwd='{$passwd}';");
        if($query->num_rows()>0){
            $this->session->set_userdata($query->row(1));
            //print_r($this->session->all_userdata());
            echo "show_msg('密碼對了^^');";
        }
        else{
            echo "show_msg('你打的帳號:{$account},密碼:{$passwd}不對喔!!');";
        }
    }

php 類別裏如何呼叫自已的method來完成array_map()

參考:http://tw2.php.net/manual/en/language.pseudo-types.php#language.types.callback

To use object methods with array_map(), pass an array containing the object instance and the method name. For same-object scope, use $this as normal. Since your method name is defined in your public $func property, you can pass $this->func. This applies to most functions that accept a callback as an argument.

As a side note, the parentheses outside array_map() aren’t necessary.

return array_map(array($this, $this->func), range($min, $max));

重點的關鍵在使用 $this 變數,以下是我在類別中使用的寫法。這是在類別內相關聯的程式片段

    private $quarters_arr;
    private $item2index;
    private $index2item;
    function __construct(){
        parent::__construct();
        $quarters_arr=array();
        $item2index=array("electric_power"=>1,"derv"=>2,"natural_gas"=>38,"gas"=>3,"LPG"=>39,"tap_water"=>12);
        $index2item=array_flip($item2index);
    }

    function index2item($a){
        $a->item_id=$index2item[$a->item_id];
        return $a;
    }

    function get_quarter_energy_amount($target_quarter){
        $response_json_data=Array();
        $energy_use=$this->db->query("select * from carbon_energy_use_statistics where quarter='{$target_quarter}'");
        $energy_use_array=(array)$energy_use->result();
        $energy_use_array=array_map($this->index2item,$energy_use_array);
        $response_json_data['quarter']=$target_quarter;
        $response_json_data['production']=$this->db->query("select production from quarter_production where quarter='{$target_quarter}';")->row(1)->production;
        $response_json_data['usage_amount']=$energy_use_array;
        return (array)$response_json_data;
    }

codeigniter 類別中自訂義有函式使用「$this->index2item」

$energy_use_array=array_map($this->index2item,$energy_use_array);

PHP 判斷物件是否是哪個類別型態?

From: http://stackoverflow.com/questions/9568793/php-check-for-instance-of-datetime

使用 instanceof 或是 is_a 都可以判斷變數是否是哪個類型。

instancof

if ($var instanceof DateTime) {
    // true
}

is_a

if (is_a($var, ‘DateTime’) {
    // true
}

至於 instanceof 和 is_a 之間的差異

From: http://stackoverflow.com/questions/3017684/what-is-the-difference-between-is-a-and-instanceof

Q:

I am aware that instanceof is an operator and that is_a is a method.

Is the method slower in performance? What would you prefer to use?

A:

Actually, is_a is a function, whereas instanceof is a language construct. is_a will be significantly slower (since it has all the overhead of executing a function call), but the overall execution time is minimal in either method.

It’s no longer deprecated as of 5.3, so there’s no worry there.

There is one difference however. 

is_a being a function takes an object as parameter 1, and a string as parameter 2. 

instanceof can take either a string as parameter, object, or an identifier (class name written without quotes).

轉貼:PHP抓取網頁和分析

有些網頁的內容是常常變動的,但是又不可能一直盯著看,所以研究看看怎麼用 PHP 抓取網頁內容。

From:http://my.opera.com/kmgocc/blog/show.dml/322416

http://272586.blogspot.com/2007/05/php.html

http://www.fantxi.com/blog/show-618-1.html

http://www.inote.tw/2009/04/php-curl.html

譯者:limodou

抓取和分析一個文件是非常簡單的事。這個教程將通過一個例子帶領你一步一步地去實現它。讓我們開始吧!

首先,首先必須決定我們將抓取的URL地址。可以通過在腳本中設定或通過$QUERY_STRING傳遞。為了簡單起見,讓我們將變量直接設在腳本中。

<?
$url = 'http://www.php.net';
?>

第二步,我們抓取指定文件,並且通過file()函數將它存在一個數組裡。

<?
$url = 'http://www.php.net';
$lines_array = file($url);
?>

好了,現在在數組裡已經有了文件了。但是,我們想分析的文本可能不全在一行裡面。為瞭解決這個文件,我們可以簡單地將數組$lines_array轉化成一個字符串。我們可以使用implode(x,y)函數來實現它。如果在後面你想用explode(將字符串變量數組),將x設成"|"或"!"或其它類似的分隔符可能會更好。但是出於我們的目的,最好將x設成空格。y是另一個必要的參數,因為它是你想用implode()處理的數組。

<?
$url = 'http://www.php.net';
$lines_array = file($url);
$lines_string = implode('', $lines_array);
?>

現在,抓取工作就做完了,下面該進行分析了。出於這個例子的目的,我們想得在<head>到</head>之間的所有東西。為了分析出字符串,我們還需要叫做正規表達式的東西。

<?
$url = 'http://www.php.net';
$lines_array = file($url);
$lines_string = implode('', $lines_array);
eregi("<head>(.*)</head>", $lines_string, $head);
?>

讓我們看一下代碼。正如你所見,eregi()函數按下面的格式執行:

eregi("<head>(.*)</head>", $lines_string, $head);

“(.*)"表示所有東西,可以解釋為,"分析在<head>和</head>間的所以東西"。$lines_string是我們正在分析的字符串,$head是分析後的結果存放的數組。

最後,我們可以輸數據。因為僅在<head>和</head>間存在一個實例,我們可以安全的假設數組中僅存在著一個元素,而且就是我們想要的。讓我們把它打印出來吧。

<?
$url = 'http://www.php.net';
$lines_array = file($url);
$lines_string = implode('', $lines_array);
eregi("<head>(.*)</head>", $lines_string, $head);
echo $head[0];
?>

轉自WeberDev.com

如何抓取網站的內容,並解析其內容

<?
$http="http://272586.blogspot.com"; //您想抓取的網址
$buffer = file($http); //將網址讀入buffer變數
for($i=0;$i<sizeof($buffer);$i++) //將每段文字讀出來,以換行為單位,sizeof會傳回共有幾筆
{
$n1=strpos(" ".$buffer[$i],"<title>"); //檢查你要找的字,是否存在,假設我想找<title>中的內容為何,為什麼前面要加空白,因為如果找到位置如果是第一個位置是0,0跟找不到在判斷會有問題
if($n1>0)
{
$n2=strrpos($buffer[$i],"</title>"); //找出</title>的位置
$title=substr($buffer[$i],$n1+6,$n2-$n1-6); //+6的意思是<title>的長度減掉前面的一個空白,-6的話是把長度減掉
//utf-8 轉 big5
$title=iconv("UTF-8","big5",$title);
echo $title."<br>n"; //將title的內容值印出n代表顯示原始碼的時候會換行,<BR>是brower顯示會換行
}
}
?>

PHP抓網頁內容

1.file_get_contents

<?
$url = http://www.xxx.com/;
$contents = file_get_contents($url);
//如果出現中文亂碼使用下面代碼
//$getcontent = iconv("gb2312", "utf-8",file_get_contents($url));
//echo $getcontent;
echo $contents;
?>

2.curl
我們必須先建立一個「curl」的連線,也因此,必須使用到「$ch = curl_init()」這個函式。而為了怕建立連線忘了關閉。因此,必須先寫好關閉的函式,「curl_close($ch)」。

接下來,你可以設定他截取網頁的選項,一般來說常用的有「CURLOPT_RETURNTRANSFER」、「CURLOPT_URL」、「CURLOPT_HEADER」、「CURLOPT_FOLLOWLOCATION」、「CURLOPT_USERAGENT」這幾個選項。而這幾個選項分別代表「將結果回傳成字串」、「設定截取網址」、 「是否截取header的資訊」、「是否抓取轉址」及「瀏覽器的user agent」。最後,再執行「curl_exec($ch)」以取出結果就可以了。

<?
$url = "http://www.xxx.com/";
$ch = curl_init();
$timeout = 5;
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
//在需要用戶檢測的網頁裡需要增加下面兩行
//curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
//curl_setopt($ch, CURLOPT_USERPWD, US_NAME.":".US_PWD);
$contents = curl_exec($ch);
curl_close($ch);
echo $contents;
?>

以抓取yahoo為例,若我們要偽裝成google bot去抓取,那麼我們可以寫成下列的樣子:

$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_URL, "www.yahoo.com.tw");
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_USERAGENT, "Google Bot");
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$output = curl_exec($ch);
curl_close($ch);
echo $output;

也可以將選項們設定一個陣列,以增加設定時的閱讀度。這時就得動用「curl_setopt_array()」這個函式了:

$ch = curl_init();
$options = array(CURLOPT_URL => 'www.yahoo.com.tw',
                 CURLOPT_HEADER => false,
   CURLOPT_RETURNTRANSFER => true,
   CURLOPT_USERAGENT => "Google Bot",
   CURLOPT_FOLLOWLOCATION => true
           );
curl_setopt_array($ch, $options);
$output = curl_exec($ch);
curl_close($ch);
echo $output;

3.fopen->fread->fclose

<?
$handle = fopen ("http://www.xxx.com/", "rb");
$contents = "";
do {
   $data = fread($handle, 8192);
   if (strlen($data) == 0) {
   break;
   }
   $contents .= $data;
} while(true);
fclose ($handle);
echo $contents;
?>