I'm using OctoberCMS based on Laravel.
I have a backend user, Matt in Groups owners, administrators.
How do I check if user belongs to a specific group to allow authentication?
I was told I need to pass a Group Object, but I don't know what that is.
use Auth;
use BackendAuth;
use Backend\Models\User;
if (BackendAuth::check()) {
// get current backend user
$backend_user = BackendAuth::getUser();
// get current backend user's groups
$backend_user_groups = Backend::getUser()->groups;
// authenticate
if ($backend_user->inGroup('administrators') {
}
}
Error
public function inGroup($group)
Call to a member function getKey() on string
Another way I've tried
if (User::inGroup('administrators') {
}
Error
Non-static method October\Rain\Auth\Models\User::inGroup() should not be called statically
Docs
https://octobercms.com/docs/backend/users
https://github.com/octobercms/library/blob/master/src/Auth/Models/User.php
There could be some helper functions for this but you can also use this:
$backend_user->groups()->whereName('administrators')->exists();
You can also extend the backend user model and add a helper method to it to check roles. Do the following in the boot() method of your plugin:
use Backend\Models\User as BackendUser;
public function boot()
{
BackendUser::extend(function($model) {
$model->addDynamicMethod('hasRole', function($role) use ($model) {
return $model->groups()->whereName($role)->exists();
});
$model->addDynamicMethod('isAdmin', function() use ($model) {
return $model->hasRole('administrators');
});
}
}
I think first you should try to understand the errors before doing the permissions part
public function inGroup($group)
Call to a member function getKey() on string
Did you look what does the inGroup() function do ? This method does not expect a string as a parameter
Here's the complete function :
/**
* See if the user is in the given group.
* #param Group $group
* #return bool
*/
public function inGroup($group)
{
foreach ($this->getGroups() as $_group) {
if ($_group->getKey() == $group->getKey()) <<== Call to a member function getKey() on string
{
return true;
}
}
return false;
}
As regards the second error
Non-static method October\Rain\Auth\Models\User::inGroup() should not be called statically
You should initialize non-static methods like this :
(new User)->someMethod()
Related
Let's suppose we have a site that shows a random list of 20 movies. Logged in users, however, can select their favorite movies, so those movies will be shown instead. This list of movies is shown both in the home page and in some other pages.
To follow the DRY principle, we could encapsulate this logic in its own class, and then inject this class wherever it is necessary to show the list of movies. This class will also have other methods that will be used throughout the application. For example, there is also a method to get one random movie.
The class could look like this (please note this is a simplified example):
class MovieService
{
/** #var Collection $movies */
protected $movies;
public function __construct()
{
$this->movies = Auth::check() ? Auth::user()->favoriteMovies : $this->randomMovies();
}
public function getRandomMovies(): Collection
{
return $this->movies->random(20);
}
public function getOneRandom(): Movie {
return $this->movies->random();
}
protected function randomMovies() {
return Movie::inRandomOrder()->take(20)->get();
}
}
Note: Please note that this is an example and that some things could be improved.
As this class could be used multiple times in the same request, it is a good idea to make it a singleton in the IoC container, so that the queries that are run when instantiated are not run more than once.
However, now we encounter a problem. We need this class in a private method in a controller. We could directly call the app container like app() or App::make() but we would like to avoid facades and global helpers with custom dependencies.
class HomeController extends Controller
{
/** #var MovieService $movieService */
protected $movieService;
public function __construct(MovieService $movieService)
{
$this->movieService = $movieService;
}
public function index()
{
$movies = $this->getMovies();
return view('home', compact('movies'));
}
protected function getMovies()
{
// Let's imagine there's some extra logic here so that we would actually need this method.
return $this->movieService->getRandomMovies();
}
}
We have found a problem. A controller's constructor is run before the middleware pipeline, which means that there's no session and, hence, no user identification. Now Auth::check() in MovieService is always returning false, so the default movies will always be shown.
What would you do to fix this?
It's cleaner to not use the constructor of an object for logic, only for managing dependencies. Coincidentally this will also fix the issue you're having by moving the Auth::check() logic to your getter methods instead. Besides that you could also consider injecting the AuthManager instead of relying on the Auth facade, but that's just a sidenote.
class MovieService
{
/** #var AuthManager $auth */
protected $auth;
protected $movies;
public function __construct(Illuminate\Auth\AuthManager $auth)
{
$this->auth = $auth;
}
public function getRandomMovies(): Collection
{
return $this->getMoviesForCurrentUser()->random(20);
}
public function getOneRandom(): Movie {
return $this->getMoviesForCurrentUser()->random();
}
protected function randomMovies() {
if ($this->movies === null) {
$this->movies = Movie::inRandomOrder()->take(20)->get();
}
return $this->movies;
}
protected function getMoviesForCurrentUser() {
if ($this->auth->check()) {
return $this->auth->user->favoriteMovies;
}
return $this->randomMovies();
}
}
I have a selection control on a blade form that is to be refreshed via ajax through this function:
function getOpciones(tbName) {
$.get('/ajax/read-data/' + tbName, function(data){
return (data);
});
}
The function takes a string variable 'tbName' whith the name of the table the control is related to, and passes it on as a parameter to the route:
Route::get('/ajax/read-data/{modelo}', 'AjaxController#readData');
Then the controller should get the parameter {modelo}, and retrieve the records in that table:
use App\RegFiscal;
public function readData($modelo) {
$arreglo = $modelo::all();
return response($arreglo);
}
But even though I am referencing the model with 'use App\RegFiscal', all I get is this error in laravel log:
2018-03-23 18:52:08] local.ERROR: exception
'Symfony\Component\Debug\Exception\FatalErrorException' with message
'Class 'RegFiscal' not found' in
C:\wamp64\www\laravel\cte\app\Http\Controllers\AjaxController.php:32
I´m new to Laravel, so needless to say I am lost and any help would be greatly appreciated. Thanks!
Just because you use App\RegFiscal doesn't mean $modelo is associated with it.
What you can do, though, is use app("App\\$modelo") to load in your model based on the parameter you get from the router. You would no longer need to use App\RegFiscal either.
$arreglo = app("App\\$modelo");
return response($arreglo::all());
This is assuming your model is stored in the default app directory within your Laravel project. If not you can change "App\" to where ever it is stored. If for example your model is in app\models\modelname.php it would be "App\Models\\$modelo".
You can do this as the following:
public function readData($modelo) {
$modelName = '\App' . '\\' . $modelo;
$class = new $modelName();
arreglo = $class::all();
return response($arreglo);
}
To those like me who wanted to inject it on a constructor, here's how to do it:
~$ php artisan make:provider MyProvider
Then override the register function like so:
class MyProvider implements ServiceProvider {
/** #override */
public function register() {
$this->app->bind(ShapeInterface::class, function ($app) {
return new Square($app->make(MyModel::class));
});
}
}
The ShapeInterface is a simple interface and Square is a simple class that implements the shape interface with a constructor parameter of the eloquent model.
class Square implements ShapeInterface {
private MyModel $model;
function __construct(MyModel $model) {
$this->model = $model;
}
...
}
For example, I want to add a variable in my default view (default.blade.php)
I can of course, define my variable directly into the view file, like this :
#php ($user = Sentinel::getUser())
But this is not recommended.
Should I add it on AppServiceProvider.php ? (https://laravel.com/docs/5.4/views#sharing-data-with-all-views)
But with which call ? Like this :
public function boot()
{ $user = Sentinel::getUser(); }
This get : Undefined variable: user
public function boot()
{ View::share('user', Sentinel::getUser()); }
This get Trying to get property of non-object, so Sentinel is not truly declare
Or in the a controller
public function __construct()
{
//user
$user = Sentinel::getUser();
view()->share('user',$user);
}
I also try this into my controller
public function boot()
{
return view('layouts/default')->with('user', Sentinel::getUser(););
}
or
public function boot() {
view()->composer('layouts.default', function($view) {
$view->with('user', Sentinel::getUser());
});
}
Still get "Undefined variable: user"
The idea is to call View::XXXXX within the boot method of a service provider...
The simplest is to call share on the view within your app service provider... this will make it available to all views... however take notice of when this value is resolved, it is resolved at boot time...
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
View::share('key', 'value');
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
}
}
You can also create a view composer that will be run prior to rendering a specific view or set of views... You can give it the view/class or view/closure... this will be evaluated before the view renders...
Bind a view composer class:
public function boot()
{
// Using class based composers...
View::composer(
'profile', 'App\Http\ViewComposers\ProfileComposer'
);
}
Or attach a closure... and do what you need to boot that vie within that closure...
public function boot()
{
// Using Closure based composers...
View::composer('dashboard', function ($view) {
//
});
}
You can bind to all views like the View::share... but push the evaluation to before the view render by using a simple closure based composer... in your case...
// Using Closure based composers...
View::composer('dashboard', function ($view) {
$view->with('user', Sentinel::getUser() );
});
Hope this helps...
In your controller, define a function like that:
public function index() {
$user = Sentinel::getUser();
//The parameter of the view is your blade file relative to your directory
//resources/views/default.blade.php
return view('default')->with('user', $user);
}
In your web.php (routes file), add this
//First parameter is the url, second is the controller/function
Route::get('/', 'YourControllerName#index');
Now test the view in localhost [Optional]
http://localhost:8000
I'm trying to allow user to view their own profile in Laravel 5.4.
UserPolicy.php
public function view(User $authUser, $user)
{
return true;
}
registered policy in AuthServiceProvider.php
protected $policies = [
App\Task::class => App\Policies\TaskPolicy::class,
App\User::class => App\Policies\UserPolicy::class
];
Routes
Route::group(['middleware' => 'auth'], function() {
Route::resource('user', 'UserController');
} );
Blade template
#can ( 'view', $user )
// yes
#else
// no
#endcan
UserController.php
public function profile()
{
return $this->show(Auth::user()->id);
}
public function show($id)
{
$user = User::find($id);
return view('user.show', array( 'user'=>$user,'data'=>$this->data ) );
}
The return is always 'false'. Same for calling policy form the controller. Where do I go wrong?
Answering my own question feels weird, but I hate it when I come across questions without followups.
So after double checking It turned out that if I remove authorizeResource from the constructor:
public function __construct()
{
$this->authorizeResource(User::class);
}
and check for authorization in the controller function:
$this->authorize('view',$user);
everything works.
I must've missed this part when I added $user as a parameter in the policy function. So the user to be viewed is never passed in the authorizeResource method.
Thanks everyone for taking your time to help me.
When you add
public function __construct()
{
$this->authorizeResource(User::class);
}
to your Controller, you have to edit all your function signatures to match it to the class e.g. your show signature has to change from public function show($id)
to public function show(User $user)
After that it should work
Just a different approach here to users viewing their own profile.
First, I will create a route for that
Route::group(['middleware' => 'auth'], function() {
Route::get('profile', 'UserController#profile');
});
Then in the profile function I do
public function profile()
{
$user = Auth::user();
return view('profile', compact('user'));
}
This way, user automatically only views their own profile.
Now, if you want to allow some users to view others' profiles, then you can use Policy. Why? Because I think user should ALWAYS be able to view their own profile. But not all users should view other users profiles.
Solution:
Change the second parameter from #can( 'view', $user ) to #can( 'view', $subject ) and it will work find.
Why:
Because you're doing it the wrong way.
public function view(User $user, $subject){
return true;
}
Just look carefully the policy view method, first parameter is authenticated user or current user and second parameter is $subject, Since policies organize authorization logic around models.
Policies are classes that organize authorization logic around a
particular model or resource. For example, if your application is a
blog, you may have a Post model and a corresponding PostPolicy to
authorize user actions such as creating or updating posts.
if you want to go further deep inside it.
https://github.com/laravel/framework/blob/5.3/src/Illuminate/Auth/Access/Gate.php#L353
/**
* Resolve the callback for a policy check.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param string $ability
* #param array $arguments
* #return callable
*/
protected function resolvePolicyCallback($user, $ability, array $arguments)
{
return function () use ($user, $ability, $arguments) {
$instance = $this->getPolicyFor($arguments[0]);
// If we receive a non-null result from the before method, we will return it
// as the final result. This will allow developers to override the checks
// in the policy to return a result for all rules defined in the class.
if (method_exists($instance, 'before')) {
if (! is_null($result = $instance->before($user, $ability, ...$arguments))) {
return $result;
}
}
if (strpos($ability, '-') !== false) {
$ability = Str::camel($ability);
}
// If the first argument is a string, that means they are passing a class name
// to the policy. We will remove the first argument from this argument list
// because the policy already knows what type of models it can authorize.
if (isset($arguments[0]) && is_string($arguments[0])) {
array_shift($arguments);
}
if (! is_callable([$instance, $ability])) {
return false;
}
return $instance->{$ability}($user, ...$arguments);
};
}
See the last line where it is calling the method with $user and $argument( in our case Model ) is passed.
Laravel Docs for Authorization/Policies
It's possible to escape one or more policies methods using options parameter at authorizeResource with except:
public function __construct()
{
$this->authorizeResource(User::class, 'user', ['except' => ['view']]);
}
This should be on Laravel's documentation, but it isn't. I discovered this just guessing. I think this way it is a better approach thus, by removing authorizeResource method in the construct, it would be necessary to implement the authorization method for each resource action in order to protect the controller.
I have a Product model
class Product extends Model
{
...
public function prices()
{
return $this->hasMany('App\Price');
}
...
}
I want to add a function which will return the lowest price, and in controller I can get the value using:
Product::find(1)->lowest;
I added this in Product model:
public function lowest()
{
return $this->prices->min('price');
}
but I got an error saying:
Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
And if I use Product::find(1)->lowest();, it will work. Is it possible to get Product::find(1)->lowest; to work?
Any help would be appreciated.
When you try to access a function in the model as a variable, laravel assumes you're trying to retrieve a related model. They call them dynamic properties. What you need instead is a custom attribute.
Before Laravel 9
Laravel 6 docs: https://laravel.com/docs/6.x/eloquent-mutators
add following method to your model:
public function getLowestAttribute()
{
//do whatever you want to do
return 'lowest price';
}
Now you should be able to access it like this:
Product::find(1)->lowest;
EDIT: New in Laravel 9
Laravel 9 offers a new way of dealing with attributes:
Docs: https://laravel.com/docs/9.x/eloquent-mutators#accessors-and-mutators
// use Illuminate\Database\Eloquent\Casts\Attribute;
public function lowest(): Attribute
{
return new Attribute(
get: function( $originalValue ){
//do whatever you want to do
//return $modifiedValue;
});
/**
* Or alternatively:-
*
* return Attribute::get( function( $originalValue ){
* // do whatever you want to do
* // return $modifiedValue;
* });
*/
}
Use Eloquent accessors
public function getLowestAttribute()
{
return $this->prices->min('price');
}
Then
$product->lowest;
you can use above methods or use following method to add a function direct into existing model:
class Company extends Model
{
protected $table = 'companies';
// get detail by id
static function detail($id)
{
return self::find($id)->toArray();
}
// get list by condition
static function list($name = '')
{
if ( !empty($name) ) return self::where('name', 'LIKE', $name)->get()->toArray();
else return self::all()->toArray();
}
}
Or use Illuminate\Support\Facades\DB; inside your function. Hope this help others.
why you just dont do this? i know , it's not what you asked for specificallyand it migh be a bad practice sometimes. but in your case i guess it's good.
$product = Product::with(['prices' => function ($query) {
$query->min('price');
}])->find($id);
change follow code
public function lowest()
{
return $this->prices->min('price');
}
to
// add get as prefix and add posfix Attribute and make camel case function
public function getLowestAttribute()
{
return $this->prices->min('price');
}