D.R.Y authorization with Gate, laravel 5.2 - authentication

This is the problem.. I have the next code repeated in different methods and in different controllers.
if (Gate::denies('admin-role')) {
return redirect()->action('HomeController#index')->with('warning','No');
}
If someday I decide change the action route or the message I don't want to go through all methods and change it. Is there a way I can put this code in some part and specify the methods where is going to be applied?
I try to do it with middlewares like this:
$this->middleware('can:admin-role', ['only' => 'index', 'create']);
but I don't know how to custom the redirection.
Use custom middlewares I don't know if its a good idea because then I have to create a middleware for each role(user, admin, superadmin, mixroles, etc).

Create a middleware class and apply that in your routes/route groups, that way you won't have to apply it in individual controllers or their methods.
<?php
namespace App\Http\Middleware;
use Closure;
class CheckRole
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if (Gate::denies('admin-role')) {
return redirect()->action('HomeController#index')->with('warning','No');
}
return $next($request);
}
}

Related

How to hide some views only in production env Laravel

I am pretty new to Laravel. So I have a Laravel Vue app where i want to hide a view.blade only in my production environment and not on staging , i tried with the config file view but i didn't succeed.
Can anyone help me please ?
Thank you
One possible solution would be to use an if statement for the view. It's difficult to give a more contextual answer without more context.
example below:
<template>
<view v-if="inProduction"></view>
</template>
export default {
computed: {
inProduction(){
return process.env.NODE_ENV === "production";
}
}
}
You could do a conditional route middleware too so it just checks the config file (cached) for the env. Then you can send them anywhere you want if someone tried to hit that route.
If you're trying to hide a full route on certain env best way would be to create a Middleware like this:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class AppEnv
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
* #return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
*/
public function handle(Request $request, Closure $next, $env)
{
if(config('app.env') == $env) {
return $next($request);
}
abort(404);
}
}
You'll have to register this middleware on the app/Http/Kernel $routeMiddleware array
Then you can use it like this:
Route::view('/', 'welcome')->middleware(['env:local'])
(Change local for the env where you want to show this route)

Using Typo3 eID for nusoap call

i'm using the following code for my soap call.
If i add the wsdl and make my client call i just get the response without the whole soap wrap.
declare(strict_types=1);
namespace Vendor\DocBasics\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use Vendor\DocBasics\Domain\Repository\EventsRepository;
use Vendor\CartExtended\Domain\Repository\Order\ItemRepository;
require_once(PATH_site . 'typo3conf/ext/doc_basics/Classes/Libs/nusoap/nusoap.php');
class EventsController
{
protected $action = '';
protected $order;
protected $Vbeln = '';
protected $Zaehl = '';
protected $objectManager;
/**
* #var array
*/
protected $responseArray = [
'hasErrors' => false,
'message' => 'Nothing to declare'
];
/**
* #param ServerRequestInterface $request
* #param ResponseInterface $response
* #return ResponseInterface
*/
public function processRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$this->initializeData(file_get_contents('php://input')); //xml datas from soap call
switch (isset($request->getQueryParams()['action']) ? (string)$request->getQueryParams()['action'] : '') {
case 'create':
$this->createAction();
break;
case 'update':
$this->updateAction();
break;
default:
$this->updateAction(); //call it as default, so i can call it as endpoint without action parameter
}
$this->prepareResponse($response,$request->getQueryParams()['action']);
return $response;
}
/**
* action create
*
* #return void
*/
public function createAction()
{
$server = new \soap_server();
$server->configureWSDL("updateorderservice", "https://domain.tld/updateorderservice", "https://domain.tld/index.php?eID=update_order");
$server->register(
"update",
array("Vbeln" => 'xsd:string', "Zaehl" => 'xsd:integer'),
array("return" => 'xsd:string'),
"https://domain.tld/updateorderservice",
"update",
"rpc",
"encoded",
"Update a given order"
);
$this->responseArray['message']= $server->service(file_get_contents('php://input'));
}
public function updateAction()
{
$this->objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$this->itemRepository = $this->objectManager->get(ItemRepository::class);
$order=$this->itemRepository->findOrderByOrder($this->Vbeln);
if($order){
$order->setCancelDate($this->Veindat);
$this->itemRepository->update($order);
$this->persistenceManager->persistAll();
$msg= '<MESSAGE><TYPE>S</TYPE><MSGTXT>Auftrag '.$this->Vbeln.' aktualisiert!</MSGTXT></MESSAGE>';
}
else $msg= '<MESSAGE><TYPE>E</TYPE><MSGTXT>Auftrag '.$this->Vbeln.' konnte nicht aktualisiert!</MSGTXT></MESSAGE>';
$this->responseArray['message'] = $msg; //receive the message but don't know how to wrap it
}
/**
* #param ResponseInterface $response
* #param String $action
* #return void
*/
protected function prepareResponse(ResponseInterface &$response, $action)
{
if($action=='create'){
$response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
$response->getBody()->write($this->responseArray['message']);
}
else{
$response = $response->withHeader('Content-Type', 'text/xml; charset=utf-8');
$response->getBody()->write($this->responseArray['message']);
}
}
/**
* #param $request
* #return void
*/
protected function initializeData($request)
{
$resp= $this->parseResult($request);
if($resp->Vbeln[0]) $this->Vbeln = (string)($resp->Vbeln[0]);
if($resp->Zaehl[0]) $this->Zaehl = intval($resp->Zaehl[0]);
}
public function parseResult($result){
$result = str_ireplace(['soapenv:','soap:','upd:'], '', $result);
$result = simplexml_load_string($result);
$notification = $result->Body->Update;
return $notification;
}
}
My response is just the small xml i'm writing as return to the updateAction(). My response should be wrapped between and so on
May be i'm missing something or the way i'm using the eID concept is wrong.
your case makes much more sense here, than on facebook, but in your future posts on stackoverflow you should write more background information for all the other devs who have no background information as I have.
In general: You overcomplicate things. :-)
First, you told me on facebook, That your soap server as such (without TYPO3 integration as eID) works. Is it so? I can not see that from your code :-)
You process some control http parameter "action" and create the SOAP server only if the value is "create".
But for the "action" value "update", there is no server initialization? How can that work?
You must remember, that a SOAP server must be initialized on each request.
It is not a deamon, which gets started once and runs in the background.
There is absolute no need for such an "action" control parameter on the input side. This is what a "SOAP remote method" registration of the NuSOAP server is for - a method with a distinguished name, which you call explicitelly on the client side.
Then your parseResult and parseResponse methods? Are you trying to handle the SOAP protocol manually? NuSOAP is supposed to handle all that for you.
You just have to register appropriate data types (ComplexType).
You need to get much more background knowledge on NuSOAP itself first.
Here is a simple working example I used in a very old project. I reduced it to show you how NuSOAP is supposed to work.
The server defines one single Method "echoStringArray", which takes one array as attribute named "inputStringArray" and echoes it back without any modifications.
You can take and copy paste without modifications into your eID script and so you will have immediate basic TYPO3 integration.
Then add other things one by one, such as database layer and so on.
Try not to use classes first, but the same procedural approach from my example.
So here is the server definition soap-server.php:
<?php
// Pull in the NuSOAP code
require_once('./nusoap-0.9.5/lib/nusoap.php');
function logRequest($userAgent, $methodName, $request, $response, $result) {
$fp = fopen("./soap.log","a+");
fputs($fp,"$userAgent\n$methodName\n$request\n$response\n$result\n=======================================\n");
fclose($fp);
}
$log = true;
// Create the server instance
$SOAP_server = new soap_server;
$SOAP_server->configureWSDL(
'Test Service',
'http://my-soap-server.local/xsd'
);
// Set schema target namespace
$SOAP_server->wsdl->schemaTargetNamespace = 'http://my-soap-server/xsd';
// Define SOAP-Types which we will need. In this case a simple array with strings
$SOAP_server->wsdl->addComplexType(
'ArrayOfstring',
'complexType',
'array',
'',
'SOAP-ENC:Array',
array(),
array(array('ref'=>'SOAP-ENC:arrayType','wsdl:arrayType'=>'string[]')),
'xsd:string'
);
// Define SOAP endpoints (remote methods)
$SOAP_server->register(
'echoStringArray', // this is the name of the remote method and the handler identifier below at the same time
array('inputStringArray'=>'tns:ArrayOfstring'),
array('return'=>'tns:ArrayOfstring'),
'http://soapinterop.org/'
);
// Define SOAP method handlers
// This is the handler for the registered echoStringArray SOAP method. It just receives an array with strings and echoes it back unmodified
function echoStringArray($inputStringArray){
$outputData = $inputStringArray;
return $outputData;
}
// Now let the SOAP service work on the request
$SOAP_server->service(file_get_contents("php://input"));
if(isset($log) and $log == true){
logRequest($SOAP_server->headers['User-Agent'],$SOAP_server->methodname,$SOAP_server->request,$SOAP_server->response,$SOAP_server->result);
}
And here is the appropriate client soap-client.php:
<?php
require_once('./nusoap-0.9.5/lib/nusoap.php');
// This is your Web service server WSDL URL address
$wsdl = "http://my-soap-server.local/soap-server.php?wsdl";
// Create client object
$client = new nusoap_client($wsdl, 'wsdl');
$err = $client->getError();
if ($err) {
// Display the error
echo '<h2>Constructor error</h2>' . $err;
// At this point, you know the call that follows will fail
exit();
}
// Call the hello method
$result1 = $client->call('echoStringArray', ['inputStringArray' => ['Hello', 'World', '!']]);
print_r($result1);
As you can see there is absolutely no custom handling of the message body, XML, headers and so on. All this is being taken care for by NuSOAP itself.
You just provide an array under the key inputStringArray in $client->call() and get the same array on the server side as parameter named inputStringArray of the method handler echoStringArray.
And last but not least, you may try something more recent than nuSOAP, e.g. zend-soap. It seems to be simpler, check out this short tutorial https://odan.github.io/2017/11/20/implementing-a-soap-api-with-php-7.html
YES! now it works. Ur last remark was the point: "SOAP Server must be initialized on each request". Tought this server initialisation was only use for creating the wsdl. The other difficulty i had was how to call my function. If the function is in the same class it'll not get call (probably due to some autoload issues), i had to make another class with the function to get things working.
Here is my whole solution.
in ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include']['update_order'] = Vendor\DocBasics\Controller\EventsController::class . '::processRequest';
My class EventsController
<?php
declare(strict_types=1);
namespace Vendor\DocBasics\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
require_once(PATH_site . 'typo3conf/ext/doc_basics/Classes/Libs/nusoap/nusoap.php');
require_once(PATH_site . 'typo3conf/ext/doc_basics/Classes/Libs/Utility.php');
class EventsController
{
protected $objectManager;
/**
* #var array
*/
protected $responseArray = [
'hasErrors' => false,
'message' => 'Nothing to declare'
];
/**
* #param ServerRequestInterface $request
* #param ResponseInterface $response
* #return ResponseInterface
*/
public function processRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{
$server = new \soap_server();
$server->soap_defencoding='utf-8';
$server->configureWSDL("updateorderservice", "https://domain.tld/updateorderservice", "https://domain.tld/index.php?eID=update_order");
$server->register(
"Utility.updateOrder",
array("Vbeln" => 'xsd:string', "Zaehl" => 'xsd:integer'),
array("return" => 'xsd:string'),
"https://domain.tld/updateorderservice",
"update",
"rpc",
"encoded",
"Update a given order"
);
$this->prepareResponse($response);
return $response;
}
/**
* #param ResponseInterface $response
* #param String $action
* #return void
*/
protected function prepareResponse(ResponseInterface &$response)
{
$response = $response->withHeader('Content-Type', 'text/xml; charset=utf-8');
$response->getBody()->write($this->responseArray['message']);
}
}
And my class Utility
class Utility
{
public function updateOrder($Vbeln,$Zaehl)
{
//do ur stuff
return "Order ".$Vbeln." done";
}
}
U can call ur wsdl with https://domain.tld/index.php?eID=update_order&wsdl
Thanks again Artur for helping me solving this. Dziekuje ;-)

Yii2 Event not run after login

I created a Behavior which contains function. This function should be afterLogon of User (yii/web/User::EVENT_AFTER_LOGIN).
But this function never will be triggered unfortunatelly.
I have a Behaviour class for the user model:
class UserBehavior extends Behavior
{
/**
* #inheritdoc
* #param \yii\base\Component $owner
*/
public function attach($owner)
{
parent::attach($owner);
$owner->on(\yii\web\User::EVENT_AFTER_LOGIN, [$this, 'updateLoginInformation']);
}
/**
* Update login information data:
* - login ip address
* - login time
*/
public function updateLoginInformation()
{
/** #var \common\models\User $owner */
$owner = $this->owner;
$owner->logged_in_ip = Yii::$app->request->getUserIP();
$owner->logged_in_at = time();
$owner->save();
}
}
I declared the events and the attach too.
But this events never be run after login...
I attached this behavior to the user model:
/**
* #inheritdoc
*/
public function behaviors()
{
return [
TimestampBehavior::className(),
UserBehavior::className()
];
}
If I know well the the EVENT_AFTER_LOGIN will be triggered automatically by the Yii framework, this is the reason why I do not trigger it again.
And I do not where is the problem, because the updageLoginInformatin never called.
I usually use any logic I want in a model inside the proper action that calls it (IE: actionLogin). But I like your approach.
I just made a test here and the correct way to call the event is something like this:
$user = \Yii::$app->user;
$user->on($user::EVENT_AFTER_LOGIN, [$this, 'updateLoginInformation']);
I didn't create a behavior class, I just added this lines in my init(), but the logic is probably the same as yours.

How to change the redirect url when logging out?

I'm working with Laravel 5 authentification system provided by default.
After logging out, a user is redirected to the root page but I'd like to change that.
I managed to do it for the "login" and "registering" process by defining "$redirectTo" in "AuthController.php". But for "logout", I defined "$redirectAfterLogout" at the same place but it seems to not be taken into account.
Could anyone explain me where is the problem and how to fix it please?
Thanks a lot.
For Laravel 5,
Open AuthController class : app/Http/Controllers/Auth/AuthController.php
Add below property to the class
protected $redirectAfterLogout = 'auth/login';
you can change auth/login with any url.
The redirect after logout is hard coded in the trait AuthenticatesAndRegistersUsers. You can override it in your AuthController by adding this:
public function getLogout()
{
$this->auth->logout();
return redirect('logout');
}
If you don't provide the $redirectAfterLogout attribute, it will use the default which is '/'.
This logic can be found in this class: \vendor\laravel\framework\src\Illuminate\Foundation\Auth\AuthenticatesUsers.php
public function logout()
{
Auth::guard($this->getGuard())->logout();
return redirect(property_exists($this, 'redirectAfterLogout') ? $this->redirectAfterLogout : '/');
}
Having said that, just add this attribute in your AuthController:
protected $redirectAfterLogout = '/afterRedirectURL';
For Laravel 5.5 override logout method inside LoginController. In my case I am redirecting to home route after login.
/**
* Log the user out of the application.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function logout(Request $request)
{
$this->guard()->logout();
$request->session()->invalidate();
return redirect()->route('home');
}
using the built in laravel Auth in the controllers I just override the loggedOut method which triggers after logout to redirect
in the "LoginController.php" which uses
use AuthenticatesUsers;
in the AuthenticatesUsers Trait is a logout method, you can optionally override this or you will see that it triggers a loggedOut method
You can override the logged out method which is default blank and have that redirect
/**
* The user has logged out of the application.
*
* #param \Illuminate\Http\Request $request
* #return mixed
*/
protected function loggedOut()
{
return redirect()->route('login.show');
}
In App\Controllers\Auth\AuthController, add the following two variables.
protected $redirectTo = '/private_dashboard';
protected $redirectAfterLogout = '/public_homepage';
You get the idea.
I have a same problem in Laravel 5.0. Override a method does the trick.
1) Go to app/Http/Controllers/Auth/AuthController.php
2) Add a new method :
// Override Logout method (define custom url)
public function getLogout()
{
$this->auth->logout();
return redirect('auth/login'); // Your Custom URL
}
it'only laravel versi 5.4 if you want custom redirect url logout,
open /your-project-laravel/vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php
and edit redirect based on you needed
public function logout(Request $request)
{
$this->guard()->logout();
$request->session()->flush();
$request->session()->regenerate();
return redirect('/login');
}

Doctrine2 ArrayCollection

Ok, I have a User entity as follows
<?php
class User
{
/**
* #var integer
* #Id
* #Column(type="integer")
* #GeneratedValue
*/
protected $id;
/**
* #var \Application\Entity\Url[]
* #OneToMany(targetEntity="Url", mappedBy="user", cascade={"persist", "remove"})
*/
protected $urls;
public function __construct()
{
$this->urls = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addUrl($url)
{
// This is where I have a problem
}
}
Now, what I want to do is check if the User has already the $url in the $urls ArrayCollection before persisting the $url.
Now some of the examples I found says we should do something like
if (!$this->getUrls()->contains($url)) {
// add url
}
but this doesn't work as this compares the element values. As the $url doesn't have id value yet, this will always fail and $url will be dublicated.
So I'd really appreciate if someone could explain how I can add an element to the ArrayCollection without persisting it and avoiding the duplication?
Edit
I have managed to achive this via
$p = function ($key, $element) use ($url)
{
if ($element->getUrlHash() == $url->getUrlHash()) {
return true;
} else {
return false;
}
};
But doesn't this still load all urls and then performs the check? I don't think this is efficient as there might be thousands of urls per user.
This is not yet possible in a "domain driven" way, ie. just using objects. You should execute a query to check for the existance:
SELECT count(u.id) FROM User u WHERE ?1 IN u.urls AND u.id = ?2
With Doctrine 2.1 this will be possible using a combination of two new features:
Extra Lazy Collections
#IndexBy for collections, so you would define #OneToMany(targetEntity="Url", indexBy="location")
ExtraLazy Collection Support for index by using ->contains().
Points 1 and 2 are already implemented in Doctrine 2 master, but 3 is still missing.
You should try using the exists method on the collection and manually compare values.