CakePHP perform Auth before Constructing Models? -
in cakephp application have multi-tenancy provided through isolated databases (each tenant has own, tenant-specific database).
there 'global' database contains users , tenancy information. 'tenants' table contains name of database particular tenant occupies. each user contains single tenant_id.
structure:
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 this system works correctly when user logged in via forms / sessions. when login through /users/login tenancy verified, stored in session, , database parameters loaded own 'isolated' models can use dynamic connection.
however, issues arise when user tries login via basic auth, , directly request controller function want access. example /orders/view/1.xml. in case, cakephp attempts construct 'order' model before user has been logged in, , therefore before tenancy information available - means has no idea database connect in order access orders.
from putting debug() statements around place can see order in models / controllers / auth constructed / executed follows (when executing /orders/view/1.xml):
- model __construct: user
- controller __construct: orderscontroller
- model __construct: permission
- model __construct: order
- function: orderscontroller/beforefilter
- authcomponent __startup
- model __construct: models related order
my problem authcomponent::_startup executed after order model has been constructed. need attempt login user (and database information) before 'order' model constructed.
questions:
- what causes user model constructed before else? (i have default cakephp acl enabled)
- where in app can put call auth->login() attempt login if request contains basicauth headers, executed prior trying load tenant-specific models? assume putting inside user __construct bad idea.
== update 01/05/2014 == inserting code samples.
bootstrap.php: checks whether request being made api. subdomain:
// determine whether request coming api.* subdomain, , if set api_request define true. if (preg_match('/^api\./i',$_server['http_host'])) { define('api_request',true); // links generated (in emails etc), contain full base url. if cron job logged in via api generating // e-mails, users receive links api.mydomain, instead of 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 ' . $full_base_url . ' ' . $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 requests api subdomain (ie: api.mydomain) api_ prefixed actions. // also, enable basic authentication if user accessing via api.* // if login fails, return 401 error instead of 302 redirect login page. if(api_request == true) { $this->params['action'] = 'api_'.$this->params['action']; // prefix actions api_ $this->auth->authenticate = array('basicalias'); // switch 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 unauthorized header (401) exit; // must called prevent 302 being sent! } } } it important note basicalias auth component not included in $components within appcontroller, used dynamically if request api.* subdomain. however, order in classes constructed has no effect whether basicalias authcomponent included in $components, or used dynamically shown above.
appmodel:
function __construct($id = false, $table = null, $ds = null) { if(($ds == null) && ($this->use_tenant_database == true)) { // create connection tenants database , configure model use connection. $tenant = classregistry::init('tenant'); $db_name = $tenant->checkandcreatetenantdatabaseconnectionforcurrentuser(); if($db_name == false) { header("http/1.0 500 server error"); // force returning server error header (500) debug('appmodel::$db_name = false, unable proceed'); cakelog::write('tenant_error', 'db_name = false, unable connect.'); exit; // must called prevent 302 being sent! } // point model tenant database connection: $this->usedbconfig = $db_name; } parent::__construct($id, $table, $ds); } and within models use specific tenant database:
class order extends appmodel { var $use_tenant_database = true; ... } tenant.php:
/** * check whether connection current users tenant database has been created , if so, return name. * otherwise, create connection , return name. * * @return boolean|ambigous <mixed, multitype:, null, array, boolean> */ public function checkandcreatetenantdatabaseconnectionforcurrentuser() { // check whether have tenants database connection information available in configure variable: if(configure::check('tenant.db_name') == true) { // db_config available in configure, use it! $db_name = configure::read('tenant.db_name'); } else { // tenants db_name has not been set in configure variable, need create database connection , // set configure variable. $tenant_id = $this->getcurrentusertenantid(); if($tenant_id == null) { // unable resolve tenant_id, instead, connect default database. debug('tried construct model without knowing tenant database!!'); exit; } $db_name = $this->tenantdatabase->createconnection($tenant_id); if($db_name == false) { // database connection not created. cakelog::write('tenant_error', 'unable find database name tenant_id: ' . $tenant_id); return false; } configure::write('tenant.db_name', $db_name); } return $db_name; } so, if user requests url example: http://api.mydomain.com/orders/getallpendingorders have supplied basic auth credentials along request, happens classes constructed / executed in following order:
- model __construct: user
- controller __construct: orderscontroller
- model __construct: permission
- model __construct: order
- model __construct: tenant
- model __construct: tenantdatabase
- function: orderscontroller/beforefilter
- authcomponent __startup --> performs login.
- model __construct: other models.
the problem is: order.php being constructed user has been logged in, means when code in appmodel.php executed:
$db_name = $tenant->checkandcreatetenantdatabaseconnectionforcurrentuser(); it unable determine users current tenancy.
i need find out workaround this, either somehow performing login before order.php constructed, or hacking if attempt construct model has $use_tenant_database = true, , user not logged in, basicauth performed @ point try , login user.. feels wrong me.
you might want have @ authorization (who’s allowed access what) portion in cake's documentation. @ isauthorized function , how works.
you might need in orders controller:
// app/controller/orderscontroller.php public function isauthorized($user) { // registered users can add posts if ($this->action === 'add') { return true; } // owner of order can edit , delete 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); }
Comments
Post a Comment