Laravel change db at runtime - php

i have an application with one level database for login the users and N databases for one or more users.
i want change the db after a user login.
the db params are stored in the table of the user, so after login i have this:
Config::set("database.connections.mysql", [
"host" => "localhost",
"driver" => "mysql",
'default' => 'mysql',
"database" => $dati->db,
"username" => "root",
"password" => ""
]);
DB::reconnect('mysql');
where $dati->db is the database name for the logged user.
it work only in the current controller but all the other model and controller don't have the new db and returns error for missing tables.
how can i propagate the new settings to all model?
thanks in advance.

You should put it into a middleware.
For example:
<?php
class UserDatabaseMiddleware {
public function handle($request, \Closure $next){
$databaseName = $request->user()->db;
config(['database.connections.mysql.database' => $databaseName]);
return $next($request);
}
}
Then you should define it in App\Http\Kernel.php
<?php
protected $routeMiddleware = [
'dbselect' => UserDatabaseMiddleware::class
]
And finally you can add it to your route file. For example:
<?php
Route::group([
'middleware' => 'dbselect',
], function(){
Route::get('/', [MyController::class, 'myMethod'])->name('index');
})
Now the config should be changed with every http request if user is logged in.
Important:
As it was mentioned in the comments - having multiple databases per user is a bad approach. You should consider it.
There is another way to do this - you can have user_id for each record in table and then create global scope to select only records of logged in user.

Related

Remotly destroy user session from admin panel

I am building an admin panel.
Where i give the option to blacklist a user.
If the user is logged in at the time of blacklisting, how can i destroy his session.
his session has the following keys- user_id, username, is_login.
I want to set the is_login to false.
I am working with php
you can do that by :
add field at users table blocked
add middleware that checks for blocked value of user
if true fire Auth::logout();
for more info kindly check the following article 3 Ways to Delete User in Laravel: Block, Hide or Hard-Delete?
Step 1: Create banned_at column at users table.
Step 2: Create middleware for this For example: CheckBanned
In CheckBanned.php Middleware
public function handle ( $request, Closure $next )
{
if ( auth ()->check () && auth ()->user ()->banned_at )
{
auth ()->logout ();
$message = __ ( 'auth.banned_error' );
return redirect ()->route ( 'login' )->with ( 'error',$message );
}
return $next( $request );
}
Step 3: Add middleware to app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
....
....
\App\Http\Middleware\CheckBanned::class,
....
],
....
....
],

How to set a value to $_SERVER['var'] on a functional testing?

I have an action to make an 'autologin' based in a id that the system gets from $_SERVER['AUTH_USER']. In my business server that value is always set for authenticated user. Now, I am trying test my autologin (and so many other things that depends the autologin to work) so I need to set some user to that global (just a string).
What I tryed
$_SERVER['AUTH_USER'] = 'someUser';
$I->amOnPage('some-route'); // this page redirects to autologin action where $_SERVER is used to get the user logged.
But when the action autologin is loaded that value is no more inside $_SERVER global and my test crashes.
What I would like to know
Where or how I can set that global value so that my page could behave normally, reading the value and just going on.
I will appreciate any help.
Thank you.
It looks like lack of proper abstraction. You should avoid accessing $_SERVER['AUTH_USER'] directly in your app and do it in at most in one place - in component which will provide abstraction for this. So you should probably extend yii\web\Request and add related method for $_SERVER['AUTH_USER'] abstraction:
class MyRequest extends \yii\web\Request {
private $_myAuthUser;
public function getMyAuthUser() {
if ($this->_myAuthUser === null) {
$this->_myAuthUser = $_SERVER['AUTH_USER'];
}
return $this->_myAuthUser;
}
public function setMyAuthUser($value) {
$this->_myAuthUser = $value;
}
}
Use new class in your config:
return [
'id' => 'app-web',
// ...
'components' => [
'request' => [
'class' => MyRequest::class,
],
// ...
],
];
And use abstraction in your action:
$authUser = explode('\\', Yii::$app->request->getMyAuthUser())[0];
In your tests you can set value using setter in MyRequest:
Yii::$app->request->setMyAuthUser('domain\x12345');
Or configure this at config level:
return [
'id' => 'app-test',
// ...
'components' => [
'request' => [
'class' => MyRequest::class,
'myAuthUser' => 'domain\x12345',
],
// ...
],
];
UPDATE:
According to slinstj comments, Codeception may loose state of request component, including myAuthUser value. In that case it may be a good idea to implement getMyAuthUser() and setMyAuthUser() on different component (for example Yii::$app->user) or create separate component for that:
return [
'id' => 'app-web',
// ...
'components' => [
'authRequest' => [
'class' => MyRequest::class,
],
// ...
],
];
For now, I am using a workaround because there is only one place where that variable value it is checked:
//Inside my action autologin:
$authUser = explode('\\', ($_SERVER['AUTH_USER'] ?? (YII_ENV_TEST ? 'domain\x12345' : 'domain\xInvalid')))[1];
The only relevant point here is YII_ENV_TEST that is true when testing. Using this I can set get an specific value that is enough to that simple test.
However I hope to see any other better idea here!
Thanks.

laravel 5.4 dynamic database connection

I am building the admission portal in laravel,
I have super admin database which has a schools table with 100 rows,
schools table structure
1.id
2.school_name
3.database details
I want to connect to the school database with its database details by its id.
technically
1.I will pass the school id from url
2.it will select that row from school table
3.after selecting the database details of particular school
4.will connect to the school database for further use.
I went through https://laracasts.com/discuss/channels/tips/set-up-dynamic-database-connection-globally
http://fideloper.com/laravel-multiple-database-connections
but no luck
please help to sort it out.
Actually you dont want multiple connections, but rather change existing connection.
public function setSchoolConnection($id) {
$school = School::find($id);
if ( $school ) {
config(['database.mysql' => [
'database' => $school->database,
'username' => $school->username,
'password' => $school->password
]]);
}
}
Now the default connection has been changed. I think.
If you don't want to change existing connection, just create a new connectio
config(['database.school' => [
'driver' => 'mysql',
'database' => $school->database,
'username' => $school->username,
'password' => $school->password
]]);
and use it like this
$users = DB::connection('school')->select(...);
Have you tried setting the connection in the School model? Laravel will take care of the rest, even if you have relations with different connection.
In your School model, I will override the constructor like so:
public function __construct(array $attributes = [])
{
$this->setConnection(env('DB_SCHOOL_CONNECTION'));
parent::__construct($attributes);
}
The one thing to be careful of is if you have a relation with a different connection, using whereHas or has in your query/builder wont work. as laravel will not be able to set the connection in the query generated.

Yii2 Logout Specific User

How can I tell yii2 to logged out specific user who has login to system?
Let say, there is 2 user is logged in on system, userA and userB.
How can I specified logged out userB?
What I know is userB has to trigger this command to logout.
Yii::$app->user->logout();
return $this->goHome();
But maybe we can use some command like Yii::$app->user->logout('userB');?
Or is it has some other way?
Well, the problem is about how to kill all sessions of the user.
Flag user to force relogin
You can add an additional column force_relogin to User identity class and set it to true when you need to logout someone:
$user = User::findByLogin('userB');
$user->force_logout = 1;
$user->save();
Then add the event handler beforeLogin() on user component like so:
'user' => [
'class' => 'yii\web\User',
'on beforeLogin' => function ($event) {
if ($event->identity->force_logout && $event->cookieBased) {
$event->isValid = false;
}
},
'on afterLogin' => function ($event) {
if ($event->identity->force_logout) {
$event->identity->force_logout = false;
$event->identity->save();
}
}
]
Check, whether $cookieBased and $identity->force_logout are so on...
But that's a bad idea, because the user may have more than one session (logged in in different browsers)
Store list user's sessions in DB
Create table user_sessions with user_id and session_id columns and save each session, you open for that user in the DB. That you can find all sessions of the user and drop them one by one. Something like: (code is not tested, just as an idea)
$sessionId = Yii::$app->session->getId();
session_commit();
foreach (UserSessions::findByUserLogin('userB') as $session) {
session_id($session->id);
session_start();
session_destroy();
session_commit();
}
session_id($sessionId); // Restore original session
session_start();
session_commit();
The idea is weak because you always should take care about consistence of sessions on the server and in the DB.
Store sessions in DB
Store sessions is the database, as described in the Yii2 Guide for Session handling
Then you can just find session in the DB and delete it directly. You shouldn't take care about the consistence and session rotation, because DB is the only place, where the sessions are being stored. As a free bonus you get a non-blocking work with sessions.
If you are still looking for a solution to this, just change the auth_key of the user that you want to logout. This auth_key is used by the system to remember if a user is logged in, thus changing this will invalidate any session that uses this key.
Example to logout a user with id=100
$user = User::findOne(100);
$user->generateAuthKey(); //a function in User (Identity) class that generates the auth_key
$user->save();
If you are using a model User for storing auth credentials, you can simply change the value of 'id' (and also a key '100') to some other integer value, to make user fail auth on his next request.
In other words, you must change all '100', for example to '200' in this code:
file: /models/User.php
private static $users = [
'100' => [
'id' => '100',
'username' => 'admin',
'password' => 'password_for_admin',
'authKey' => '43e02a0f0e5f6a7907b2f2c69a765be7',
'accessToken' => '7b2f2c69a765be743e02a0f0e5f6a790',
],
];

Laravel 4: Multi-Page Form on the same route?

Hello folks I am stuck.
I want to register a User in Laravel 4. Now the thing is, that I want to first grab the email and password and save them in the database. And in step 2 of the registration process, I want to grab all the other details like first and last name and so on.
The difficulty is, that everything should be under one route called signup, for example everything under http://example.org/signup
Another difficulty is, that I have to access the same route with the same methods (GET & POST) twice, because I once get and post the form for Email and Password, and then I get and post the First, Last and Company Name into the Database.
I came up with the following solution, to store everything into the session, because through the session I can access the variables. So whenever I access my UserController I check, if there is data in the session and if yes, redirect to form 2.
Here are all my files:
http://help.laravel.io/d4104cae42f9a2efe1466ce53d086826bc9f6d7f
My Get-Method from the UserController:
public function create()
{
if(Session::has('email')) {
return View::make('frontend.signup.step2');
}
else {
return View::make('frontend.signup.step1');
}
}
My Post-Method from the UserController:
public function store()
{
// If User has a email and password in the session from the first create-View
// his data should be stored and then he gets redirected to a new create-View
Session::flush();
Session::put('email', Input::get('email'));
Session::put('password', Input::get('password'));
if (Session::has('email')) {
try
{
// Let's register a user.
$user = Sentry::register(array(
'email' => Input::get('email'),
'password' => Input::get('password'),
));
// Let's get the activation code
$activationCode = $user->getActivationCode();
// Send activation code to the user so he can activate the account
// Save Email in Emaillist
Email::create(array(
'email' => Session::get('email')
));
// Redirect
return Redirect::action('UserController#create');
}
return Redirect::route('signup');
}
else {
return 'No Session here';
}
}
Here are my routes:
Route::get('signup', array('as' => 'signup', 'uses' => 'UserController#create'));
Route::post('signup', array('as' => 'signup', 'uses' => 'UserController#store'));
For some reason I believe that it gets unneccessary complicated and I believe that there must be another more simple and intuitiv way to solve this, instead with if statements and redirects to the same controller-method.
Nonetheless I came up with some other solutions, for example just using the "signup" as prefix, but I don't like it that way.
Route::group(array('prefix' => 'signup'), function()
{
Route::get('/', function(){
return 'Yeab bababy yea';
});
Route::get('step1', array('as' => 'signup.step1', 'uses' => 'UserController#getStep1'));
Route::post('step1', array('as' => 'signup.step1', 'uses' => 'UserController#postStep1'));
Route::get('step2', array('as' => 'signup.step2', 'uses' => 'UserController#postStep2'));
Route::post('step2', array('as' => 'signup.step2', 'uses' => 'UserController#postStep2'));
});
Is there any way of accomplishing the task while only using one route and without using clientside Javascript to store the variables in the database? (I am a unexperienced with ajax)
The Goal should be to catch the email and still stay on the same route, like those smart guys here for example:
https://www.crazyegg.com/signup
I hope there is a way. Thank you for your help Internet.
Kind regards,
George
P.S.
It's 1 am here in Germany, so don't be mad if I don't respond the next couple of hours to comments, because I am going to sleep now. Thank you very much.

Categories