How can I set at run time a global variable that is visible for all request? After login user, I need to save the data of DB connection and connect to his database in all request (with a middleware). I just proved $GLOBALS of PHP and app()->singleton() of Laravel, but, in both case, after the request I lose the content of variable.Why?
And how can I resolve? I don't want put the db data to session or cache.
If it is not already stored on the users' object then I'd probably store the database connection info with the user. Then I'd write a middleware to create the connection if a user is logged in. Something like:
public function handle($request, Closure $next)
{
if ($request->user()) {
$connection = new mysqli('localhost', $user->dbUsername, $user->dbPassword, $user->dbName);
$request->merge("connection" => $connection));
return $next($request);
}
return redirect('login'); //the user isn't signed in
}
Then later in your code you would use this connection by:
$this->connection = $request->connection;
Do note that there's no error handling for if the new connection fails and I haven't tested the code. But this is probably enough code to get you started.
Related
I am using a Laravel Application as a backend and wish to set the database connection dynamically (and keep it until the page is refreshed) through an axios request which will contain the database to use and the credentials.
For that purpose, I am storing the received DB configuration in a session and set the env variables with it in the constructor of whichever controller I am trying to use.
Here are the default database settings in .env :
DB_DATABASE=database1
DB_USERNAME=username1
DB_PASSWORD=password1
However, the issue seems to be that the session is not being kept alive, as each sent request contains a different session ID and therefore returns an Access denied error whenever I try to interact with the Database because the session variables are undefined.
Here is how the request is sent from the client :
axios
.post("https://data-test.io/api/Setconnection", {
database: "database2",
username: "username2",
password: "password2",
})
.then((result) => {
// console.log(result);
// do stuff here
});
This is how I store the DB connection in the session :
class RootController extends Controller
{
public function setConnection(Request $request){
session(['database' => $request->database]);
session(['username' => $request->username]);
session(['password' => $request->password]);
return $request->session()->all(); // this returns the correct values
}
}
And I set the route in api.php like so :
Route::post('/Setconnection',[RootController::class, 'setConnection']);
Then, on all subsequent requests, I set the connection in the constructor this way :
public function __construct() {
Artisan::call('config:cache');
Config::set('database', session('database'));
Config::set('username', session('username'));
Config::set('password', session('password'));
}
public function getConfig(){
return [session('database'),session('username'),session('password')];
// this returns an array of undefined elements.
}
Am I making a mistake here or is this not how I am supposed to set database connections dynamically? If not then what is the best way do so ?
As Tim Lewis said, APIs are supposed to be "Stateless" which no previously stored data that may be utilized in every transaction/request. If you don't want to use a database to store the dynamic database credentials, you need to save dynamic database credentials on the client side and send them each request.
But if you really want to create "Stateful" which can use session in the API you can follow these instructions.
Then to change database config you should use Config::set('database.connections.dbdrivername.configname','configvalue') and there is no need Artisan::call('config:cache')
an example:
Config::set('database.connections.mysql.database', session('database'));
Config::set('database.connections.mysql.username', session('username'));
Config::set('database.connections.mysql.password', session('password'));
You should really know what you are doing because it may have a big security hole.
I have needed this in the past for a multi-tenant application, and my solution was this:
DB::purge('mysql');
Config::set('database.connections.mysql.database', $request->database);
Config::set('database.connections.mysql.username', $request->username);
Config::set('database.connections.mysql.password', $request->password);
DB::reconnect('mysql');
Without the DB::purge and the DB::reconnect, the changes would not be applied to the connection.
With that out of the way, I too think this is a super risky practice. You are giving the world your database name and credentials.
I do have a UserController and User Model in my Laravel 5 source.
Also there is one AuthController is also Present (shipped prebuilt with laravel source).
I would like to query data from db in my blades making use of Eloquent Models.
However, Neither in my User Model (Eloquent ) nor in any of the controller, the user() method is defined. even then, I could use it in my blade by accessing it from Auth class. why?
For example,
in my blade, {{ Auth::user()->fname }} works. it retrieve the data fnamefrom my users table and echo it.
What is the logic behind it, and can i emulate the same for other db tables such as tasks?
Whenever you do it automatically or manually some like this
if (Auth::attempt(['email' => $email, 'password' => $password]))
{
}
The selected User's Data will be stored in the storage/framework/sessions
It will have data something like
a:4:{s:6:"_token";s:40:"PEKGoLhoXMl1rUDNNq2besE1iSTtSKylFFIhuoZu";s:9:"_previous";a:1:{s:3:"url";s:43:"http://localhost/Learnings/laravel5/laravel";}s:9:"_sf2_meta";a:3:{s:1:"u";i:1432617607;s:1:"c";i:1432617607;s:1:"l";s:1:"0";}s:5:"flash";a:2:{s:3:"old";a:0:{}s:3:"new";a:0:{}}}
The above sessions file doesn't have any data and it will have the data such as user's id, url, token in json format.
Then whenever you call the {{ Auth::user()->fname }} Laravel recognises that you're trying to fetch the logged in user's fname then laravel will fetch the file and get the user's primary key and refer it from the user's table from your database. and you can do it for all coloumns of the users table that you have.
You can learn more about it here
This user function is defined under
vendor/laravel/framework/src/Illuminate/Auth/Guard.php
with following content :
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if ($this->loggedOut) return;
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method because that would tremendously slow an app.
if ( ! is_null($this->user))
{
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if ( ! is_null($id))
{
$user = $this->provider->retrieveById($id);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->getRecaller();
if (is_null($user) && ! is_null($recaller))
{
$user = $this->getUserByRecaller($recaller);
if ($user)
{
$this->updateSession($user->getAuthIdentifier());
$this->fireLoginEvent($user, true);
}
}
return $this->user = $user;
}
this Guard.php has more functions defined in it which we use every now and then without even knowing where they are coming from
It works because Laravel comes with decent authentication.
Auth is the authentication library and has plenty of features like this, check out the documentation!
Here is my code for now:
$cloud = new Rackspace('https://identity.api.rackspacecloud.com/v2.0/', $php_cloudconfig['credentials']);
$array_creds = getCredentials();
$cloud->ImportCredentials($array_creds);
$array_creds = $cloud->ExportCredentials();
setCredentials($array_creds['authorization_token'], $array_creds['expiration'], $array_creds['tenant_id'], $array_creds['service_catalog']);
function getCredentials() {
$sql_get_credential = "SELECT * FROM cloud_apiconnection";
$q = $conn->prepare($sql_get_credential);
return $q->execute();
}
function setCredentials($authorization_token, $expiration, $tenant_id, $service_catalog) {
$sql_insert = "INSERT INTO cloud_apiconnection (authorization_token, expiration, tenant_id, service_catalog) VALUES (:authorization_token, :expiration, :tenant_id, :service_catalog)";
$q = $conn->prepare($sql_insert);
$q->execute(array(':authorization_token' => $authorization_token, ':expiration' => $expiration, ':tenant_id' => $tenant_id, ':service_catalog' => $service_catalog));
}
Is there a way to detect if the credentials were updated in: $cloud->ImportCredentials($array_creds); ?
I am wandering because I don't want to write to the DB if I don't need to.
Also is this the best strategy for managing my connection to RackSpace API?
It seems like a good strategy for maintaining a persistent session, because you're re-using an existing token ID. The only other suggestion is to cache your credentials in a local file, rather than making an MySQL transaction. You don't really need to store your tenant ID and service catalog because these are easily retrievable through the software layer.
To check the validity an existing token, I'd do this:
$connection = new Rackspace(...);
// Import existing credentials
$credentials = getCredentials();
$connection->importCredentials($credentials);
// Authenticate against the API to make sure your token is still valid
$connection->authenticate();
// Compare result
$newCredentials = $connection->exportCredentials();
if ($newCredentials['authorization_token'] != $credentials['authorization_token']) {
// You know it's been updated, so save new ones
setCredentials();
}
All the authenticate() method does is execute a request against the Rackspace API; and based on the results, it's effectively letting you know whether your existing stuff is still valid. When other methods call authenticate(), they usually do another check beforehand: they check the expiry value (i.e. not in the past). You could implement the same thing they do (to find out whether credentials need to be refreshed and saved):
if (time() > ($credentials['expiration'] - RAXSDK_FUDGE)) {
// They're old, you need to call authenticate()
} else {
// They seem to be still valid. Sweet!
}
By the way, we've recently changed this functionality so that exportCredentials() makes a call to authenticate() - so this would mean you wouldn't need to call it yourself. But for your current version it's worth leaving it in.
Does that answer everything?
I am building my first Laravel 4 Application (PHP).
I find myself needing to call somthing like this often in most of my Models and Controllers...
$this->user = Auth::user();
So my question is, is calling this several times in the application, hitting the Database several times, or is it smart enough to cache it somewhere for the remainder of the request/page build?
Or do I need to do it differently myself? I glanced over the Auth class but didnt have time to inspect every file (16 files for Auth)
Here is the code for the method Auth::user().
// vendor/laravel/framework/src/Illuminate/Auth/Guard.php
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Auth\UserInterface|null
*/
public function user()
{
if ($this->loggedOut) return;
// If we have already retrieved the user for the current request we can just
// return it back immediately. We do not want to pull the user data every
// request into the method becaue that would tremendously slow the app.
if ( ! is_null($this->user))
{
return $this->user;
}
$id = $this->session->get($this->getName());
// First we will try to load the user using the identifier in the session if
// one exists. Otherwise we will check for a "remember me" cookie in this
// request, and if one exists, attempt to retrieve the user using that.
$user = null;
if ( ! is_null($id))
{
$user = $this->provider->retrieveByID($id);
}
// If the user is null, but we decrypt a "recaller" cookie we can attempt to
// pull the user data on that cookie which serves as a remember cookie on
// the application. Once we have a user we can return it to the caller.
$recaller = $this->getRecaller();
if (is_null($user) and ! is_null($recaller))
{
$user = $this->provider->retrieveByID($recaller);
}
return $this->user = $user;
}
To me, it looks like it will get the user from the database only once per request. So, you can call it as many times as you want. It will only hit the DB once.
Auth::user() only hits the DB once, so it's not a problem invokes it many times. Btw, you can cache useful information of the user that you want to access frequently.
I was hoping someone could help me with a question I've come up on.
I have a Session object that handles storage of general session data, I also have a Authentication object which validates a users credentials.
Initially I passed the desired Authentication class name to my Session object then had a login method that created an instance of the Authentication object and validate the credentials. I stored the result of this validation in a Session variable and made it available via a getter. The user data was also stored in the Session for later use. In addition to all this, I have a logout method, which removes the user data from the Session and thus logging the user out.
My question is what role should the Session object play in users logging into their account?
And what other ways might one suggest I go about handling user login, as it stands right now I feel as though I'm getting too much wrapped up in my Session object.
Simply calling your authenticate method should trigger logic within Auth to store the proper data in the session (or some other data store) and Auth should also be used exclusively to retreive/revoke this info. So using the example form your comment it might be:
class Auth {
public static function authenticate($identity, $pass)
{
// do lookup to match identity/pass if its good then
/* assume $auth is an array with the username/email or
whatever data you need to store as part of authentication */
Session::set('auth', $auth);
return true;
// if auth failed then
Session::set('auth', array('user'=>'anonymous'));
return false;
}
public function isAuthenticated()
{
$auth = Session::get('auth');
if(!$auth)
{
return false;
}
return (isset($auth['user']) && $auth['user'] !== 'anonymous');
}
}
[...] as it stands right now I feel as
though I'm getting too much wrapped up
in my Session object.
And id agree. Idelaly for authentication/credentials you shoudl only be interacting with the Auth/Acl object(s). They would then utilize the session as stateful store... but you shouldnt care that its even stored in session. The code utilizng the Auth/Acl object(s) should be completely unaware of this fact.
For example:
//Bad
if($session->get('authenticated', 'auth'))
{
// do stuff
}
// also bad
if(isset($_SESSION['authenticated']))
{
// do stuff
}
//Good
if($auth->isAuthenticated())
{
// do stuff
}
// inside $auth class it might look like this
public function isAuthenticated()
{
$store = $this->getSotrage(); // assume this returns the $_SESSION['auth']
return isset($store['authenticated']);
}
The session is a good place for holding user data that you want to have managed in some sort of state across various pages or if you need a fast accessible way to get at it without hitting the database. It is a bad idea to keep secure information (re: passwords/etc) in the session, but rapid access information like username, name, email address, preferences, etc is all good data to put in the session. Try to keep it simple though.
Keep in mind though, that the session (or related cookie) should only ever be used for identification. It should not be used for authentication.
Authentication object is a good method. Make sure that it only holds secure information as long as it needs to and that it has all the necessary functions available to keep sensitive data protected.