I have multiple acceptance tests, which need to run in a specific order. Because they rely on each other. Now how do i call the tryToCreateACollection in the createstore class so that I can run it from there, so that i runs in the order I want.
class CreateStoreCest
{
public function _before(AcceptanceTester $I)
{
$I->amOnPage('http://127.0.0.1:8000/login');
$I->submitForm('[name="login"]', [
'_username' => 'test',
'_password' => 'test12']);
$I->dontSee('Invalid credentials.');
}
public function tryToCreateAStore(AcceptanceTester $I)
{
$I->wantTo('I create a store');
$I->click('//*[#id="dropdown-webshop"]/li[3]/a');
$I->see('Account information');
$I->submitForm('[name="user"]', [
'user[first_name]' => 'TestUserFirstName',
'user[last_name]' => 'TestUserLastName'
]);
$I->see('Create store');
$I->submitForm('[name ="webstore"]', [
'webstore[name]' => 'TestWebstoreName',
'webstore[description]' => 'TestWebstoreDescription',
'webstore[phone]' => '06-12345678',
'webstore[address][country]' => 'TestCountry',
'webstore[address][region]' => 'TestRegion',
'webstore[address][city]' => 'TestCity',
'webstore[address][street]' => 'TestStreet',
'webstore[address][number]' => 'TestNumber',
'webstore[address][postal]' => 'TestPostal'
]);
$I->click('//*[#id="dropdown-webshop"]/li[1]/a');
$I->see('products');
}
}
and
public function tryToCreateACollection(AcceptanceTester $I)
{
$I->wantTo('I want to create a collection');
$I->click('//*[#id="mobile-demo"]/ul/a[8]');
$I->see('collections');
$I->click('//*[#id="content"]/div[2]/a');
$I->submitForm('[name="category"]', [
'category[name]' => 'TestCategory'
]);
$I->click('//*[#id="mobile-demo"]/ul/li[3]/a[1]');
$I->see('TestCategory');
$I->amGoingTo('Edit the category');
$I->click('//*[#id="content"]/div[1]/table/tbody/tr[2]/td[3]/a');
$I->fillField('category[name]', 'This is edited');
$I->attachFile('//*[#id="category_icon_picture_file"]', 'test.jpg');
$I->click('//*[#id="category_save"]');
$I->click('//*[#id="broukecrumbs"]/a[2]');
$I->see('This is edited');
$I->click('//*[#id="content"]/div[1]/table/tbody/tr[2]/td[4]/a');
}
Relying on specific order of tests execution is a test smell.
The right way to reuse test code in Codeception is Step objects: http://codeception.com/docs/06-ReusingTestCode#StepObjects
Related
I'm trying to shorten my controller code, and I want to know the conventions to use with Laravel while validating and storing.
Controller
public function store(Request $request)
{
// Validation
$user_id = Auth::user()->id;
$request->validate([
'lname' => 'required|max:255',
'fname' => 'required|max:255',
'ar_lname' => 'required|max:255',
'ar_fname' => 'required|max:255',
'tel' => 'required|digits:10|unique:infos',
'level' =>'required|max:50',
'goal' =>'required',
'img' => 'required|image|mimes:jpeg,bmp,png',
'cin' => 'required|image|mimes:jpeg,bmp,png',
]);
// Store
info::create([
'user_id' => $user_id,
'lname' => $request->lname,
'fname' => $request->fname,
'ar_fname' => $request->ar_fname,
'ar_lname' => $request->ar_lname,
'bday' => $request->bday,
'tel' => $request->tel,
'level' => $request->level,
'goal' => $request->goal,
'img' => $request->file('img')->store('images', 'public'),
'cin' => $request->file('cin')->store('cins/' . $request->lname . ' '. $request->fname ),
'registered' => true,
]);
// Redirect
return redirect()->route('user.index');
}
First of all you can isolate the validation in a dedicated class following the Laravel way by creating a custom Request with your rules.
php .\artisan make:request StoreInfoRequest
StoreInfoRequest
class StoreInfoRequest extends FormRequest
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'lname' => 'required|max:255',
'fname' => 'required|max:255',
'ar_lname' => 'required|max:255',
'ar_fname' => 'required|max:255',
'bday' => 'date',
'tel' => 'required|digits:10|unique:infos',
'level' => 'required|max:50',
'goal' => 'required',
'img' => 'required|image|mimes:jpeg,bmp,png',
'cin' => 'required|image|mimes:jpeg,bmp,png',
];
}
}
Then this can't be called a Laravel way but it will be very short and clean:
public function store(StoreInfoRequest $request)
{
info::create(
array_merge(
['user_id' => Auth::user()->id],
$request->safe()->except(['img', 'cin']),
['img' => $request->file('img')->store('images', 'public')],
['cin' => $request->file('cin')->store('cins/' . $request->lname . ' ' . $request->fname)],
['registered' => true],
)
);
return redirect()->route('user.index');
}
If you are using Laravel 9 you can do return to_route('user.index');
It is a good practice to create separate classes for each concerns, like for your controller it should only handle receiving and returning the output of your http request.
So you should create classes for the FF:
Class that will handle the validation
Class that you will handle the business logic
As what Medilies answered you have to create a separate file for validating all incoming data. You need to create a Request file that will handle it.
php artisan make:request StoreInfoRequest
StoreInfoRequest
class StoreInfoRequest extends FormRequest
public function authorize(): bool
{
return true;
}
public function rules(): array
{
// declare here everything even it is not required
return [
'lname' => 'required|max:255',
'fname' => 'required|max:255',
'bday' => 'date',
];
}
this will return an array of the validated columns you entered in your StoreInfoRequest $validated.
Then create a service file that will handle your business logic say InfoService. Within this file you can do the eloquent saving. By then you can have clean and thin controller like this.
public function store(StoreInfoRequest $request)
{
$this->InfoService->store($request->$validated);
return redirect()->route('user.index');
}
Don't forget to instantiate the service file in your controller's __constructor method.
public function __constructor(StoreInfoRequest $storeInfoRequest)
{
$this->storeInfoRequest = $storeInfoRequest;
}
I'm in windows using php-7.4.1 Architecture-x64, phalcon - 4.0.2, psr - 0.7.0 and follow instruction from 'https://docs.phalcon.io/4.0/en/application' but the problem is its always render frontend module & its view. I'm unable to find out what i'm doing wrong?
[Index.php]
use Phalcon\Mvc\Router;
use Phalcon\Mvc\Application;
use Phalcon\Di\FactoryDefault;
$di = new FactoryDefault();
$di->set('router',function () {
$router = new Router(false);
$router->setDefaultModule('frontend');
$router->add('/login',
[
'module' => 'backend',
'controller' => 'index',
'action' => 'index',
]
);
$router->add('/admin/products/:action',
[
'module' => 'backend',
'controller' => 'index',
'action' => 1,
]
);
return $router;
}
);
$application = new Application($di);
$application->registerModules(
[
'frontend' => [
'className' => \Multiple\Frontend\Module::class,
'path' => '../apps/frontend/Module.php',
],
'backend' => [
'className' => \Multiple\Backend\Module::class,
'path' => '../apps/backend/Module.php',
],
]
);
try {
$response = $application->handle($_SERVER["REQUEST_URI"]);
$response->send();
} catch (\Exception $e) {
echo $e->getMessage();
}
[Backend module]
<?php
namespace Multiple\Backend;
use Phalcon\Loader;
use Phalcon\Mvc\View;
use Phalcon\Di\DiInterface;
use Phalcon\Mvc\Dispatcher;
use Phalcon\Mvc\ModuleDefinitionInterface;
class Module implements ModuleDefinitionInterface
{
public function registerAutoloaders(DiInterface $di = null)
{
$loader = new Loader();
$loader->registerNamespaces(
[
'Multiple\Backend\Controllers' => '../apps/backend/controllers/',
'Multiple\Backend\Models' => '../apps/backend/models/',
]
);
$loader->register();
}
public function registerServices(DiInterface $di)
{
$di->set('dispatcher',function () {
$dispatcher = new Dispatcher();
$dispatcher->setDefaultNamespace('Multiple\Backend\Controllers');
return $dispatcher;
}
);
$di->set('view',function () {
$view = new View();
$view->setViewsDir('../apps/backend/views/');
return $view;
}
);
}
}
[Index Controller]
<?php
namespace Multiple\Backend\Controllers;
use Phalcon\Mvc\Controller;
class IndexController extends Controller
{
public function indexAction()
{
return '<h1>Back Controller!</h1>';
}
}
Did you set your namespaces in the frontend module as well? Like you did with registerAutoloaders in the backend.
Make sure you have registered your new module in
../app/bootstrap_web.php around line 47 which looks as shown below
$application->registerModules([
'frontend' => ['className' => 'MyApp\Modules\Frontend\Module'],
// <--- add your new module here --->
]);
and that your module class is also registered in the loader at ../app/config/loader.php around line 18 which looks as shown below
$loader->registerClasses([
'MyApp\Modules\Frontend\Module' => APP_PATH . '/modules/frontend/Module.php',
// <--- Add your new module class here --->
]);
Always keep an eye on your namespaces. I hope it helps.
I am having a trouble with login part.
I read this topic : http://www.yiiframework.com/wiki/771/rbac-super-simple-with-admin-and-user/ .
Then i follow its steps, but in step 6. it only configs for just one Controller. I have a Module called Admin with many controllers in it and i don't know how to apply this access control to the whole module. Can anyone help me ?
Sorry for my bad English.
You can create AdminController class, which will extends yii\web\Controller where you define your access rules in behaviors method and make other module controllers extend your AdminController and override behaviors method like this:
public function behaviors()
{
return \yii\helpers\ArrayHelper::merge(parent::behaviors(), [
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['post'],
],
],
]);
}
Here parent::behaviors() are behaviors from AdminController which define default access rules, and you merge them with specific behaviors in your child controller. It gives you flexibility to override some access rules if you need.
I can propose a variation of the method from the article that you mentioned.
Make first 2 steps as it was described and then do the following:
1. Add the field role to User model and evaluate it with thevalue of one of the constants from the article's example (User::ROLE_ADMIN or User::ROLE_USER)
2. Override the yii\web\User->can()
public function can($permissionName, $params = [], $allowCaching = true)
{
/** #var \app\models\User $user */
$user = $this->identity;
$access = false;
do {
if (\Yii::$app->user->isGuest) {
break;
}
if ($user->role === \common\models\User::ROLE_ADMIN) {
$access = true;
break;
}
if (is_array($permissionName)) {
$access = in_array($user->role, $permissionName);
} else {
$access = $permissionName === $user->role;
}
} while (false);
return $access;
}
So now you can check user's role like this:
\Yii::$app->user->can(User::ROLE_USER)
3. You say:
i don't know how to apply this access control to the whole module.
Then open your module class and add the following to the behaviors() method:
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
'rules' => [
[
'allow' => true,
'roles' => [User::ROLE_ADMIN]
]
]
]
];
}
In this example we grant access to ROLE_ADMIN to all actions of all controllers of your module.
That's it.
Make a custom model AccessRules.php as shown below:
<?php
namespace app\models;
class AccessRules extends \yii\filters\AccessRule
{
/**
* #inheritdoc
*/
protected function matchRole($user)
{
if (empty($this->roles)) {
return true;
}
foreach ($this->roles as $role) {
if ($role === '?') {
if ($user->getIsGuest()) {
return true;
}
} elseif ($role === '#') {
if (!$user->getIsGuest()) {
return true;
}
// Check if the user is logged in, and the roles match
} elseif (!$user->getIsGuest() && (int)$role === $user->identity->user_role) {
return true;
}
}
return false;
}
}
?>
Now open your site controller and add the following code in fuction behavior part:
use app\models\AccessRules;
public function behaviors()
{
return [
'access' => [
'class' => AccessControl::className(),
// We will override the default rule config with the new AccessRule class
'ruleConfig' => [
'class' => AccessRules::className(),
],
'only' => ['create', 'update', 'delete','index'],
'rules' => [
[
'actions' => ['create', 'update', 'delete','index'],
'allow' => true,
// Allow admin to create
'roles' => [
'1'
],
]
],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'logout' => ['post'],
],
],
];
}
According to The Yii2 Guide
"ACF is an action filter that can be used in a controller or a module" in the same way.
Just add below code in controller which you want to restrict functionality
'access' => [
'class' => AccessControl::className(),
'rules' =>
[
[
'actions' => ['index','view'],
'allow' => true,
'roles' => ['#']
],
[
'actions' => ['create','update','delete'],
'allow' => true,
'roles' => ['#'],
'matchCallback' => function ($rule, $action)
{
return Admin::isUserAdmin(Yii::$app->user->identity->username);
}
],
],
],
I have admin module and different CWebUser(adminuser) for that module. It works good for login. So I can login in main app and in module by different users. But when I call logout method in module
Yii::app()->getModule('admin')->adminuser->logout();
it log me out from module and from main app as well.
how can I fix it?
thanks beforehand.
I think the key is stateKeyPrefix which can be used to tell different modules to use different session keys.
I will put main config file user section.
'user' => [
'allowAutoLogin' => true,
**'stateKeyPrefix' => 'YOUR-DEFAULT_',**
'loginUrl' => array('/login'),
'class' => 'application.wsi.auth.WSIWebUser',
'authTimeout' => 3600 * 24 // 1 hour
],
I have Admin module and I will put my AdminModule.php for you.
class AdminModule extends \CWebModule
{
public $defaultController = 'index';
public function init()
{
$this->setImport(array(
'admin.components.*',
));
$this->layout = 'main';
\Yii::app()->setComponents(array(
'authManager' => array(
'class' => 'CPhpAuthManager',
'authFile' => \Yii::getPathOfAlias('admin.data.auth') .'php',
'showErrors' => true,
),
'user' => array(
'stateKeyPrefix' => 'admin_',
'loginUrl' => \Yii::app()->createUrl('/admin/index/login'),
'class' => 'AdminWebUser',
'authTimeout' => 3600 * 24 // 1 day
),
), false);
}
}
I have components folder in admin module with AdminWebUser class in it as well.
class AdminWebUser extends \CWebUser {
public function getId() {
return Yii::app ()->user->getState ( 'id' );
}
public function getName() {
return Yii::app ()->user->getState ( 'name' );
}
public function getRole() {
return Yii::app ()->user->getState ( 'role' );
}
public function getEmail() {
return Yii::app ()->user->getState ( 'email' );
}
}
The rest of login and logout controller codes are same.
Hope it helps. If not please let me know.
I would like to implement CakePHP 2 website over existing database with plain-text password field.
This is my AppController
class AppController extends Controller {
public $components = array(
'Session',
'Auth' => array(
'loginRedirect' => array('controller' => 'users', 'action' => 'index'),
'logoutRedirect' => array('controller' => 'users', 'action' => 'home'),
'authError' => 'You cannot view this page',
'authorize' => array('Controller'),
'authenticate' => array(
'Form' => array(
'userModel' => 'User',
'fields' => array('username' => 'user_id', 'password' => 'user_password')
)
)
)
);
public function isAuthorized($user) {
return true;
}
function beforeFilter() {
$this->Auth->allow('home');
//$this->Auth->authenticate = $this->User;
parent::beforeFilter();
}
This is my UserController.
class UsersController extends AppController {
public $paginate = array(
'fields' => array('user_id', 'user_desc', 'user_password'),
'limit' => 25,
'order' => array(
'user_id' => 'asc'
)
);
function login() {
if ($this->request->is('post')) {
if ($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
} else {
$this->Session->setFlash('Cannot Login');
}
}
}
}
This is my User model
class User extends AppModel {
public $name = 'User';
public $primaryKey = 'user_id';
public $belongsTo = 'Group';
}
According to those files above, when I pressed the Login button on login.ctp, I saw
select * from users where user_password = 'this_is_hashing_password'
on the sql dump section.
So, how to turn-off the automatic hashing algorithm, so the login() will compare the user input to the database stored password as plain-text???
I have tried lots of reading on the CakePHP book but I cannot find any, also using hashPasswords($data) technique which found from the internet is not working.
Please help.
Kongthap.
The best answer really is to batch-process your stored passwords so they are hashed, however there are cases where you may be adding a Cake app to an existing application that hashes passwords differently (say by not hashing them at all), so the question is valid even if the goal in this case is not.
Try these resources for modifying Cake's password hashing function, depending on your Cake version:
Cake 2.x
Cake 1.3
I got plain text password working in Cakephp 3, this should only be use for DEVELOPMENT purpose, you should never store password in plain text in production.
That being said, during development, plain text password allows me to focus on login instead of implementing a fully functional user encrypt/decrypt logic. Which is going to be replaced by an OAuth / SAML module anyway...
OK here comes the source code:
ROOT/src/Auth/PlainTextPasswordHasher.php
<?php
namespace App\Auth;
use Cake\Auth\AbstractPasswordHasher;
/**
* Plain text password for demo use, DO NOT PUSTH THIS TO PROD
*/
class PlainTextPasswordHasher extends AbstractPasswordHasher
{
public function hash($password)
{
return $password;
}
public function check($password, $hashedPassword)
{
return $password === $hashedPassword;
}
}
ROOT/src/Controller/PagesController.php
<?php
class PagesController extends AppController
{
public function initialize()
{
parent::initialize();
$this->loadComponent('Auth', [
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'username',
'password' => 'password',
],
'passwordHasher' => [
'className' => 'PlainText',
],
'userModel' => 'YourUsers',
]
],
'loginAction' => [
'controller' => 'Logins',
'action' => 'login'
]
]);
}
}
Source: This video https://www.youtube.com/watch?v=eASSNS1f3V4 and this section of the official doc: https://book.cakephp.org/3.0/en/controllers/components/authentication.html#creating-custom-password-hasher-classes