Dynamically setting database name

Login or register to post comments
18 replies [Last post]
LaCeja's picture
Offline
Joined: 11/04/2009

I am trying to dynamically set the name of the database to be used defining a database connection. Here's the requirement:

I have one database that holds the user table, where each registered user is defined.
I have a login form, where the user enters their userid and password. I have a table, in which is defined the database name where the data is stored for that user. There are an undefined number of possible users and therefor an undefined number of databases. All databases for all users are identical in structure. So, I need a way to use the database name I find in the first database to create a connection adapter for the user, when they login.

The problem is, configure is called, when the login form is loaded, so all database connections are set, before I know the name of the database for the user. Is there any way to set up an adapter constant outside of configure.inc.php? I've tried creating the constant DB_CONNECTION_2 in a separate script and then calling InitializeDatabaseConnections, but that doesn't work.

LaCeja's picture
Offline
Joined: 11/04/2009

I've done a bit more testing and investigation on this.

After researching the structure of prepend.inc.php, I realized the problem is that configure.inc.php is required before the session_start is issued. Therefor, the session variable created to hold the database name is never available to configure.inc.php. The solution may be to move the session_start to the top of prepend.inc.php, ahead of the require configure.inc.php. Although I'm not aware of any other problem it may cause, I tested that and it seems to work at least for the database connections.

One of the reasons I wanted to use a session variable is, because it is possible for multiple users to attempt to "login" from the same machine, using separate tabs of the same browser or another instance of the browser. After testing, I discovered this is a problem, because even though I'm using separate instances of the browser on the same machine, there is only one session created by Apache. Therefor, the second user login replaces the value in the session variable from the first login and, with a browser refresh, the first database connection for the first user switches to the same connection as the second user.

I don't have a solution to this problem, other than to have "login.php" check to see if the session variable already exists and disallow a second login. Does anyone have any ideas? I can foresee situations, at least in my application, where it might be advantageous to allow multiple logins from the same workstation.

Steven Warren's picture
Offline
Joined: 11/06/2008

Look at QApplicationBase.class.php, there is a function InitializeDatabaseConnections. I haven't done it before, but it looks like the Database connections are stored in the QApplication::$Database array. You should be able to pop your database connection to the end of the array and then when you need to make calls to the new database you could do something like.

<?php
$objDynamicDatabase
= QApplication::$Database[$intDynamicDatabaseIndex];
$objDynamicDatabase->NonQuery('Your SQL Code Here');
?>

I am only shooting from the hip, but I think this would work for your case.

LaCeja's picture
Offline
Joined: 11/04/2009

Thanks Steve, I was looking at that last night (although the memory is kinda foggy). However, my thought was that I would then not be able to use the ORM. Wouldn't I then have to use my own SQL?

At this point, the real problem is the sessions. If logging in from another instance of the browser doesn't create a new session, then even that wouldn't work. Actually, just moving the session_start statement to the top of prepend.inc.php, just after:

if (!defined('__PREPEND_INCLUDED__')) {
// Ensure prepend.inc is only executed once
define('__PREPEND_INCLUDED__', 1);

creates the connection to the desired database, because the session variable I create in login can then be used for the database name and it gets added to the QApplication::$Database array, without making any other modifications to the core. I've already got lots of mods in prepend.inc.php, adding to the QApplicationBase::$Classfile array. So, when I upgrade to future versions of QCubed, I'll have to rework the standard prepend.inc.php anyway.

I think I'll just need to have login.php disallow multiple concurrent logins from the same workstation.

Thanks for the idea.

Steven Warren's picture
Offline
Joined: 11/06/2008

You could still use the ORM, you would just have to add a intDatabaseIndex variable to your generated models and then override the GetDatabase in your base model to take the new index. Something like:

<?php
public static function GetDatabase() {
    return
QApplication::$Database[$intDatabaseIndex];
}
?>

LaCeja's picture
Offline
Joined: 11/04/2009

Great idea Steve!

However, I just discovered another problem. If a user logs in and, while the first user is still logged in, another user logs in with a new tab or even a new instance of the browser, they both get the same session and therefor the same instance of the session variables. I suppose this is because QCubed is managing sessions based on cookies. If you turn off cookies in the browser, QCubed applications stop working.

However, if I open an instance of Firefox and login, for example. And, with the Firefox session still open, I open an instance of Chrome and login, they get separate sessions. Wouldn't both browsers (Firefox & Chrome) be using the same cookies, if they're both open and active on the same QCubed application at the same time?

Is there a solution to this?

Steven Warren's picture
Offline
Joined: 11/06/2008

If I understand your initial post, you need users to be authenticated against a central database, once authenticated you need to load an individual database for that user(each user having his own database separate from all others).

The flow would be:
Login -> authenticate against central store->Load user's database->run app logic

If this is the case you do not need to load the user's database until after he has been authenticated. Therefore, you can add his database connection to the QApplication::$Database array in your authentication logic. What I would do is add a static function to QApplication like this:

WARNING: not tested code. This is just a pseudo mock-up.

<?php


public static function DynamicDatabaseConnection(strAdapter = 'QMySqliDatabase', strServer, strPort = null, strDatabase, strUsername, strPassword, blnProfiling = false, strDateformat) {
   
$objConfigArray = array(
       
'adapter' => $strAdapter,
       
'server' => $strServer,
       
'port' => $strPort,
       
'database' => $strDatabase,
       
'username' => strUsername,
       
'password' => strPassword,
       
'profiling' => blnProfiling);
    if (!
class_exists($strDatabaseType)) {
       
$strDatabaseAdapter = sprintf('%s/database/%s.class.php', __QCUBED_CORE__, $strDatabaseType);
        if (!
file_exists($strDatabaseAdapter))
            throw new
Exception('Database Type is not valid: ' . $objConfigArray['adapter']);
        require(
$strDatabaseAdapter);
    }
   
QApplication::$Database['Dynamic'] = new $strDatabaseType($intIndex, $objConfigArray);
}
?>

and the in my login authentication function make a call to it. Then in your codegen models make the change I stated before to GetDatabase and use QApplication::$Database['Dynamic'];

Steven Warren's picture
Offline
Joined: 11/06/2008

Each browser stores cookies in their own way. They do not share. That would create all types of security sandbox issues. However this does work out for testing purposes. If you are working on application and want to see how two users ineteract (i.e. admin panel updates after user enters data) you can have IE logged in as admin and FireFox logged in as a user.

LaCeja's picture
Offline
Joined: 11/04/2009

Steve, I tried to do something like this, but the problem is, each Form that's called needs to know the database name in order to create the connection. That's why I put the database name into a session variable. Now, I discovered a problem in the following scenario:

1. User opens browser or a new browser tab.
2. User navigates to login screen and performs login.
3. User closes the tab, without logging out and without closing the browser.

At this point, even though the connection and application is terminated, the session cookie remains active, because the browser hasn't been closed, even though all tabs relating to the session have been closed. Now, assuming I have code in the login script to disallow multiple users to login, the user will not be allowed to login again, without actually closing the browser and restarting.

The only way I can figure to get around this is to convert every form into a QPanel and manage everything from within the login script. Am I missing something?

Steven Warren's picture
Offline
Joined: 11/06/2008

In your disallow multiple login script you could give the user the option to logout the previously opened session.

For example:

  • User logs in
  • multiple login script finds an open session for the user
  • Dialog warns user of other session and prompts if they would like to terminate that session and start a new one.
LaCeja's picture
Offline
Joined: 11/04/2009

Steve, I really appreciate your excellent guidance on this. I'm thinking your last suggestion is probably the best. I have a couple of other questions I would like your advice on:

1. Let's assume there really is another user still logged in (possibly the same user). This means I would need to include a variable in the menu manager, so when the user takes a new menu option, it would check to see if the session variable had changed (new user logged in) and terminate their login at that point... present a DisplayAlert first of course.

2. Some programs do not terminate, when the user finishes. For example, in Order Entry, when a user saves a new order, the redirect is to itself so they can create the next order. This would go cause prepend.inc.php to run again, which would create a new connection, using the second users Id. So, it means the Order Entry program should get a extra parameter passed to it, being the users Id. It would immediately check the parameter against the session variable to insure they are equal and terminate they are not.

3. Actually, now that I think of it, every QForm should go check to see of the form was opened by the same user as the current session variable. Maybe create a separate class to do the check and just call it from the top of the Form_Create method.

4. If the first and second user are the same user, is it really a problem?

Am I missing anything you can think of?

Steven Warren's picture
Offline
Joined: 11/06/2008

You are basically identifying a unique user by username+sessionID. I would check for both on page load.
Without digging deeper I don't see anything you are missing. We have built a few multi-tenant applications for VOIP clients and while we use a single database the checks are about the same as what you are doing.

LaCeja's picture
Offline
Joined: 11/04/2009

Steve, thanks again for your help. Sometimes it seems impossible to see the forest for the trees. Your perspective is very much appreciated!

Steven Warren's picture
Offline
Joined: 11/06/2008

No problem, are you building this app in version 2.0.1 or 1.1.2? If it is 2.0.1 are using using QDialogBoxes? and if so are you experiencing the same issue I am here?

LaCeja's picture
Offline
Joined: 11/04/2009

I'm using 2.0, which is obviously an orphan, and yes I am having the same problem. I've been thinking of upgrading, but knowing 2.0.2 is on the horizon, I've decided to wait for it and hope the problem is fixed. It really doesn't make much sense to upgrade to 2.0.1, if 2.0.2 is going to be available soon. It appears there's a huge difference from 2.0 to 2.0.1 or 2.0.2. Vartan has made some significant enhancements as relates to JQuery plugins. The problem is, the longer it takes, the more programs I will need to upgrade, since I use several plugins.

vakopian's picture
Offline
Joined: 04/08/2008

LaCeja,
Without knowing all your requirements, I get the feeling that you're chasing the wrong problems. In a typical web application, just because the user closes the browser tab does not mean they should be logged out. If you force such a behavior, you're basically rendering the concept of tabs useless (people routinely use multiple tabs of the same web application to have multiple views open at the same time). Also in a typical web application, two different users really can't be logging in from different tabs of the same browsers (precisely for the reasons you discovered: cookies). I'm not sure if you're trying to allow such a thing or prevent it?

As for closing the entire browser, you can use non-persistent cookies (probably with some configuration in apache), so the cookies won't survive a browser restart. And the only thing you would have to worry about is some session cleanup in the back-end (but even that you could just rely on session expiration). But even for closing the browser, I strongly suggest to stick with standard behaviors (i.e. persistent cookies, maybe with short expirations). And if you're trying to time the users, and see how much time they're spending on the site, using the actions they perform (i.e. http requests) is more reliable and could even be more meaningful (if the user does nothing, even if they haven't logged off, you probably should not count that as active usage time anyway).

So I really think you're not going after the right problems. Most likely you should be just preventing the same user having multiple sessions in the back-end (or some kind of a maximum). And that's not very difficult. My advise is not to try to force anything on the client side, and allow standard browser behavior, and implement your business requirements on the back-end. That way your users will be less frustrated and you'll get less support calls :-)

Hope it helps.
-Vartan

LaCeja's picture
Offline
Joined: 11/04/2009

Vartan, thank you for the thoughtful response. I do agree with you, if this were a normal web application, I would not be a problem and I wouldn't be trying to close it.

This is a business application, which normally would be used on a corporate intranet. However, I am offering it as a web based service, where (as Steve described it) multiple tenants are served. Each tenant (corporation) has its own database and has multiple users. There is the possibility that one workstation could belong to a user, who is a user on multiple clients. An example is an accountant, who services both tenants (corporations). In this case, it doesn't seem to matter whether I user cookies or session variables (other than security), because the user identification must be available to determine the correct database to connect.

So, the problem is, if two users attempt login to the same QCubed application from the same machine, even if it's from two separate browser instances, QCubed assigns the same session to them both. If I set a cookie or a session variable they are available to both users and I have no way to know which is which.

Even if the users are from the same tenant, it is possible that the two users logged in will have differing levels of security, not to mention that I would lose the ability to log user activity, but user.

Anyway, it appears that Steve's recommendations will work for me. There will of course be a "public" version of the application, offered for demonstration purposes. In that case, it will certainly be considered a normal web application and a different type of security front end will be appropriate.

Again, I want to thank you for your valuable input. I do very much appreciate it!

Offline
Joined: 03/31/2008

Yeah, I agree that things would get confusing for the user if you tried to allow multiple simultaneous logins in the same system. You'll note that no site allows that, instead informing you that you are still logged in, and that you can log out to switch users.

However, if you do want to allow multiple simultaneous logins, there _is_ a solution. Different domain names. Or at least subdomains. So for example you could have client1.app.com be a simple cname and apache alias for app.com, so the code and IP would all be the same, but the two would not share cookies. So client1.app.com could be logged into simultaneously to client2.app.com.

This also makes intuitive sense to the user, because they visualize them as two separate (though identical) applications.

And if you have the ability to set up a wildcard (ie: *.app.com is an alias for app.com), there's no additional setup required, apart from communicating the separate login domains as the "application address" for each client..

LaCeja's picture
Offline
Joined: 11/04/2009

Thanks Vexed, I may have to consider this in the future. However, for now at least, I'm going to keep it simple.

Thanks again.