I've got Backpack for Laravel installed and have been using it for some time as an admin back end on a project. I'm also using the spatie/permission module (might come with Backpack, can't remember) to create users for the front end.
Currently, all users are able to access both front end and back end regardless of the group they belong to. I'd like to change that so that only members in an "admin" group are able to access the back end. I've followed the instructions here for separating front end and back end sessions but that's not really what I want as all users are still able to access both sites of the project.
I'm guessing I need to add a guard to the CRUD routes but I'm finding it to be much harder than it should be. Any pointers on how to do this would be greatly appreciated. TIA.
You can create a new middleware and use it in your routes group for admin routes.
To create a new middleware use the php artisan command like so: (you can name the new middleware what ever you want:
php artisan make:middleware RequireAdminRole
Now, inside your new middleware, on the handle function you can have something like this that returns a 403 Forbidden error message:
public function handle($request, Closure $next)
{
$user = auth()->user();
if (!$user) return $next($request);
if (!$user->hasRole('Admin'))
{
// if your sessions are decoupled from the frontend
// you can even logout the user like so:
// auth()->logout();
abort(403, 'Access denied');
}
return $next($request);
}
Here we are using the hasRole method, but there are more that you can use. See the spatie/laravel-permissions documentation for more info.
Now, let's assign this middleware a 'name' so we can use it in our route groups for the admin. Inside the App\Kernel.php file, in the end, inside the $routeMiddleware array add it and give it a new, for example:
'isadmin' => \App\Http\Middleware\RequireAdminRole::class,
And finally, you can add this middleware to your admin routes group (which should be in custom.php file if you're using latest backpack 3.4 version) :
Route::group([
'prefix' => 'admin',
'middleware' => ['web', 'isadmin', config('backpack.base.middleware_key', 'admin')],
'namespace' => 'App\Http\Controllers\Admin',
], function () {
// your routes are here
});
Now all your requests to the admin routes should be protected by the user role check.
Please let us know how it went for you, and if you encountered any issues.
Best regards,
~Cristian
Related
Is it possible to store all records of the news extension (ext:news) on the same storage page, but show only records, which are created by the loggedin backend user?
So the current backend user can just see and edit his own records? Admins should see all records of course.
No, this is not possible, since backend user permissions on record level are not implemented in TYPO3.
So you either have to separate the news records of the users in separate sysfolders or you could try to use hooks (e.g. $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/class.db_list_extra.inc']['getTable']) or XClass to customize TYPO3 backend to your needs. I do not recommend the latter, since the TYPO3 backend permission system is complex and you would need to make sure to restrict record access in several parts of TYPO3 (e.g. recordlist, element browser, related news field, ...)
There are two ways to archive that:
If the backend user is not too much. You can just create a page(type
is folder) named with the backend user name. And in the backend user
module you can set the permission(Not for the group user but for the
single backend user only).
if the backend user is too much. and You just wanna set permissions for the group and all backend users are sharing the same rules. You can refer to Hook:https://docs.typo3.org/p/georgringer/news/main/en-us/Tutorials/ExtendNews/Hooks/Index.html so that the basic logic is like this:
2.1 get current logged-in user group.
2.2 if the group is Reporter, we can use the hook for the listing page:
$constraints[] = $query->equals('cruser_id', $be_id);
Edit(3/3/2022):
Hi Chris, Yes you are right.
Today, I have got a chance to dig into the news extension. I think we can still make it by the hook
in your ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][\TYPO3\CMS\Recordlist\RecordList\DatabaseRecordList::class]['modifyQuery'][$_EXTKEY]
= \T3docs\SitePackage\Hooks\DatabaseRecordListHook::class;
(Please make sure the namespace is correct)
within the file : T3docs\SitePackage\Hooks\DatabaseRecordListHook.Create a function named modifyQuery:
public function modifyQuery($parameters,
$table,
$pageId,
$additionalConstraints,
$fields,
$queryBuilder)
{
if ($table === 'tx_news_domain_model_news') {
$tsconfig = $GLOBALS['BE_USER']->getTSConfig();
if (!empty($tsconfig['options.']['be_users'])) {
$be_users = $tsconfig['options.']['be_users'];
$queryBuilder->andWhere('cruser_id IN (' . $be_users . ')');
}
}
return $queryBuilder;
}
in the user Options tab set Tsconfg : options.be_users = 3,100
(3,100 is the be_user id. And the 2 two backend users's news will show up)
thus, it works for me.
Quick background: I'm fairly experienced with PHP, but needed to build my first RESTful API. I figured I'd try Laravel (5.2) and am starting to feel pretty comfortable with it.
I started adding auth to my project over the weekend and I am really struggling to get it working. I got the basic Laravel Auth middleware working quickly, but I think I need to be using OAuth2 for production (I will be building a mobile app that will connect up to this server). I'm using the Luca Degasperi OAuth2 package, which seems to be pretty popular.
I reviewed the actual documentation: https://github.com/lucadegasperi/oauth2-server-laravel/tree/master/docs#readme)
I also went through this tutorial: https://medium.com/#mshanak/laravel-5-token-based-authentication-ae258c12cfea#.5lszb67xb
And, most recently, I found this thread about the need to seed the OAuth tables before anything will work: https://github.com/lucadegasperi/oauth2-server-laravel/issues/56
That's all great, but there are some minor differences in the most recent distribution of Laravel. For example, /app/Http/Kernel.php is slightly different from what's shown in some of the examples I found because it now uses middleware groups. I thought I handled those differences correctly (I added the OAuthExceptionHandlerMiddleware class to the 'web' section of $middlewareGroups instead of $middleware). I got my seeder working (the current oauth_scopes table only allows you to supply a description, so I had to slim down what was provided in the third link above).
If I put a test route in my 'web' group in routes.php, I would have thought this would require OAuth because I added OAuth to the 'web' middleware group in Kernel.php. That's not the case. My route works with no authentication if I do that.
I then explicitly added the OAuth middleware to my test route as follows:
Route::get('tests/events', ['middleware' => 'oauth', function() {
$events = App\Event::get();
return response()->json($events);
}]);
That causes a 500 error ("ErrorException in OAuth2ServerServiceProvider.php line 126: explode() expects parameter 2 to be string, object given").
I'm to feel pretty lost. Each of these packages seems to be shifting so quickly that there's no complete documentation on how to get this up and running.
What else do I need to do to get this functioning?
The following link is what finally got me un-stuck:
https://github.com/lucadegasperi/oauth2-server-laravel/blob/master/docs/authorization-server/password.md
Now that I have it working, I'll try and make this a complete how-to FOR PASSWORD GRANT TYPES ONLY. I didn't play with other grant types. So this assumes you're building something like a RESTful API where users will connect to it with a client app that you're going to build. So users will create a user account in your system and then when they send a REST request, the OAuth2 package will authenticate them and send them a token to stay logged in.
I'm using Laravel 5.2 and already had the basic Auth package up and running. Be advised that a lot of these steps seem to change even with incremental releases of Laravel or the OAuth2 package.
The first part of getting this working is fairly well documented already (https://github.com/lucadegasperi/oauth2-server-laravel/tree/master/docs#readme), but here's a summary just in case...
Edit the require section of your composer.json file to look something like this:
"require": {
"php": ">=5.5.9",
"laravel/framework": "5.2.*",
"lucadegasperi/oauth2-server-laravel": "5.1.*"
},
Run composer update to download the package.
Open your config/app.php file and add the following two lines to the end of the providers section:
LucaDegasperi\OAuth2Server\Storage\FluentStorageServiceProvider::class,
LucaDegasperi\OAuth2Server\OAuth2ServerServiceProvider::class,
Also in config/app.php, add this line to the aliases array:
'Authorizer' => LucaDegasperi\OAuth2Server\Facades\Authorizer::class,
Now we start to do things a little differently from the documentation to accommodate the current version of Laravel...
Open app/Http/Kernel.php. Laravel now uses groups and it didn't used to. Update your $middlewareGroups to look like this:
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
//Added for OAuth2 Server
\LucaDegasperi\OAuth2Server\Middleware\OAuthExceptionHandlerMiddleware::class,
//Commented out for OAuth2 Server
//\App\Http\Middleware\VerifyCsrfToken::class,
],
'api' => [
'throttle:60,1',
],
];
Also in app/Http/kernel.php, update $routeMiddleware to look like this:
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
//Added for OAuth2 Server
'oauth' => \LucaDegasperi\OAuth2Server\Middleware\OAuthMiddleware::class,
'oauth-user' => \LucaDegasperi\OAuth2Server\Middleware\OAuthUserOwnerMiddleware::class,
'oauth-client' => \LucaDegasperi\OAuth2Server\Middleware\OAuthClientOwnerMiddleware::class,
'check-authorization-params' => \LucaDegasperi\OAuth2Server\Middleware\CheckAuthCodeRequestMiddleware::class,
'csrf' => App\Http\Middleware\VerifyCsrfToken::class,
];
You now have to set up your grant types. You used to do this all in one place in config\oauth2.php using an array with a closure for callback. With the most recent version of the OAuth2 server package, you can't use a closure for callback anymore. It has to be a string. So your grant_types should look something like this:
'grant_types' => [
'password' => [
'class' => '\League\OAuth2\Server\Grant\PasswordGrant',
'callback' => '\App\PasswordGrantVerifier#verify',
'access_token_ttl' => 3600
]
]
access_token_ttl is the duration that an auth token will be good for (in seconds). The main package documentation uses 3600 (1 hour) by default. You might want to try 604800 (1 week) instead -- at least during testing.
You now need to create the PasswordGrantVerifier class and verify method that you just called in the code section above. So you create a file App/PasswordGrantVerifier.php and use the following code (which is basically what used to go in the closure for callback).
<?php
namespace App;
use Illuminate\Support\Facades\Auth;
class PasswordGrantVerifier
{
public function verify($username, $password)
{
$credentials = [
'email' => $username,
'password' => $password,
];
if (Auth::once($credentials)) {
return Auth::user()->id;
}
return false;
}
}
You will need at least one row in the oauth_clients table before OAuth2 will work. You can insert something manually or create a seeder. To create a seeder, modify database/seeds/DatabaseSeeder.php and add the following to the end of the run() method:
$this->call(OAuthClientsTableSeeder::class);
Now create a file called database/seeds/OAuthClientsTableSeeder.php and enter something like this:
<?php
use Illuminate\Database\Seeder;
class OAuthClientsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
//Add sample users
$oAuthClients = array(
array(
'id' => 'TEST_ENVIRONMENT',
'secret' => 'b17b0ec30dbb6e1726a17972afad008be6a3e4a5',
'name' => 'TEST_ENVIRONMENT'
)
);
foreach ($oAuthClients as $oAuthClient) {
App\OAuthClient::create($oAuthClient);
}
}
}
Run php artisan vendor:publish to publish the package configuration and migrations. Run php artisan migrate to set up the billion-or-so new tables for OAuth. Run php artisan db:seed to seed your database.
You can now set up some test routes in app\Http\routes.php. They should look something like this:
Route::post('oauth/access_token', function() {
return Response::json(Authorizer::issueAccessToken());
});
Route::group(['middleware' => 'oauth'], function () {
Route::get('authroute', function() {
//OAuth will be required to access this route
});
Route::post('postwithauth', function(Request $request) {
$userID = Authorizer::getResourceOwnerId();
$input = $request->input();
return response()->json(array('userID' => $userID, 'input' => $input));
});
});
Route::get('noauthroute', function () {
//No authorization will be required to access this route
});
Pay close attention to the postwithauth route I included above. The OAuth2 package recently changed how you access the user's ID and it took me quite a while to figure out how to get it.
Now that it's time for testing, point your browser to localhost:8000 (or whatever the path is for your test environment) and create a user account for yourself (this step just uses the standard Laravel Auth package).
Go into your HTTP client (I'm currently using Paw and I like it). Go to request->authorization->OAuth2 to set up authorization for the route you're going to test. For Grant Type, select Resource Owner Password Credentials. If you used the seed example I provided above, the Client ID is TEST_ENVIRONMENT, the Client Secret is b17b0ec30dbb6e1726a17972afad008be6a3e4a5, enter the username (email) and password you created through the web Auth interface, your Access Toekn URL will be something like localhost:8000/oauth/access_token (depending on how you set up your test environment), leave Scope blank, and Token should say Bearer. Click on Get Access Token then say Use Access Token when prompted.
That should be it!
I want to create some routes with restrictions by role. Something like this:
my $auth = $app->routes->under->to('auth#check');
$auth->get('/list')->to('some#list')->name('list');
$auth->get('/add')->to('some#add', roles => ['user', 'admin'])->name('add');
I don't have any problem with checking roles in after_dispatch hook. But I cannot access this data when I'm trying to create a links for this routes.
For example, I'm a guest on /list route and want to form menu with available links. So, I have to check roles from route /add to decide to show this link or not.
For this moment I found only one way to get default data from route with name:
app->routes->lookup('add')->pattern->defaults->{roles}
And it looks like a hack. How I can do this in a right way?
You are right. This method to get defaults of the route is documented here
my $defaults = $pattern->defaults;
$pattern = $pattern->defaults({foo => 'bar'});
I use dektrium user registration form.
registration link generates domain.com/user/register link
but it's not base user model, this module is located inside vendor/dektrium folder.
Now in base controllers folder I have UsersController with view action.
and after finishing registration I want to start view action of UsersController to view users page.
This is registration module code
public function actionRegister()
{
if (!$this->module->enableRegistration) {
throw new NotFoundHttpException;
}
$model = $this->module->manager->createRegistrationForm();
if ($model->load(\Yii::$app->request->post()) && $model->register()) {
return $this->redirect(array('users/'.$model->username));
}
return $this->render('register', [
'model' => $model
]);
}
As you can see I've put there this code
return $this->redirect(array('users/'.$model->username));
Which is supposed to take user to it's own page at domain.com/users/username.
But unfortunatelly url is forming in the following way
domain.com/user/users/username
How can I fix this problem and direct user to domain.com/users/username page ?
Add an extra / in front of the users redirect. it should be
return $this->redirect(array('/users/'.$model->username));
Or you should actually create the url the proper way, that would be the best way to do this, but I do not know the way you have your rules set up so I cannot help you there. I am just guessing here but it should be:
return $this->redirect(array('users/view, 'username' => $model->username));
In this way you are using your url manager, not just hardcoding the url. In the future if you decide to change the link it will be much easier (replace just the url line in your config) and not go in files to change it.
I'm writing tests for my current project, made with Zend Framework.
Everything's fine, but I have a problem testing the logged users actions/controllers: I need to be logged in to be able to perform the action/controller.
How can I be logged in PHPUnit?
As you are saying you want to test actions/controllers, I suppose you are not writting unit-tests, but functional/integration tests -- ie, working with Zend_Test and testing via the MVC.
Here is a test-function I used in a project, where I'm testing if logging in is OK :
public function testLoggingInShouldBeOk()
{
$this->dispatch('/login/login');
$csrf = $this->_getLoginFormCSRF();
$this->resetResponse();
$this->request->setPost(array(
'login' => 'LOGIN',
'password' => 'PASSWORD',
'csrfLogin' => $csrf,
'ok' => 'Login',
));
$this->request->setMethod('POST');
$this->dispatch('/login/login');
$this->assertRedirectTo('/');
$this->assertTrue(Zend_Auth::getInstance()->hasIdentity());
}
Simply : I'm loading the login form, extracting the CSRF token, populating the form, and posting it.
Then, I can test if I'm connected.
With that, you can probably extract the logging-in part, to call it before each one of your tests that require a valid user to be logged-in.
There is another way. On my User entity I have a login() method that puts the user's id into the session and a static variable. What I just do in the test setUp() is call $user->login() and it works. In testing environment sessions are not used (setting Zend_Session::$isUnitTested = true has this effect) and tests rely on the static variable. Just remember to clear the static variable (logout() the user) on tearDown().
I think this article could help you:
http://perevodik.net/en/posts/7/
It describes how to create a fake identity you can use to set the environment to a state equivalent to a user being logged in.
In much the same way Pascal is using this function:
$this->_getLoginFormCSRF();
I have created a generic function that returns the value by loading the form using the form element manager:
public function _getCSRFHashValueFromForm($formAlias, $csrfName) {
$form = $this->servicemanager->get('FormElementManager')->get($formAlias);
return $form->get($csrfName)->getValue(); }
This of course assumes that the CSRF is bound to the form and not within any fieldset etc.