frozennode administrator relationship restrict by tenant - php

I have three Laravel models within a multitenanted app:
Tenant {}
Company {
public function tenant() {
return $this->belongsTo('Tenant');
}
public function group() {
return $this->belongsTo('Group');
}
}
Group {
public function companies() {
return $this->hasMany('Company');
}
public function tenant() {
return $this->belongsTo('Tenant');
}
}
I am building an admin interface for my app using Frozennode Administrator, so the super admin can change stuff and so on. I want to be able to change the group of a company:
'edit_fields' => [
],
'group' => [
'title' => 'Group',
'type' => 'relationship',
'name_field' => "title",
//'constraints' => ['tenant' => 'tenant']
]
]
But my constraint does not work. How can I limit the groups shown to those matching the tenant that the company belongs to?

This is relatively simple. Just define a new relationship in your Company model like so: (and use this instead of "group" in your administrator config file):
public function tenant_group() {
return $this->belongsTo('Group')->whereTenantId(Session::get('current_tenant_id'));
}
The above code assumes you have a session variable called "current_tenant_id" which determines the current users' tenant. You can replace that with something else, perhaps something like this:
Auth::getUser()['tenant_id']

Related

How to update foreign key ide value in Laravel Eloquent?

I'm quite new to Laravel and I was not able to find the answer to this problem neither on Laravel docs, nor here.
I guess it's just a matter of how to search for it, cause I'm pretty sure it's a common case.
I have two models in relationship (this is a simplified case), I retrieve the info I need through a Resource file, but I'm not able to understand how to properly store or update info.
Here's a code example:
Models\Company.php
class Company extends Model
{
protected $fillable = [
'name', 'blablabla', 'country_id', 'blablabla2',
];
public function country() {
return $this->belongsTo(Country::class);
}
}
Models\Country.php
class Country extends Model
{
protected $fillable = [
'code', 'name', 'prefix', 'tax_code_id',
];
public function companies() {
return $this->hasMany(Company::class);
}
}
Then I have a CompanyController file to manage API requests:
Controllers\CompanyController.php
class CompanyController extends BaseController
{
public function index()
{
$companies = Company::paginate();
$response = CompanyResource::collection($companies)->response()->getData(true);
return $this->sendResponse($response, 'Companies retrieved successfully');
}
public function store(Request $request)
{
$input = $request->all();
$validator = Validator::make($input, $this->validation_rules);
if($validator->fails()){
return $this->sendError('Validation error.', $validator->errors());
}
$company = Company::create($input);
return $this->sendResponse($company->toArray(), 'Company added successfully.');
}
}
...
public function update(Request $request, Company $company)
{
$input = $request->all();
$validator = Validator::make($input, $this->validation_rules);
if($validator->fails()){
return $this->sendError('Validation error.', $validator->errors());
}
$company->update($input);
return $this->sendResponse($company->toArray(), 'Company updated successfully.');
}
And here the CompanyResource I'm using to display info as I need.
Resources/CompanyResource.php
class CompanyResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'blablabla' => $this->blablabla,
'country' => $this->country,
'blablabla2' => $this->blablabla2,
];
}
}
So when retrieving Companies (or single company) I get a nested JSON:
{
"id": "1",
"name": "something",
"blablabla": "blablabla",
"country": {
"id": "100",
"code": "MA",
"name": "Mars",
"prefix": "123",
"tax_code_id": "#$%"
},
"blablabla2": "blablabla2"
}
If I create or update a new company I send a payload that has the same structure of what I'm getting above, but if I edit country id value my company model doesn't get it.
PUT Api/companies/1
{
"name": "something",
"blablabla": "blablabla3",
"country": {
"id": "200",
"code": "JU",
"name": "Jupiter",
"prefix": "456",
"tax_code_id": "#=%"
},
"blablabla2": "blablabla2"
}
I'm expecting to update country_id field in companies table for record 1 so that it matches payload (so going from 100 to 200), but it's not happening.
I could edit frontend logic in order to send only country_id in payload since I'm not going to update countries table and all that additional info is redundant, but I'd like to know how to manage it in controller with Laravel.
Would you mind helping me? Thanks in advance.
If you want it to work with the code now, you need to have country_id in the root JSON object you are sending. As this is the way you would fill the id. This is not the best approach in my opinion, but this is why your update is not working at the moment.
{
"name": "something",
"blablabla": "blablabla3",
"country_id": 200,
...
I actually like the approach of sending complete objects. Commonly to fill id's is not good, as it can interfere with the way relations work. Laravel will set your relationships when you associate, if not you are not guaranteed to have the correct relationship after the fill.
Therefor i would fetch out the id and associate the country object with the company. In a logic similar to this.
// only fill non relation fields, fill used as save is needed after associate()
$company->fill($request->only(['name', 'blabla']));
$company->country()->associate(Country::find($request->get('country')['id']));
//associate does not save
$company->save();
I wrote a gist for this years ago that can relate any two models regardless of their relationship type. You just need to supply it with the name of the relationship method: https://gist.github.com/kmuenkel/055f107139d904e30810bf53750d9c6e

PHP Laravel Auth password from users table, id from another table

I've been looking around the internet for a while now and I'm not able to find an answer to my question which is why I am asking it here.
I want to authenticate users to login but this must be done with information from two separate tables.
I am sending to values to my controller: badge and password
I am accessing them with
$request->badge (note: This is the account id)
$request->password (note: this is the users password)
Previously I have tried the following:
public function store(Request $request)
{
$request->validate([
'badge' => 'required|int',
'password' => 'required|string',
]);
$account = Account::select('id', 'user_id')->where('id', $request->badge)->first();
if(!$account)
return back()->withInput()->withErrors(['password' => 'Incorrect badge or password!']);
else
{
if(Auth::attempt(['username' => $accounts->user->username, 'password' => $request->password]))
{
return redirect()->route('home');
}
else
{
return back()->withInput()->withErrors(['password' => 'Incorrect badge or password!']);
}
}
}
This will log the user in, however when I use Auth::id() it returns the ID of the user and not of the account.
example: $request->badge is filled with 25 (which is the account id), the user id is 1. Auth::id returns 1 instead of my desired 25.
My table looks like the following:
users
----
id
username
email
password
accounts
-----
id
user_id
name
I have a relationship in accounts and users to link them together
public function accounts()
{
return $this->hasMany(Accounts::class, 'user_id', 'id');
}
public function user()
{
return $this->belongsTo(User::class, 'id', 'user_id');
}
I want auth::id to give me 25 instead of 1.
Since you already have the ID if the correct account it's only a hasOne relationship between the account and the related user.
In your auth config, you need to change the model of the users provider to your actual account model:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\Account::class,
],
],
In the account model we're then adding a global scope which always fetches the password from the related user entry so auth won't have a problem with it:
class Account extends Model
{
protected $table = 'accounts';
protected static function boot()
{
parent::boot();
static::addGlobalScope('withPassword', function ($builder) {
$builder
->join('users', 'accounts.user_id', 'users.id')
->select(DB::raw('accounts.*, users.password'));
});
}
}
The scope makes sure that the password column is always present if you query an account. You can change it up so that the account always includes certain other columns as well, but for now, this should work as expected.
In chat, we've been discussing the topic of the advantages of manual authentication with either login or loginById in this scenario. A possible solution would be this:
$account = Account::select(
'accounts.id',
DB::raw('(SELECT `password` FROM users WHERE accounts.user_id = users.id) AS `password`'))
)
->where('accounts.id', $request->badge)
->first();
if ($account) {
if (Hash::check($request->password, $account->password)) {
Auth::loginUsingId($account->id);
return redirect()->route('home');
} else {
return back()->withInput()->withErrors(['password' => 'Incorrect badge or password!']);
}
} else {
return back()->withInput()->withErrors(['password' => 'Incorrect badge or password!']);
}
I might be on the totally wrong track here, but what you could do is to apply a global scope to the Users model which automatically joins the accounts table every time you query for a user.
This join automatically replaces the user_id with the account ID given how joins work but you may have to fiddle around with a raw select to get the values how you want them.
class User extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('account', function (Builder $builder) {
$builder->join('accounts', 'users.id', ''accounts.user_id');
});
}
}
To remove the scope from any query you can just use User::withoutGlobalScope('account').
Read more about usage of global scopes here.

RBAC Yii2 doesn't work with default roles

I'm following the Definitive Guide to Yii 2.0. In my application I have two roles: the admin, who can do everything and the viewer, who can do some actions that unregistered users can't do. I'm trying to use default roles functionality of Yii 2 RBAC, but it seems doesn't work. The user table in my database has a column named "role": for admin it's value set to 1 and for viewers = 2.
What I did:
/app/rbac/UserGroupRule.php
namespace app\rbac;
use Yii;
use yii\rbac\Rule;
class UserGroupRule extends Rule {
public $name = 'userGroup';
public function execute($user, $item, $params) {
if (!Yii::$app->user->isGuest) {
$group = Yii::$app->user->identity->role;
if ($item->name === 'admin') {
return $group == 1;
} elseif ($item->name === 'viewer') {
return $group == 1 || $group == 2;
}
}
return false;
}
}
$auth = Yii::$app->authManager;
$rule = new \app\rbac\UserGroupRule;
$auth->add($rule);
$author = $auth->createRole('viewer');
$author->ruleName = $rule->name;
$auth->add($viewer);
$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
$auth->addChild($admin, $viewer);
in my controller:
public function behaviors() {
return [
'access' => [
'class' => AccessControl::className(),
'only' => ['admin'],
'rules' => [
[
'allow' => true,
'actions' => ['admin'],
'roles' => ['admin'],
],
],
],
];
}
When I try to access "admin" action, it says Forbidden #403, even when I'm an admin. How to make it work?
The user table in my database has a column named "role": for admin it's value set to 1 and for viewers = 2
That's not how it works unfortunately.
The rights/roles a user has are (by default) done via the auth_assignment-table.
Just add an entry in it:
INSERT INTO `auth_assignment` VALUES ("admin", <user-id>, NOW());
(be sure to change the user ID into whatever user you want to make admin.
That should solve your issue.
Edit (as I misread some of your question):
As per this link you can indeed define default roles, but you have to make sure to also reconfigure your authManager-component in the configuration file to include the default roles:
'components' => [
'authManager' => [
// ...
'defaultRoles' => ['admin', 'viewer'],
],
],
This list of roles indicate the permissions that always should be checked for every user, no matter if they have an entry in the auth_assignment-table or not.
I was facing the same issue with op. Finally made it work after tinkering with xdebug for a while.
I feel the official documentation on default roles is missing a couple important points, I will summarize them below with some of my personal experiences. The project structure is based on Yii 2.0 Advanced Project Template
Database
user table contains id and group. Where group is type int, 1 for admin and 2 for author
Rules setup
Code simplified for clarity.
The rule class, where you put the actual rule logic.
yii/console/controller/UserGroupRule.php
namespace app\rbac;
use Yii;
use yii\rbac\Rule;
/**
* Checks if user group matches
*/
class UserGroupRule extends Rule
{
public $name = 'userGroup';
public function execute($user, $item, $params)
{
if (!Yii::$app->user->isGuest) {
$group = Yii::$app->user->identity->group;
if ($item->name === 'admin') {
return $group == 1;
} elseif ($item->name === 'author') {
return $group == 1 || $group == 2;
}
}
return false;
}
}
Now defining the roles..
yii/console/controller/RbacController.php
namespace console\controllers;
use Yii;
use yii\console\Controller;
class RbacController extends Controller
{
public function actionInit()
{
$auth = Yii::$app->authManager;
$rule = new \app\rbac\UserGroupRule;
$auth->add($rule);
$admin = $auth->createRole('admin');
$admin->ruleName = $rule->name;
$auth->add($admin);
// define 'author' here...
}
}
After you have this file ready, you should be able to run ./yii rbac/init to generate the rule files:
console/rbac/items.php
console/rbac/rules.php
Important: You need to place the generated files under your desired application folder, this is crucial. Other wise Yii 2.0 will not be able to pick up the rules. For example: yii/backend/rbac/
Controller and config setup
This is mostly identical to the documentation
yii/commom/config/main.php
Add the following to the return array:
'authManager' => [
'class' => 'yii\rbac\PhpManager',
'defaultRoles' => ['admin', 'author'], // your define roles
],
Now the fun part, under the controller class you would like to apply the rules
yii/backend/controllers/SiteController.php
'access' => [
'class' => AccessControl::className(),
'rules' => [
[
'allow' => true,
'actions' => [], // applies to all actions
'roles' => ['admin'], // your defined roles
],
],
],
Up to this point, the rules should be working. Under your controller class, double check Yii::$app->getAuthManager() see if it contains your defined roles. If not, it means Yii did not pick up the rules correctly, please check previous steps again.

Zend Framework 2: Extend ZfcUser with own fields

I am working on my first Zend Framework 2 Project. I needed a User Module and integrated ZfcUser for this. Because I have a slight difference in my User Table, I had to use my own User Entity and User Mapper. I created a new Module called ZfcUserExtension.
I then copied a lot of files from the original ZfcUSer Module like:
Entity/User.php
Entity/UserInterface.php
Factory/Entity/IndexControllerFactory.php
Factory/Mapper/UserHydratorFactory.php
Mapper/Exeption/ExceptionInterface
Mapper/Exeption/InvalidArgumentException.php
Mapper/Exeption/RuntimeException.php Mapper/HydratorInterface.php
Mapper/User.php Mapper/UserHydrator.php Mapper/UserHydrator.php
Mapper/UserInterface.php
In zfcuser.global.php I set the user_entity_class to use my own Entity.
'user_entity_class' => 'ZfcUserExtension\Entity\User',
In the module.config.php from the ZfcUserExtension I add the below to make sure that I use my own User Mapper and UserHydrator. The reason for that was that I use "id" as a Primary Key in my User table instead of "user_id", so I had to make sure that this gets overwritten as well.
<?php
return array(
'controllers' => array(
'factories' => array(
'ZfcUserExtension\Controller\Index' => function(Zend\Mvc \Controller\ControllerManager $cm) {
$sm = $cm->getServiceLocator();
return new \ZfcUserExtension\Controller\IndexController(
$sm->get("doctrine.entitymanager.orm_default")
);
}
),
),
'service_manager' => array(
'factories' => array(
'zfcuser_user_mapper' => function ($sm) {
$options = $sm->get('zfcuser_module_options');
$mapper = new \ZfcUserExtension\Mapper\User();
// No db adapter present add below line
$mapper->setDbAdapter($sm->get('zfcuser_zend_db_adapter'));
$entityClass = $options->getUserEntityClass();
// No entity prototype set add below line
$mapper->setEntityPrototype(new $entityClass);
$mapper->setHydrator($sm->get('zfcuser_user_hydrator'));
$mapper->setTableName($options->getTableName());
return $mapper;
},
// 'zfcuserextension_change_password_form' => 'ZfcUserExtension\Factory\Form\ChangePhoneFormFactory',
),
),
I finally got all this to work, till I now run into another problem. I want some additional fields for the User like Phone Number. How would I approach this? I know there are some ideas on the Internet, but I am mainly interested to know how I would actually offer the option to have a "Change Phone" Form. I have created a Form, similar to the "Change Password and "Change Email". I have then created a IndexController.php in my ZfcUSerExtension, again followed the set-up of the UserController from the ZfcUser Module
class IndexController extends AbstractActionController {
const ROUTE_LOGIN = 'zfcuser/login';
/**
* #var \Doctrine\ORM\EntityManager
*/
protected $em;
public function __construct(\Doctrine\ORM\EntityManager $em)
{
$this->em = $em;
}
/**
* #var Form
*/
protected $changeEmailForm;
public function indexAction() {
if (!$this->zfcUserAuthentication()->hasIdentity()) {
return $this->redirect()->toRoute(static::ROUTE_LOGIN);
}
return new ViewModel();
}
public function changephoneAction() {
// if the user isn't logged in, we can't change phone
if (!$this->zfcUserAuthentication()->hasIdentity()) {
return $this->redirect()->toRoute(static::ROUTE_LOGIN);
}
$form = $this->getChangePhoneForm();
$request = $this->getRequest();
$request->getPost()->set('PrevPhone', $this->getUserService()->getAuthService()->getIdentity()->getPrevPhone());
return array(
'status' => false,
'changePhoneForm' => $form,
);
$fm = $this->flashMessenger()->setNamespace('change-phone')->getMessages();
if (isset($fm[0])) {
$status = $fm[0];
} else {
$status = null;
}
$prg = $this->prg(static::ROUTE_LOGIN);
if ($prg instanceof Response) {
return $prg;
} elseif ($prg === false) {
return array(
'status' => $status,
'changePhoneForm' => $form,
);
}
$form->setData($prg);
if (!$form->isValid()) {
return array(
'status' => false,
'changePhoneForm' => $form,
);
}
$change = $this->getUserService()->changeEmail($prg);
if (!$change) {
$this->flashMessenger()->setNamespace('change-email')->addMessage(false);
return array(
'status' => false,
'changeEmailForm' => $form,
);
}
$this->flashMessenger()->setNamespace('change-email')->addMessage(true);
return $this->redirect()->toRoute(static::ROUTE_CHANGEEMAIL);
}
public function getChangePhoneForm()
{
$sl = $this->getServiceLocator();
$this->setChangePhoneForm($sl->get('zfcuserextension_change_phone_form'));
return $this->changePhoneForm;
}
public function setChangePhoneForm($changePhoneForm)
{
$this->changePhoneForm = $changePhoneForm;
return $this;
}
I now noticed that I will face a problem with the User Service Service/User.php. The Service offers a changePassword() and changeEmail() Method. I now thought that I need to copy this file into my own Modules. Am I right that if I extend the User Service from ZfcUser then the Methods changePassword() and changeEmail() will still be available, so I would delete it from the just copied file and just add changePhone()?
And if I am right with my thoughts, the User Service currently starts like this:
class User extends EventProvider implements ServiceManagerAwareInterface
How would I have to change it that I extend the original User Service? I hope somebody can help, I am still rather confused with all this. Thanky you very much in advance.
There are two possible methods:
Build custom classes extending ZfcUser's entity, form and input filter and add your custom fields. In the ZfcUser configuration change aliases or override factories to ensure your custom classes are instantiated rather than the built in ones.
If you are OK with having the custom profile fields stored and accessed separately from the ZfcUser user entity, check out my module on GitHub: LdcUserProfile. It provides a profile system for ZfcUser but also makes it easy to add your own custom profile fieldsets linked to a user.

CakePHP Private messaging system

I am just wondering if anyone can get me started in writing a private messaging system on the CakePHP framework. I am aiming for something similar to the Facebook inbox system. Of course it doesnt have to be as complicated!
I currently have a AUTH system with users whom can log on and off.
The simplest way would be to just create a messages database table with at the very least, five columns: id, sender_id, recipient_id, subject, body. You can then also add other columns you desire, such as created.
You can then set up your controller as follows:
<?php
class MessagesController extends AppController {
public function inbox() {
$messages = $this->Message->find('all', array(
'conditions' => array(
'recipient_id' => $this->Auth->user('id')
)
));
}
public function outbox() {
$messages = $this->Message->find('all', array(
'conditions' => array(
'sender_id' => $this->Auth->user('id')
)
));
}
public function compose() {
if ($this->request->is('post')) {
$this->request->data['Message']['sender_id'] = $this->Auth->user('id');
if ($this->Message->save($this->request->data)) {
$this->Session->setFlash('Message successfully sent.');
$this->redirect(array('action' => 'outbox'));
}
}
}
}
Obviously you'll need to flesh this example out, and change anything that may not apply to your application. You'll also need to add in checks for if the user is friends with the person they're trying to message if you want that as well.

Categories