CakePHP在构建模型之前执行Auth?

时间:2014-04-16 03:21:43

标签: cakephp cakephp-2.4

在我的CakePHP应用程序中,我有多租户,它通过独立的数据库提供(每个租户都有自己的,特定于租户的数据库)。

还有一个全球性的'包含用户和租赁信息的数据库。租户' table包含特定租户占用的数据库的名称。每个用户都包含一个tenant_id。

结构:

global_db:
    users (contains tenant_id foreign key)
    tenants (contains tenant-specific database name, ie: 'isolated_tenant1_db')

isolated_tenant1_db:
    orders
    jobs
    customers

isolated_tenant2_db:
    orders
    jobs
    customers

当用户通过表单/会话登录时,此系统可正常工作。当他们通过/ Users / login登录时,他们的租赁被验证,存储在Session中,并且数据库参数被加载,因此他们自己的“隔离”了。模型可以使用这种动态连接。

但是,当用户尝试通过Basic Auth登录并直接请求他们想要访问的控制器功能时,会出现问题。例如/Orders/view/1.xml。 在这种情况下,CakePHP尝试构建“订单”。在用户登录之前的模型,因此在任何租赁信息可用之前 - 这意味着它不知道要连接到哪个数据库以访问订单。

从把debug()语句放到这个地方我可以看到构造/执行models / controllers / auth的顺序如下(执行/Orders/view/1.xml时):

  1. 模型__construct:用户
  2. Controller __construct:OrdersController
  3. 模型__construct:权限
  4. 模型__construct:订单
  5. function:OrdersController / beforeFilter
  6. AuthComponent __startup
  7. 模型__construct:与订单相关的模型
  8. 我的问题是AuthComponent :: _ startup是在构建Order Model之后执行的。在此之前我需要尝试登录用户(并获取他们的数据库信息)' Order'模型构建。

    问题:

    • 是什么原因导致User模型先于其他内容构建? (我也启用了默认的CakePHP ACL)
    • 如果请求包含BasicAuth标头,我可以在应用程序中调用Auth-> gt; login()来尝试登录,这将在尝试加载特定于租户的模型之前执行吗?我假设把它放在用户__construct里面是一个非常糟糕的主意。

    ==更新01/05/2014 == 插入代码示例。

    bootstrap.php中: 检查是否正在向api发出请求。子域:

    // Determine whether the request is coming from the api.* subdomain, and if so set the API_REQUEST define to true. 
    if (preg_match('/^api\./i',$_SERVER['HTTP_HOST']))
    {
        define('API_REQUEST',true);
    
        // Any links generated (in emails etc), will contain the full base url. If a cron job logged in via the API is generating
        // those e-mails, then users will receive links to api.mydomain, instead of just mydomain.
        $full_base_url = Router::fullBaseUrl();
        $new_full_base_url = preg_replace('/\/\/api\./i', '//', $full_base_url);
        Router::fullBaseUrl($new_full_base_url);
        CakeLog::write('auth_base_url_debug', 'modified fullbaseurl from ' . $full_base_url . ' to ' . $new_full_base_url);
    }
    else
    {
        define('API_REQUEST',false);
    }
    

    AppController.php:

    public $components = array(
                'Security',
                'Session',
                'Acl',
                'Auth' => array(
                        'className' => 'ExtendedAuth',
                        'authenticate' => array(
                                'FormAlias',
                        ),
                        'authorize' => array(
                                'Actions' => array('actionPath' => 'controllers')
                        ),
                        'loginRedirect' => array('controller' => 'Consignments', 'action' => 'index'),
                        'logoutRedirect' => array('controller' => 'Users', 'action' => 'login'),
                ),
                //'Users.RememberMe',
        );
    
    function beforeFilter() 
    {
    // Reroute all requests to API subdomain (ie: api.mydomain) to api_ prefixed actions.
            // Also, enable Basic Authentication if the user is accessing via api.*
            // If login fails, return 401 error instead of 302 redirect to login page.
            if(API_REQUEST == true)
            {   
                $this->params['action'] = 'api_'.$this->params['action'];   // prefix the actions with api_
    
                $this->Auth->authenticate = array('BasicAlias');            // Switch to using Basic Authentication
    
                if($this->Auth->login() == false)                           // Attempt Basic Auth Login
                {   // Login failed
                    CakeLog::write('auth_api', 'Unauthorized API request to: ' . $this->params['action']);
                    header("HTTP/1.0 401 Unauthorized");                    // Force returning an Unauthorized header (401)
                    exit;                                                   // MUST BE CALLED TO PREVENT 302 BEING SENT!
                }
            }
    }
    

    重要的是要注意,BasicAlias Auth Component不包含在AppController中的$ components中,但如果请求是api。*子域,则动态使用。但是,构造类的顺序不会影响BasicAlias AuthComponent是否包含在$ components中,或者如上所示动态使用。

    AppModel:

    function __construct($id = false, $table = null, $ds = null)
    {       
        if(($ds == null) && ($this->use_tenant_database == true))
        {           
            // Create a connection to the tenants database and configure model to use this connection.          
            $Tenant = ClassRegistry::init('Tenant');
    
            $db_name = $Tenant->checkAndCreateTenantDatabaseConnectionForCurrentUser();
    
            if($db_name == false)
            {
                header("HTTP/1.0 500 Server Error");                    // Force returning a Server Error Header (500)
                debug('AppModel::$db_name = false, unable to proceed');
                CakeLog::write('tenant_error', 'db_name = false, unable to connect.');
                exit;                                                   // MUST BE CALLED TO PREVENT 302 BEING SENT!
            }
    
            // Point model to the tenant database connection:
            $this->useDbConfig = $db_name;
        }
    
        parent::__construct($id, $table, $ds);
    }
    

    然后在任何使用特定租户数据库的模型中:

    class Order extends AppModel
    {
        var $use_tenant_database = true;
            ...
    }
    

    Tenant.php:

    /**
         * Check whether a connection to the current users tenant database has already been created and if so, return its name.
         * Otherwise, create the connection and return its name.
         * 
         * @return boolean|Ambigous <mixed, multitype:, NULL, array, boolean>
         */
        public function checkAndCreateTenantDatabaseConnectionForCurrentUser()
        {
            // Check whether we have the tenants database connection information available in the Configure variable:
            if(Configure::check('Tenant.db_name') == true)
            {   // the db_config is available in configure, use it!
                $db_name = Configure::read('Tenant.db_name');
            }
            else
            {   // The tenants db_name has not been set in the configure variable, we need to create a database connection and then
                // set the configure variable.
                $tenant_id = $this->getCurrentUserTenantId();
    
                if($tenant_id == null)
                {   // Unable to resolve the tenant_id, instead, connect to the default database.
                    debug('TRIED TO CONSTRUCT MODEL WITHOUT KNOWING TENANT DATABASE!!'); 
                                exit;
                }
    
                $db_name = $this->TenantDatabase->createConnection($tenant_id);
    
                if($db_name == false)
                {   // The database connection could not be created.
                    CakeLog::write('tenant_error', 'unable to find the database name for tenant_id: ' . $tenant_id);
                    return false;
                }
    
                Configure::write('Tenant.db_name', $db_name);
            }
    
            return $db_name;
        }
    

    因此,如果用户请求URL,例如: http://api.mydomain.com/Orders/getAllPendingOrders 在他们提供BASIC auth凭证以及请求的情况下,会发生的是按以下顺序构造/执行类:

    1. 模型__construct:用户
    2. Controller __construct:OrdersController
    3. 模型__construct:权限
    4. 模型__construct:订单
    5. 模型__construct:租户
    6. Model __construct:TenantDatabase
    7. function:OrdersController / beforeFilter
    8. AuthComponent __startup - &gt;然后执行登录。
    9. 模型__construct:其他模型。
    10. 问题是:正在构建用户已登录的Order.php,这意味着当执行AppModel.php中的代码时:

      $db_name = $Tenant->checkAndCreateTenantDatabaseConnectionForCurrentUser();
      

      无法确定用户当前的租期。

      我需要找到一个解决方法,要么通过以某种方式在构造Order.php之前执行登录,或者黑客攻击,以便在尝试构建具有$ use_tenant_database = true且用户不是的模型时登录后,此时执行BasicAuth尝试登录用户..但是这对我来说是错误的。

3 个答案:

答案 0 :(得分:3)

您可能需要查看Cake的文档中的Authorization (who’s allowed to access what)部分。具体来看isAuthorized函数及其工作原理。

您的订单控制器中可能需要这样的内容:

// app/Controller/OrdersController.php

public function isAuthorized($user) {
    // All registered users can add posts
    if ($this->action === 'add') {
        return true;
    }

    // The owner of an order can edit and delete it
    if (in_array($this->action, array('edit', 'delete'))) {
        $orderId = (int) $this->request->params['pass'][0];
        if ($this->Order->isOwnedBy($orderId, $user['id'])) {
            return true;
        }
    }

    return parent::isAuthorized($user);
}

答案 1 :(得分:0)

在app控制器中过滤请求生命周期回调之前实现逻辑。

Controller :: beforeFilter(): 此功能在控制器中的每个操作之前执行。这是检查活动会话或检查用户权限的便利位置。

http://book.cakephp.org/2.0/en/controllers.html

答案 2 :(得分:0)

事实证明,这些模型是由“Search.Prg”构建的。插件,一个用于处理搜索/过滤结果的CakeDC插件。正在执行组件中的initialize()函数,并导致在用户登录之前构建模型。

解决这个问题的方法是将Basic Auth检查/登录过程从AppController beforeFilter移动到ExtendedAuthComponent(我自己的自定义authenciation组件)初始化函数。 结束代码是这样的:

ExtendedAuthComponent.php

public function initialize(Controller $controller) 
    {
        parent::initialize($controller);    // Call parent initialization first, this sets up request and response variables.
        $this->controller = $controller;

        // Reroute all requests to API subdomain (ie: api.rms.roving.net.au) to api_ prefixed actions.
        // Also, enable Basic Authentication if the user is accessing via api.*
        // If login fails, return 401 error instead of 302 redirect to login page.
        if(API_REQUEST == true)
        {   
            $controller->params['action'] = 'api_'.$controller->params['action'];   // prefix the actions with api_

            if($this->loggedIn() == false)                                  // Attempt Basic Auth Login
            {   // Login failed
                $this->authenticate = array('BasicAlias');                  // Switch to using Basic Authentication
                if($this->login() == false)
                {
                    CakeLog::write('auth_api', 'Unauthorized API request to: ' . $this->params['action']);
                    header("HTTP/1.0 401 Unauthorized");                    // Force returning an Unauthorized header (401)
                    exit;                                                   // MUST BE CALLED TO PREVENT 302 BEING SENT!
                }
            }
        }
    }

这会导致用户在运行Search.Prg组件initialize()函数之前通过Basic Auth登录,这意味着在构建模型之前确定用户租期,从而解决问题。