I've created a rather simple multi-tenant application using separate schemas in a Postgresql database. I keep a public schema, which only a few model use and then the rest of my models use the tenant. I determine the Client from the subdomain through a middleware then, call the following function on my Client model to set the credentials for the connection.
public function logInAsClient()
{
$settings = $this->settings;
// set tenant db
config([
'client' => $settings,
'client.id' => $this->id,
'database.connections.tenant.schema' => $this->schema,
'schema' => $this->schema,
'domain' => $this->domain,
]);
DB::disconnect('tenant');
DB::reconnect('tenant');
return true;
}
It's working great in normal functions, but I have several tasks which require me to queue up hundreds of jobs at a time. Since they are all running on the same app, I pass the client_id to each job and then at the start of the handle run:
Client::find($this->client_id)->logInAsClient();
However, when running this code I get the following error:
PDOException: SQLSTATE[08006] [7] FATAL: sorry, too many clients already
FATAL: sorry, too many clients already
I don't there there is any other part of my app where I am connecting or reconnecting to databases so I'm not sure where else this issue might be coming from. If there are any hints on how to debug an issue like this it would be greatly appreciated.
Related
So I'm trying to write some unit/feature tests for an implementation of Passport on Lumen for our new authentication server. However I'm running into so many annoying issues I'm not even sure I'm going about doing this the right way.
The way passport is currently set up in Lumen, for web requests especially, we fake internal requests to Passport and store the client id and secret in an env file on the backend. (The client secret is hardcoded in .env so it will always be created with the same ID and secret via seeder on each dev machine) Here is the code for that:
class PassportOauth
{
const OAUTH_LOGIN = '/api/v1/oauth/token';
/**
* Functions to encapsulate the token request (login) needs
* */
public static function login($username, $password)
{
return self::clientLogin($username, $password, env('CLIENT_ID'), env('CLIENT_SECRET'));
}
public static function clientLogin($username, $password, $clientID, $clientSecret)
{
return self::post(self::OAUTH_LOGIN, [
'grant_type' => 'password',
'client_id' => $clientID,
'client_secret' => $clientSecret,
'username' => $username,
'password' => $password
]);
}
private static function post($url, $data)
{
return app()->handle(Request::create($url, 'POST', $data));
}
}
Anyways, this class works great in our dev environment. It handles requests and hands out a access token / refresh token like it's supposed to.
However, when I run unit tests testing out this class, I keep getting the error "grant type not supported," despite the password client existing in the unit test db and all the environment variables being present.
Thus, I have switched to instead of testing out the PassportOauth class, to using $this->call() in unit tests for faking calls to Passport:
private function doLogin($email, $password)
{
return json_decode($this->call('POST', self::OAUTH_LOGIN, [
'grant_type' => 'password',
'client_id' => env('CLIENT_ID'),
'client_secret' => env('CLIENT_SECRET'),
'username' => $email,
'password' => $password
])->getContent());
}
this works great on development! Unit tests pass and everything. However, when they run in our ci/cd pipeline and it runs unit tests, I get the error that the oauth public/private keys are not installed. This is pretty obvious, I just need to run passport:install right?
Problem is, the passport db tables aren't created in the pipeline at that moment. And I'm not sure I want to add creating the db migrations each time a ci/cd pipeline is run.
So my questions here are:
1) Am I even approaching this the right way? If I am, how do I get around the oauth private/public keys that I don't even need, because I've statically created the password client from hardcoded values in my env file?
2) Is there a better approach to unit testing this? So far my approach has given me a lot of grief.
yes, the way to test this is by not trying to unit test it. What you want is integration testing.
You would call the authentication server directly, like a normal client, you'd get a token then you start doing the other tests, what happens when the token is fine, what happens when it expires, what happens when you have the wrong token for your resource server etc.
My Laravel is setup with a MultiSite Middleware Provider that checks the subdomain of the address and based on this subdomain changes the connection on-the-fly to another database.
e.g.
Config::set('database.connections.mysql.host', $config['host'] );
Config::set('database.connections.mysql.database', $config['db_name'] );
Config::set('database.connections.mysql.username', $config['user']);
Config::set('database.connections.mysql.password', $config['password']);
Config::set('database.connections.mysql.prefix', $config['prefix']);
Config::set('database.connections.mysql.theme', $config['theme']);
// purge main to prevent issues (and potentially speed up connections??)
DB::disconnect('main');
DB::purge();
DB::reconnect();
return $next($request);
This all works fantastic, except that I now want to use Laravel Queues with the built-in Database driver (sync actually works fine but blocks the user experience for long report generations).
Except Artisan isn't sure which database to connect to so I'm guessing it connects to the default, which is a kind of supervisor database that stores all the subdomains and corresponding db names etc.
Note none of these databases are setup in my database conf as connections, they're stored in a singular management database as there's quite a lot of them.
I've tried cloning the built-in Queue listener and modifying it to swap to the different site connection as so:
/**
* Create a new queue listen command.
*
* #param \Illuminate\Queue\Listener $listener
* #return void
*/
public function __construct(Listener $listener)
{
// multisite swap
$site = MultiSites::where('machine_name', $this->argument('site'));
MultiSites::changeSite($site->id);
parent::__construct();
$this->setOutputHandler($this->listener = $listener);
}
But this fails with
$commandPath argument missing for the Listener class.
Trying a similar database/site swap in the fire() or handle() methods stops the $commandPath error however it simply does nothing, no feedback and doesn't begin to process any jobs from the database.
I'm at a loss how to get this working with a multisite environment, does anyone have any ideas or am I going the wrong way about this?
My ideal scenario would be being able to run a singular Queue command, have supervisor monitor that and it to skip through each database checking. But I am also willing to spawn a queue command per database/site if necessary.
I have an Android app in which I've implemented AWS Cognito. I'm hoping to use this as a means for controlling access to PHP scripts on my web root which connect to an RDS instance with a MySQL db. So far, I've set the registration process in my app to use a developer authenticated id to register the user in a cognito identity pool. Now, what I would like to do is have a method for checking whether the user trying to access the various scripts I've exposed in my web root is indeed a verified user. What I was thinking of doing is implementing a script like this:
use Aws\CognitoIdentity\CognitoIdentityClient;
$identityId = $_POST['identityId'];//sending cached identity id from client
$client = CognitoIdentityClient::factory ( array (
'profile' => 'profile',
'region' => 'region'
) );
$result = $client->lookupDeveloperIdentity(array(
'IdentityPoolId' => 'IdentityPoolId',
'IdentityId' => $identityId,
'MaxResults' => 1,
));
if ($result != null) {
//connect to db and do whatever operation/query needs to be done
}
However, checking this every time I need to make some kind of transaction on my db seems to be pretty inefficient and slow.
a) Am I using Cognito in the intended fashion?
b) If not, what is a better way of going about this?
Please let me know if I'm way off base here. Thanks!
I've been working on converting an application of mine from CodeIgniter to Phalcon. I've noticed that [query heavy] requests that only took a maximum of 3 or 4 seconds using CI are taking up to 30 seconds to complete using Phalcon!
I've spent days trying to find a solution. I've tried using all the different means of access offered by the framework including submitting raw query strings directly to Phalcon's MySql PDO adapter.
I'm adding my database connection to the service container exactly like it is shown in Phalcon's INVO tutorial:
$di->set('db', function() use ($config) {
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => $config->database->host,
"username" => $config->database->username,
"password" => $config->database->password,
"dbname" => $config->database->name
));
});
Using webgrind output I've been able to narrow the bottleneck down to the constructor in Phalcon's PDO adapter class (cost is in milliseconds):
I've already profiled and manually tested the relevant SQL to make sure the bottleneck isn't in the database (or my poorly constructed SQL!)
I've discovered the problem, which to me wasn't immediately apparent, so hopefully others will find this useful as well.
Every time a new query was started, the application was getting a new instance of the database adapter. The request which produced the webgrind output above had a total of 20 queries.
While re-reading Phalcon's documentation section on dependency injection I saw that services can optionally be added to the service container as a "shared" service, which effectively forces the object to act as a singleton, meaning that once one instance of the class is created, the application will simply pass that instance to any request instead of creating a new instance.
There are several methods to force a service to be added as a shared service, details of which can be found here in Phalcon's Documentation:
http://docs.phalconphp.com/en/latest/reference/di.html#shared-services
Changing the code posted above to be added as a shared service looks like this:
$di->setShared('db', function() use ($config) {
return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => $config->database->host,
"username" => $config->database->username,
"password" => $config->database->password,
"dbname" => $config->database->name
));
});
Here's what the webgrind output looks like for the same query referenced above, but after setting the database service to be added as a shared service (cost in milliseconds):
Notice that the invocation count is now 1 instead of 20, and the invocation cost dropped from 20 seconds down to 1 second!
I hope someone else finds this useful!
In most examples services are shared as de facto, not in the most apparent way though, but via:
$di->set('service', …, true);
The last bool argument passed to the set makes it shared and in 99.9% you'd want your DI services to be that way, otherwise similar things would happen as described by #the-notable, but because they are likely to be not as "impactful", they would be hard to trace down.
Task
What I need is, to authenticate user based on Oracle DB user system:
User enters to login page
Enters username and password
Application tries to establish connection with given parameters
If successful, makes this connection persistent over application
I'm using Yii framework.
What I've done ...
I removed db component from protected/config/main.php file. Based on Larry Ulman's blog post replaced authenticate (which located in protected/components/UserIdentity.php) with following one
public function authenticate()
{
$dsn='oci:dbname=XE;charset=UTF8';
$connection=new CDbConnection($dsn,$this->username,$this->password);
$connection->active=true;
return $connection->getConnectionStatus();
}
Well it didn't establish any connection. BTW in server I tested with oracle client. All works well.
Questions are
I'm not sure if I'm doing it in right manner.
How to make established db connection persistent over whole application?
BTW
My development environment
Oracle DB and Apache 2.2.4 + PHP 5.4 webserver working on Windows Server 2003.
It took some time to understand what you want. So basically it's like:
A user tries to log in with username/password
To authenticate the user, you check, whether you can establish a DB connection using that same username/password as DB credentials
If the connection could be opened, the user should have a db component available, that uses this same connection credentials throughout the user session
Your approach to create a custom UserIdentity is right. They are meant for doing the authentication. I'm not sure, why your code there fails, but you're on the right track. Maybe getConnectionStatus() does not work right for Oracle. You could try to execute another simple SQL command for a test.
If you've managed to test for a successful connection, you should store the username and password in the user session, e.g.
Yii::app()->user->setState('__db_username',$this->username);
Yii::app()->user->setState('__db_password',$this->password);
Now to have the db component available after login is a bit tricky. You can take different approaches. I'll describe two:
1. Create a custom CDbConnection
class DynamicDbConnection extends CDbConnection
{
public function init()
{
$this->username = Yii::app()->user->getState('__db_username');
$this->password = Yii::app()->user->getState('__db_password');
if(!$this->username || !$this->password) {
throw new CException('No username/password available! User not logged in?');
}
parent::init();
}
}
Now you can configure this as db component in your main.php without username and password, of course:
'components' => array(
'db' => array(
'class' => 'DynamicDbConnection',
'connectionString' =>'oci:dbname=XE;charset=UTF8',
),
),
2. Adding a CDbConnection from Controller::init()
You could add a db component from your base controllers init() method:
class Controller extends CController
{
public function init()
{
$username = Yii::app()->user->getState('__db_username');
$password = Yii::app()->user->getState('__db_password');
if($username && $password) {
Yii::app()->setComponent('db', array(
'username' => $username,
'password' => $password,
));
}
parent::init();
}
You also still need a base db component configuration in your main.php for this.
Both approaches may need a little tweaking as i've not tested this. But you should get the basic idea.