title: CakePHP perform Auth before Constructing Models?
body: <p>In my CakePHP application I have multi-tenancy which is provided through isolated databases (each tenant has their own, tenant-specific database).</p>
  
  <p>There is also a 'global' database which contains users  and tenancy information. The 'tenants' table contains the name of which database the particular tenant occupies. Each user contains a single tenant_id.</p>
  
  <p>Structure:</p>
  
  <pre><code>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
  </code></pre>
  
  <p>This system works correctly when the user is logged in via forms / sessions. When they login through /Users/login their tenancy is verified, stored in Session, and database parameters are loaded so their own 'isolated' models can use this dynamic connection.</p>
  
  <p>However, issues arise when the user tries to login via Basic Auth, and directly request the controller function they want to access. For example /Orders/view/1.xml. 
  In this case, CakePHP attempts to construct the 'Order' Model before the user has been logged in, and therefore before any tenancy information is available - which means it has no idea what database to connect to in order to access orders.</p>
  
  <p>From putting debug() statements around the place I can see that the order in which models / controllers / auth are constructed / executed is as follows (when executing /Orders/view/1.xml):</p>
  
  <ol>
  <li>Model __construct: User</li>
  <li>Controller __construct: OrdersController</li>
  <li>Model __construct: Permission</li>
  <li>Model __construct: Order</li>
  <li>function: OrdersController/beforeFilter</li>
  <li>AuthComponent __startup</li>
  <li>Model __construct: Models related to Order</li>
  </ol>
  
  <p>My problem is that AuthComponent::_startup is executed after Order Model has been constructed. I need to attempt to login the user (and get their database information) before this 'Order' model is constructed.</p>
  
  <p>Questions:</p>
  
  <ul>
  <li>What causes the User model to be constructed before anything else? (I also have the default CakePHP ACL enabled)</li>
  <li>Where in the App can I put a call to Auth->login() to attempt login if the request contains BasicAuth headers, that will be executed prior to trying to load tenant-specific models? I assume putting this inside User __construct is a very bad idea.</li>
  </ul>
  
  <p><strong>== UPDATE 01/05/2014 ==</strong>
  Inserting code samples.</p>
  
  <p>bootstrap.php:
  Checks whether the request is being made to api. subdomain:</p>
  
  <pre><code>// 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);
  }
  </code></pre>
  
  <p>AppController.php:</p>
  
  <pre><code>public $components = array(
              'Security',
              'Session',
              'Acl',
              'Auth' =&gt; array(
                      'className' =&gt; 'ExtendedAuth',
                      'authenticate' =&gt; array(
                              'FormAlias',
                      ),
                      'authorize' =&gt; array(
                              'Actions' =&gt; array('actionPath' =&gt; 'controllers')
                      ),
                      'loginRedirect' =&gt; array('controller' =&gt; 'Consignments', 'action' =&gt; 'index'),
                      'logoutRedirect' =&gt; array('controller' =&gt; 'Users', 'action' =&gt; '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-&gt;params['action'] = 'api_'.$this-&gt;params['action'];   // prefix the actions with api_
  
              $this-&gt;Auth-&gt;authenticate = array('BasicAlias');            // Switch to using Basic Authentication
  
              if($this-&gt;Auth-&gt;login() == false)                           // Attempt Basic Auth Login
              {   // Login failed
                  CakeLog::write('auth_api', 'Unauthorized API request to: ' . $this-&gt;params['action']);
                  header("HTTP/1.0 401 Unauthorized");                    // Force returning an Unauthorized header (401)
                  exit;                                                   // MUST BE CALLED TO PREVENT 302 BEING SENT!
              }
          }
  }
  </code></pre>
  
  <p>It is important to note that BasicAlias Auth Component is not included in the $components within AppController, but used dynamically if the request is to the api.* subdomain. However, the order in which classes are constructed has no effect whether BasicAlias AuthComponent is included in $components, or used dynamically as shown above.</p>
  
  <p>AppModel:</p>
  
  <pre><code>function __construct($id = false, $table = null, $ds = null)
  {       
      if(($ds == null) &amp;&amp; ($this-&gt;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-&gt;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-&gt;useDbConfig = $db_name;
      }
  
      parent::__construct($id, $table, $ds);
  }
  </code></pre>
  
  <p>And then within any models which use a specific tenant database:</p>
  
  <pre><code>class Order extends AppModel
  {
      var $use_tenant_database = true;
          ...
  }
  </code></pre>
  
  <p>Tenant.php:</p>
  
  <pre><code>/**
       * 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 &lt;mixed, multitype:, NULL, array, boolean&gt;
       */
      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-&gt;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-&gt;TenantDatabase-&gt;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;
      }
  </code></pre>
  
  <p>So, if the user requests a URL for example:
  <a href="http://api.mydomain.com/Orders/getAllPendingOrders" rel="nofollow">http://api.mydomain.com/Orders/getAllPendingOrders</a>
  Where they have supplied BASIC auth credentials along with the request, then what happens is that classes are constructed / executed in the following order:</p>
  
  <ol>
  <li>Model __construct: User</li>
  <li>Controller __construct: OrdersController</li>
  <li>Model __construct: Permission</li>
  <li>Model __construct: Order</li>
  <li>Model __construct: Tenant</li>
  <li>Model __construct: TenantDatabase</li>
  <li>function: OrdersController/beforeFilter</li>
  <li>AuthComponent __startup --> This then performs the login.</li>
  <li>Model __construct: other models.</li>
  </ol>
  
  <p>The problem is: Order.php is being constructed the user has been logged in, which means when the code in AppModel.php is executed:</p>
  
  <pre><code>$db_name = $Tenant-&gt;checkAndCreateTenantDatabaseConnectionForCurrentUser();
  </code></pre>
  
  <p>It is unable to determine the users current tenancy.</p>
  
  <p>I need to find out a workaround for this, either by somehow performing the login BEFORE Order.php is constructed, or hacking it so that if you attempt to construct a model which has $use_tenant_database = true, and the user is not logged in, then BasicAuth is performed at this point to try and login the user.. however this feels wrong to me.</p>