I'm trying to make some kind of a installer for a CMS I'm working on.
The installer page is basically a page where the user inputs the database host, port, username, password and schema name. How could I use this inputed data to test if I actually can connect with the inputs given?
You can get the user inputs into a controller function and update the config/datadata.php. Assuming the driver is mysql by default, you can do this:
public function checkDatabaseConnection(Request $request)
{
//update the config
config(['database.connections.mysql' => [
'host' => $request->host,
'username' => $request->username,
'password' => $request->password
]]);
//Check the credentials by calling PDO
try {
DB::connection()->getPdo();
} catch (\Exception $e) {
return redirect()->back()->withErrors(["connection" => "Could not connect to the database. Please check your input."]);
}
}
Don't forget to add use DB at the top of your controller.
Related
I'm having some trouble trying to achieve multiple connection to database in some clean way.
Keep in mind that this is my first symfony project ever, and i'm only a young developer.
In my project, the goal is to be able to select a client, with a specific database, and to connect to the database to be able to export some datas.
I tried to do the solution describe in this post Symfony 3 connection to multiple databases and i tried to generate dynamically an entityManager.
So i created a factory EntityManagerFactory :
Factory\EntityManagerFactory
<?php
namespace App\Factory;
use Doctrine\ORM\Tools\Setup;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Yaml\Yaml;
class EntityManagerFactory {
private $config_db_group;
public function __construct(string $config_db_group) {
$this->config_db_group = $config_db_group;
}
public function createManager($idDb) {
$isDevMode = false;
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__ . "/src"), $isDevMode);
$connectionConfig = $this->getConfigDb($idDb);
$dbParams = [
'driver' => 'pdo_mysql',
'host' => $connectionConfig['host'],
'username' => $connectionConfig['user'],
'password' => $connectionConfig['password'],
'dbname' => $connectionConfig['db_name']
];
return EntityManager::create($dbParams, $config);
}
private function getConfigDb($idDb) {
$connectionConfig = Yaml::parseFile("$this->config_db_group");
return $connectionConfig[$idDb];
}
}
I have a yaml that describes the connection config :
config\dbgroup.yaml
1:
db_name: "db_name1"
host: "host1"
user: "user1"
password: "password1"
port: "3306"
2:
db_name: "db_name2"
host: "host2"
user: "user2"
password: "password2"
port: "3306"
In my config\services.yaml, i did something that was described in the post.
# Create a service for the factory
App\Factory\EntityManagerFactory:
arguments:
$config_db_group: '%kernel.project_dir%\config\db_group.yaml'
# Use the factory service as the first argument of the 'factory' option
# and the factory method as the second argument
App\Factory\EntityManager:
factory: ['#App\Factory\EntityManagerFactory', 'getManager']
I don't really understand what this is, i think this defines my factory as a service ? ...
And then i try to create an entityManager in my controller, this was just to test if it works, i think database request should be in a Repository, or a Services ?
<?php
namespace App\Controller;
use Twig\Environment;
use App\Factory\EntityManagerFactory;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\Istrator\DatabaseGroupRepository;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Query\ResultSetMapping;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class DashboardController extends AbstractController {
private $twig;
private $databaseGroupRepository;
private $factory;
public function __construct(Environment $twig, DatabaseGroupRepository $databaseGroupRepository, EntityManagerFactory $factory) {
$this->twig = $twig;
$this->databaseGroupRepository = $databaseGroupRepository;
$this->factory = $factory;
}
#[Route('{slug}/dashboard', name: 'app_dashboard')]
public function index(string $slug): Response {
// I get the specific database
$databaseGroup = $this->databaseGroupRepository->findBySlug($slug);
// Then i try to create an entityManager with the correct config
$entityManager = $this->factory->createManager($databaseGroup->getIdDb());
// Then just to try my connection, i create a basic query
$rsm = new ResultSetMapping();
$test = $entityManager->createNativeQuery("USE mydatabase; SELECT * FROM mytable", $rsm)->execute();
return new Response($this->twig->render('dashboard/dashboard.html.twig', [
'controller_name' => 'DashboardController',
]));
}
}
For now it doesn't work.. But i have several questions :
The databases i try to connect are not database with the same database schemes than my actual database. They are external database. Should i create Entities, and repository to manage them ? Or should i just do some connection, some request without entity and repository ?
In the stackoverflow post that i based my code on, there is a second way of doing it, by defining all future connection in a doctrine.yaml. I have a defined number of connection but like 50 or something, should i do this instead of creating entityManager dynamically ?
As you can see, i'm a bit confused right now but if someone could tell me their point of vue, it would be great.
If you need any other information, just tell me !
Thanks in advance
EDIT :
I found the solution, and it was really stupid :
In my EntityManagerFactory, i did this :
$dbParams = [
'driver' => 'pdo_mysql',
'host' => $connectionConfig['host'],
// IT'S NOT USERNAME, IT'S USER ....
'username' => $connectionConfig['user'],
'password' => $connectionConfig['password'],
'dbname' => $connectionConfig['db_name']
];
In the StackOverflow post that is used to create this factory, it was written username, but the correct field was user.
That was my first mistake, my second mistake is that, when i tried to execute my nativeQuery, I created a resultSetMapping empty :
$entityManager = $this->factory->createManager($databaseGroup->getIdDb());
// I did this
$rsm = new ResultSetMapping();
$test = $entityManager->createNativeQuery("USE mydatabase; SELECT * FROM mytable", $rsm)->execute();
// I SHOULD HAVE DONE THIS
$rsm = new ResultSetMappingBuilder($entityManager);
$rsm->addScalarResult('id', 'id');
[... for every field]
$result = $entityManager->createNativeQuery("SELECT id, prenom, nom FROM mytable",$rsm)->execute();
I use addScalarResult because what i get from those databases are not Entities I will keep in my program.
I hope if someone get stuck like me, this could help him/her/etc..
Are OTP supposed to store in session or database. Can anyone please tell the flow of OTP. As Far as i understood, when a user submits the necessary field the user details and the otp gets stored in database, and after register another form opens to enter otp and then the registration finally success. But I dont get the actual logic. To store the otp we need to store all the data in database, all the data gets stored (user info) only then we can verify the otp. I am using session but I am not sure if the code is correct,
public function otpVerify(Request $request)
{
$data = $request->validate([
'verification_code' => ['required', 'numeric'],
'phone_number' => ['required', 'string'],
]);
$otp = $request->session()->get('otp');
$enteredOtp = $request->session()->get('otp');
if ($otp == $enteredOtp) {
$user = tap(User::where('phone_number', $data['phone_number']));
// ->update(['isVerified' => true]);
return success([
$success,
$otp
], __('User created successfully'));
} else {
return problem([], 500, 'OTP Doesnt Match');
}
public function register(RegisterUserRequest $request)
{
$user = new User($request->validated());
$otp = rand(10000, 99999);
$otp_expires_time = Carbon::now()->addSeconds(20);
if (!env('APP_ENV') === 'local') {
$sms = AWS::createClient('sns');
$sms->publish([
'Message' => 'Your OTP code is:' + $otp,
'PhoneNumber' => $user->phone_number,
'MessageAttributes' => [
'AWS.SNS.SMS.SMSType' => [
'DataType' => 'String',
'StringValue' => 'Transactional',
]
],
]);
} else {
Log::channel('otplog')->info('Your OTP code is:'. $otp);
}
$status = $user->save();
$user->roles()->attach($request->role_id);
$user->brands()->attach($request->brand_id);
$user->appliances()->attach($request->appliance_id);
$success['token'] = $user->createToken('MyAuthApp')->plainTextToken;
$success['name'] = $user->name;
Session::put('OTP', $otp, 'expiry_time',$otp_expires_time);
if ($status) {
return success([
$success,
$otp_expires_time,
$otp
], __('User created successfully'));
} else {
return problem([], 500, 'USER_REGISTER_FAIL');
}
}
Store in database is a good option
Here's my take on this matter:
Session Storage
Use session storage if you want the user to only be able to use that session on their requesting device real time. The moment they close their session, it will dispose. In this case, the duration of holding such OTP is dependent on the Session lifecycle. Use this method for real time verification processes on the app, e.g 2 Factor-Authentication.
Database Storage
Database storage is great but can be rather an overkill for some scenarios, especially real time ones. Because you have to add records that will only last for few seconds and deleted again, in the case of 2FA. I would prefer using Database storage in cases where the OTP expiry has some "set" LifeSpan. For example, imagine a process where a user is filling in a registration form, where they have an option to save and continue later. In such scenarios, if part of the registration requires some OTP verification and the user can't verify real time, you normally give the user an OTP that expires after some lifespan e.g 3Hrs. So the user can come back later and use that OTP to continue with their registration. In this case, you delete the OTP from database as soon as the registration is completed.
in session put method you did wrong you need to do like put array in session the way you are sending the expiration time you will not get the expiration time when you call you need to do like this
Session::put([
'OTP' => $otp,
'expiry_time' => $otp_expires_time,
])
I'm Developing SaaS application using Yii2 with separate DB architecture. I have a problem in login to system by using tenant database.
I need to get tenant database details from common db and establish tenant db connection after entering company id, username and password in login form.
This is my index.php file.
<?php
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
require(__DIR__ . '/_protected/vendor/autoload.php');
require(__DIR__ . '/_protected/vendor/yiisoft/yii2/Yii.php');
$config = require(__DIR__ . '/_protected/config/web.php');
(new yii\web\Application($config));
if (Yii::$app->session->get('company')) :
$appConnection = \app\models\Userdbconnections::find()->where(['company_id' => Yii::$app->session->get('company')])->one();
\Yii::$app->dbDynamic->dsn = "mysql:host=localhost;dbname=$appConnection->dns";
\Yii::$app->dbDynamic->username = $appConnection->user;
\Yii::$app->dbDynamic->password = $appConnection->password;
\Yii::$app->dbDynamic->charset = 'utf8';
endif;
Yii::$app->run(); // this will run the application
?>
From login function after post logging data, auth controller is like this
if ( Yii::$app->request->post() ){
$connection = \app\models\Userdbconnections::find()->where(['company_id'=>Yii::$app->request->post('LoginForm')['company']])->one();
$_SESSION["dsn"] = $connection->dns;
$_SESSION["user"] = $connection->user;
$_SESSION["pass"] = $connection->password;
$_SESSION["company_id"] = $connection->company_id;
// Yii::$app->db()->close();
Yii::$app->set('db', [
'class' => '\yii\db\Connection',
'dsn' => "mysql:host=localhost;dbname={$connection->dns}",
'username' => $connection->user,
'password' => $connection->password,
]);
$model_db = new LoginForm();
$model_db->load(Yii::$app->request->post());
$model_db->login();
$_SESSION["login_user"] = $model_db->username;
}
User Management Module called in web.php under component part as following
'user' => [
'class' => 'webvimark\modules\UserManagement\components\UserConfig',
// Comment this if you don't want to record user logins
'on afterLogin' => function($event) {
\webvimark\modules\UserManagement\models\UserVisitLog::newVisitor($event->identity->id);
},
'enableSession' =>true,
],
Each model file consist with following code
public static function getDb()
{
return Yii::$app->get('dbDynamic');
}
So now i'm able to log from tenant db. But after checking i noticed User Management part, creation, role creation all these linked to common db when ever i logged in to tenant db. Is there anything I misses in here?
One way to do it is having two connections. One connection for common details coming from common database (user details, tenant db he belongs to, et al). This connection is static, so must be defined in the config (or just rename what comes with Yii Basic app to something like commonDb or use it with just db name.
Another one will be connected to the specific user tenant database. This will be dynamic and details must change. There are many ways to do it. One is to defined it before app runs. See this forum post for details. Another would be setting it up before request using Yii Container and call it inside your models et al. There might be other ways too.
So the process goes like this
User logs in. Connection used is the common connection (let it be defined as Yii::$app->db).
Using details from (1) create the dynamic connection.
Use the connections where needed (in models, Active data providers or Query builders)
Here is untested example
//common database with user login
----------------------------------
| id | name | tenant_database |
----------------------------------
| 1 | Stef | company_a |
----------------------------------
Note here that Yii::$app->user->identity will hold model class that wraps this table
//config/web.php
return [
'components' =>[
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=common_db',
'username' => 'username',
'password' => 'password',
'charset' => 'utf8',
]
'userDb' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=${database}',
'username' => 'username',
'password' => 'password',
'charset' => 'utf8',
]
]
//set it up before request
'on beforeRequest' => function ($event) {
if(Yii::$app->user->isGuest)
{
// redirect user to Login page
}
else
{
$currentDSN = Yii::$app->userDb->dsn;
$tenantDB = Yii::$app->user->identity->tenant_database;
Yii::$app->userDb->dsn = str_replace('${database}', $tenantDB, $currentDSN);
}
},
]
Then in model class override getDb as follows
class Data extends \yii\db\ActiveRecord
{
public static function getDb()
{
return Yii::$app->userDb;
}
}
Then user it as in:
$data = Data::find()->all();
$data = Yii::$app->userDb->createCommand('SELECT * FROM data')->queryAll();
UPDATE
Since OP wants the data to be in tenant db, the only way is having each tenant to have special Tenant Code, and on login page you will provide inputs for Tenant Code, Username and Password. Then
1. Query the common table for the database name associated with that code
2. Change Connection details as shown above
3. Login with TenantLogin class that uses tenant connection as shown above with Data class.
The new common table
----------------------------
| code | tenant_database |
----------------------------
| 12333 | company_a |
----------------------------
I'm working on some admin functions to my site where a user with admin privileges is able to open a form for a given user ID. From this form they can change user permissions, alter the user password or other means.
However, when the form is constructed from the model, the password field pulls the hashed password from the database and populates the password field. As a result, when the admin goes to save the form the hashed password is treated as a plaintext and therefore hashed again, overwriting the original password.
What I need is some way to allow admin users to change the form but only have passwords hashed and updated on the database in the event that it is changed.
My thoughts are to construct the form setting password to blank:
view/User/edit.ctp:
echo $this->Form->input('User.password',array(
'value' => '',
'type' => 'password',
'autocomplete' => 'off'
)
);
And have some sort of check on the save to skip the password; but this is where I'm stuck.
controller/userscontroller.php
public function edit($id = null)
{
$this->User->id = $id;
if ($this->request->is('get'))
{
$this->request->data = $this->User->read();
} else {
//Something here????
if ($this->User->save($this->data))
{
$this->Session->setFlash('Your user has been updated.');
$this->redirect(array('action' => 'index'));
} else
{
$this->Session->setFlash('Unable to update your user.');
}
}
$this->set('groups',$this->User->Group->find('list'));//, array( 'fields' => array('id', 'name'))));
$this->set('sites',$this->User->Site->find('list'));//, array( 'fields' => array('id', 'name'))));
}
How do I check this and prevent the password from updating when there is no change?
Decided Solution:
As per the answers provided I used a second form on the same page that re-uses the signup validation that users go through. When updating site/group privileges the users are sent through one form while passwords through another.
I would build two admin forms, one for changing permissions and the other for updating the password only. While you are at it, the change password form should have a second field for validating the change.
There are some CakePHP plugins to help with managing users and specifically passwords.
I always create a new form specially for changing passwords. You should replace the password field with a link to change the password.
Alternatively, you could disable the input field and require a button to click and use javascript to remove the disabled attribute on the input element
echo $this->Form->input('User.password',array(
'value' => '',
'type' => 'password',
'autocomplete' => 'off',
'disabled' => true
)
);
Jquery because it's easy
$(function(){
$('UsersPassword').click(function(){
$(this).attr('disabled', '0');
});
});
I am trying to implement facebook Connect to my cakephp Application. i am using Nick's Facebook Plugin.
I wanna implement it this way
When a user Visits the Site he should be able to login via Registration on the site or Facebook Connect
Existing users should be able to connect their account to their FB account
People who first time login to the site using FB Connect and dont have an account on the site. should be redirected to a page where they have to enter details to complete the profile.
What i have done -
I have followed the instruction of Nick to implement it and when i click Login - it connects to my app. but i dont understand how to create a username and password associated with the Fb Connect Id. and user it against the FB token.
Apparently I'm doing the same thing a little before you... ;-)
Here's a method for Facebook login I'm using (slightly redacted and annotated):
public function facebook($authorize = null) {
App::import('Lib', 'Facebook.FB');
$Fb = new FB();
$session = $Fb->getSession();
// not logged into Facebook and not a callback either,
// sending user over to Facebook to log in
if (!$session && !$authorize) {
$params = array(
'req_perms' => /* the permissions you require */,
'next' => Router::url(array('action' => 'facebook', 'authorize'), true),
'cancel_url' => Router::url(array('action' => 'login'), true)
);
$this->redirect($Fb->getLoginUrl($params));
}
// user is coming back from Facebook login,
// assume we have a valid Facebook session
$userInfo = $Fb->api('/me');
if (!$userInfo) {
// nope, login failed or something went wrong, aborting
$this->Session->setFlash('Facebook login failed');
$this->redirect(array('action' => 'login'));
}
$user = array(
'User' => array(
'firstname' => $userInfo['first_name'],
'lastname' => $userInfo['last_name'],
'username' => trim(parse_url($userInfo['link'], PHP_URL_PATH), '/'),
'email' => $userInfo['email'],
'email_validated' => $userInfo['verified']
),
'Oauth' => array(
'provider' => 'facebook',
'provider_uid' => $userInfo['id']
)
);
$this->oauthLogin($user);
}
This gives me an array with all the user details I could grab from Facebook and invokes ::oauthLogin, which either logs the user in with the given information or asks the user to fill in missing details and/or creates a new user record in the database. The most important part you get from the Facebook API is the $userInfo['id'] and/or email address, either of which you can use to identify the user in your database. If you're using the AuthComponent, you can "manually" log in the user using $this->Auth->login($user_id), where $user_id is the id of the user in your own database.
private function oauthLogin($data) {
$this->User->create();
// do we already know about these credentials?
$oauth = $this->User->Oauth->find('first', array('conditions' => $data['Oauth']));
if ($oauth) {
// yes we do, let's try to log this user in
if (empty($oauth['User']['id']) || !$this->Auth->login($oauth['User']['id'])) {
$this->Session->setFlash('Login failed');
}
$this->redirect('/');
}
// no we don't, let's see if we know this email address already
if (!empty($data['User']['email'])) {
$user = $this->User->find('first', array('conditions' => array('email' => $data['User']['email'])));
if ($user) {
// yes we do! let's store all data in the session
// and ask the user to associate his accounts
$data['User'] = array_merge($data['User'], $user['User']);
$data['Oauth']['user_id'] = $user['User']['id'];
$this->Session->write('Oauth.associate_accounts', $data);
$this->redirect(array('action' => 'oauth_associate_accounts'));
}
}
// no, this is a new user, let's ask him to register
$this->Session->write('Oauth.register', $data);
$this->redirect(array('action' => 'oauth_register'));
}
Look no further. Here is an excellent article that'll guide you all the way through (minus any readymade plugins):
Integrating Facebook Connect with CakePHP's Auth component
Simply follow the approach described in there.
Cheers,
m^e