I am building an app using Yii2 and I am working with multi-tenant architecture. So... every client has his own database (identical structure).
What I have done so far:
I declare all the different databases in the config/web.php file
I have a master database that corresponds each user to his database. So, when someone logs in, the app knows what database should use.
What I have done but I am not sure about:
I created a file components/ActiveRecord.php with the following code:
<?php
namespace app\components;
use Yii;
class ActiveRecord extends \yii\db\ActiveRecord {
public static function getDb() {
if (isset($_SESSION['userdb'])) {
$currentDB = $_SESSION['userdb'];
return Yii::$app->get($currentDB);
}
}
}?>
So... on login I save the database on the session and in the aforementioned file which extends the default ActiveRecord I override the getDb function and I choose my own. Subsequently, I changed all models so they extend my ActiveRecord.
I am not sure that this strategy is correct but it almost works.
Where is the problem:
Everything works fine except from... RBAC! For RBAC I use the yii2-rbac extension. The problem is that the user is not getting his role from his database but from the first declared database in the config/web.php file. So, whatever the logged in user, the first db is used.
Question(s):
How can I solve this problem? Do you have any idea on where is the file that gives the role to the logged in user?
Bonus Questions: Do you think this strategy is wrong? If so, what would you suggest?
Thanks a lot in advance for your time and your support.
Question # 1: How can I solve this problem? Do you have any idea on where is the file that gives the role to the logged in user?
I would suggest you use yii2 admin extension for this purpose(https://github.com/mdmsoft/yii2-admin) that will solve your issue and it is the best extension to manage user role. use this link for better understanding (https://github.com/mdmsoft/yii2-admin/blob/master/docs/guide/configuration.md)
Install above by following above URL or just add "mdmsoft/yii2-admin": "~2.0" in your composer.json file and run composer update.
After successfully installed this extension run migration to create RBAC tables, if you already have then skipped it.
yii migrate --migrationPath=#yii/rbac/migrations
You have to do some configuration in your main.php to tell your application about public routes and for all other application route system will implement RBAC on them.
This is what you have to add in your main.php file.
'components' => [],
'as access' => [
'class' => 'mdm\admin\components\AccessControl',
'allowActions' => [
'site/login', // add or remove allowed actions to this list
'site/logout',
'site/error',
'site/index',
]
]
Above settings will tell your application to make the login, logout, error and index function are public under site controller and all other applications routes need to have RBAC assignment to perform the action.
main.php is exist in backend/config/main.php in my case, you can use according to your requirements may be in common/config/main.php
Question # 2: Bonus Questions: Do you think this strategy is wrong? If so, what would you suggest?
I think your approach is not extendable, I suggest you create a master database and use that for your application tenants with their databases.
Setup tenant table in master database with tenant name, database required parameters
Make master database connection in your application, that is used to get all tenant and there store DB connections from the master database.
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=127.0.0.1;dbname=master-db',
'username' => 'root',
'password' => 'root',
'charset' => 'utf8',
]
Now at login page show tenants list to choose their info OR you can also play with the domain name as well if you are using the unique domain for each tenant. if you are using unique domain then you have to store domain name with tenant info in the master database to able to get tenant info based on the domain.
// Set Session Values on success login page OR read user domain and get tenant info from the master database and add into session.
``
$tenant = Tenant::findOne(Yii::$app->request->post('tenant_id'));
Yii::$app->session->set('DB_HOST', $tenant->DB_host);
Yii::$app->session->set('DB_USER', $tenant->DB_username);
Yii::$app->session->set('DB_PASS', $tenant->DB_password);
Yii::$app->session->set('DB_NAME', $tenant->DB_name);
``
Once you have tenant info you can get the tenant from the master database and add their database settings into SESSION.
Create a BaseModel.php class and extend it with all of your application models classes.
In you BaseModel.php add below method to make runtime DB connection based on domain or tenant selection from the login page.
public static function getDb(){
if ( Yii::$app->session->get('DB_HOST') ){
$host = Yii::$app->session->get('DB_HOST');
$dbName = Yii::$app->session->get('DB_NAME');
$dsn = "mysql:host=$host;dbname=$dbName";
Yii::$app->db->dsn = $dsn;
Yii::$app->db->username = Yii::$app->session->get('DB_USER');
Yii::$app->db->password = Yii::$app->session->get('DB_PASS');
return Yii::$app->db;
}
}
By doing this you will have the option to add multiple tenants from backend to master database and that will automatically available in your application.
I believe that this information is helpful for you.
Cheers!!!
Here is the documentation on dbmanager.
If the you want to get rbac info from a different database, this should work.
Inside your config folder, create a file called rbacdb.php. Add the following:
<?php
return [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=<db host or ip>;dbname=<dbname>',
'username' => '<dbuser>',
'password' => '<dbPassword>',
'charset' => 'utf8',
];
Then go to your config file and find the authmanger section. It should look like this:
'authManager' => [
'class' => 'yii\rbac\DbManager',
'db'=> require(__DIR__ . '/rbacdb.php'),
'assignmentTable'=>'<tableName>',
'itemChildTable'=>'<tableName>',
'itemTable'=>'<tableName>',
'ruleTable'=>'<tableName>'
]
EDIT: after rereading your post...each user doesn't need their own rbac table/schema. use the system that Yii has in place.
Related
i'm developing an application in Laravel but the thing is: my client already have a wordpress website and wants all registrations made in this website to go automatically to my application. I'm using mysql and the wp website too.
Can anyone give me a light here?
Thanks and sorry for my bad english.
I will try to give a basic workflow concept.
Do you want that, user will register in wp site, and that time he will be added/registered in the laravel application as well ? If so, then,
Create a user table, User model and userController with a 'store' method in laravel app. This 'store' method will create the user. The table's column names would be better to match the wp site's wp_users table columns name.
Now create an api route in laravel app that will take user data as post parameters. This user data will come from wp site after registration. The api route could be something like this
Route::post('store', 'userController#store')->name('users.store');
Now in wp site , place this code in 'user_register' hook. This code calls that store api of laravel app and pass the user data of that registered user as user data.
add_action( 'user_register', 'my_registration_save', 10, 1 );
function my_registration_save( $user_id ) {
$user = get_userdata($user_id);
//api call
wp_remote_post( the_api_url, array(
'user_email' => $user->user_email,
'user_login' => $user->user_login
...
...
) )
}
Now the wp site is sending the data through the api, your userController's store method have the access of that data. Use that data to store it in laravel's db, the userController's store method will be something like this
function store( Request $request) {
$user = new App\User();
$user->user_email = $request->user_email,
$user->user_login = $request->user_login
...
...
$user->save()
}
That's it.
I want to implement rbac in frontend and backend separate.
I have a table for admins in backend and admin users work with this table. Also a table for normal users and they work with this table (for login, signup etc).
In the rbac related table (auth_assignment) the 'user_id' field must be valued from another table (user or admin) and this is not possible to get value from both admin and users table.
Is it possible to implement rbac for frontend and backend separate?
if yes how?
It is possible. RBAC DbManager tables can be configured so you can prepare two RBAC components, one for frontend and second for backend, with different tables.
This is the attribute for assignment:
public $assignmentTable = '{{%auth_assignment}}';
Check out this link. Its very useful for RBAC.
https://github.com/yiisoft/yii2/blob/master/docs/guide/security-authorization.md
The RBAC componet are base on common part .. typically if they are base on DB you use common models and shared the related db table ..
You can declare this element in component section of main.php in cofig area and if you do this in common dir this component si correctly shared between both the enviroment (frontend , backend).
eg : common/config/main.php
'components' => [
.....
'authManager' => [
'class' => 'yii\rbac\DbManager',
'cache' => 'cache',
....
],
this mean they could be naturally shared between frontend and backend ..
I need to create about 20-30 Drupal8 sites on different domains. There will be similar content (difference only in details like city name, ajax calls, etc.) but also there will be a specific content like news.
I know all weakness of this idea, but anyway I think that shared tables in one database will be the best solution for this project.
My steps:
installing first default site (sites/default) with prefix for tables default_
creating directory for second site (sites/second), and configuring sites.php (seconddomain.com => sites/second)
installing second site (sites/second) with prefix for tables second_
... then I tried to use solution which is described on many sites:
$databases['default']['default'] = array(
'database-configuration-stuff' => '[...database configuration]'
'prefix' => array(
'default' => 'second_', // default prefix for second site
'users' => 'default_', // shared users...
'sessions' => 'default_',
'role' => 'default_',
'authmap' => 'default_',
),
);
but it doesn't work. I see only users from second site. Cache cleaning doesn't change anything. Any ideas?
Maybe there is possibility to create multi-page solution with one shared database (not only for users but for nodes also) and create content directed to different domains from one admin console?
BTW: If there is any possibility to create sth like this using Drupal7 I can change d8 to d7.
if you'd like to make sth I was looking for you've got three options:
you need to write your own module ;) ,
you need to wait for "Domain Access" module for D8: https://www.drupal.org/project/domain ,
you can also use D7 and module from URL which I provide above.
I chose 3rd option.
So my app is running in development mode in one place and in production mode on its live server. I've just put some changes live and a field that has been in the production DB (MySQL) for a good week or two is not being found by a call to Model::read(). Here's the code, verbatim:
$this->Order->id = $id;
$created = $this->Order->field('created');
$this->Order->contain(array('User', 'OrderStatusChange', 'Cart' => array('CartItem' => array('conditions' => array('deleted_date' => null, 'created <=' => $created)))));
$this->request->data = $order = $this->Order->read();
Same code in dev environment returns all fields. The new(ish) field is missing in production. I have deleted every file in /app/tmp/cache/models and it has not fixed the problem. The production site has Configure::write('debug', 0), development site is set to 2.
Any ideas?
Thanks
As you already cleared your caches, but problem still exists. So, I will point out one things here:
$this->Order->contain(...) here you're using contain() and for contain(), need to attach Containable behavior to model. Your code doesn't clarify that you attach that or not. If not, then I will suggest you to attach that like:
.....
$this->Order->Behaviors->attach('Containable'); // attach Containable behavior
$this->Order->contain(...);
......
when the user buys some car manuals in application, he can able to use them without network
so I want to cache the manuals he purchased, I want step by step method to cache the database
$memcache = new Memcache();
$memcache->connect('localhost', 11211);
$servers = array(
'host' => 'memcache_host',
'port' => 11211,
'persistent' => true
);
$cacheDriver = new \Doctrine\Common\Cache\MemcacheCache(
array(
'servers' => $servers,
'compression' => false
)
);
$cacheDriver->setMemcache($memcache);
$cacheDriver->save('cache_id', 'my_data');
I just want to know how does the my_data and id works ?
Memcached and all the other cache providers included with Doctrine 2 are server side caching mechanisms. Meaning that regardless of the users context, connectivity is required.
If you want to allow your user to access data from your application while offline, you will need to use something like HTML5 local storage, you can check out nettuts for a quick video intro to using it.