Before user tries an unauthorized action I save:
1) Controller Name
2) Action
3) POST params
Then when user has a successful login I redirect it to...
$params["controller"] = "manage";
$params["action"] = $lastRequest["action"];
$params["name"] = "Ignacio";
$params["email"] = "ignacio#gmail.com";
return $this->redirect()->toRoute("user-create", $params);
It does redirect, but NO posted params.
How can I emulate POST request on ZF2 from a controller?
The point is I do not know where the user it is going to be redirected, so it could be GET or POST and any controller.
This is the way I save a request and use it later to redirect to the correct action.
1) Unauthorized action saves the request with all GET/POST params.
$session = new Container('base');
$session->offsetSet("lastRequest", $event->getRequest());
2) After success login, redirect to requested
$session = new Container('base');
if($lastRequest = $session->offsetGet("lastRequest")) {
//Just redirect, because I could NOT find a way to POST params
return $this->redirect()->toUrl($lastRequest->getRequestUri());
}
3) Before controller action, retrieve all POST/GET params
class Module {
//...
public function init($moduleManager)
{
$sharedEvents = $moduleManager->getEventManager()->getSharedManager();
$sharedEvents->attach(__NAMESPACE__, \Zend\Mvc\MvcEvent::EVENT_DISPATCH, array($this, 'preDispatch'), 100);
}
public function preDispatch($event)
{
//Unauthorized request after success login
$session = new Container('base');
if($lastRequest = $session->offsetGet("lastRequest")) {
$event->getTarget()->getRequest()->setMethod($lastRequest->getMethod());
$event->getTarget()->getRequest()->setPost($lastRequest->getPost());
$event->getTarget()->getRequest()->setQuery($lastRequest->getQuery());
//Delete request
$session->offsetSet("lastRequest", null);
}
}
4) Just use the request on any destination action as normal
class ManageController extends AbstractActionController {
public function createAction() {
if ($this->getRequest()->isPost()) {
$post = $this->getRequest()->getPost()->toArray();
}
}
You can't redirect user with POST data, but ZF2 provide functionality to simulate this:
http://framework.zend.com/manual/2.0/en/modules/zend.mvc.plugins.html#the-post-redirect-get-plugin
This solution might help but instead of using redirect, it forwards to another controller.
Before forwarding it to the other controller, replace (or update) the post params on the current request. The receiving controller then will see the replaced (or updated) post params.
// Controller that handles unauthorized access
class YourController extends AbstractActionController
{
// ...
$request = $this->getEvent()->getRequest();
$postParam = new \Zend\Stdlib\Parameters();
$postParam->set('controller', 'manage');
$postParam->set('action', $lastRequest['action']);
$postParam->set('name', 'your-name';
$postParam->set('email', 'your-email';
$request->setPost($postParam);
return $this->forward()->dispatch('Your\Other\UserCreateController', [
'action' => 'userCreate',
]);
// ...
}
Related
Laravel 5.1
I'm trying to build this functionality for every method in my controller. And this is very unpractical to do and very difficult to maintain. How can I set this that is when I registered an auth middleware in a specific route then it will redirect into a login page together with the URL that trying to view/visit.
Example:
URL: public/users
If this URL will try to visit by an unauthenticated user, then the user will be redirected to a url like public/login?url=http://localhost/myproject/public/users
Then After the user loggedin successfully, the user then will be redirected into http://localhost/myproject/public/users
What I have now: (But I think not a good one to use)
public function getLoginURL(){
if(!Input::get('location-login')){
// URL is not set, redirect user to home.
return redirect('/');
}
if(auth()->check()){
// User is logged in. Go the specified URL (depends on the middleware of the url (route))
return redirect()->to(Input::get('location-login'));
}
if(!auth()->check()){
// Redirect user to a login with the URL provided
return view('pages::auth.login.url');
}
}
You can use the next method in middleware... no need to send the url
public function handle($request, Closure $next)
{
if (Auth::user()) {
return $next($request);
}
return redirect('/login');
}
Use this in your AuthController
namespace App\Http\Controllers;
use Auth;
use Illuminate\Routing\Controller;
class AuthController extends Controller
{
/**
* Handle an authentication attempt.
*
* #return Response
*/
public function authenticate()
{
if (Auth::attempt(['email' => $email, 'password' => $password])) {
// Authentication passed...
return redirect()->intended('dashboard');
}
}
}
intended() function will redirect you to your intended location.
Check the full detail here Intended redirect
I am new to Angular 2 and would like to know how to use it in a mvc project when calling get and post actions:
Let say I have an EmployeeController with 2 actions:
1) GetEmployee: which list employee info
2) UpdateEmployee: which update the employee info
Without using Angular 2, we can easily use views to deal with it. However, if I want to use Angular 2, how can I do this? Could you please provide some samples so I can learn how to use Angular 2 to call actions to get or post data?
Thanks
As promised :
From the Controller side : (controller is named APIConnexionController.cs)
[HttpGet]
public IActionResult Connexion(string aLogin, string aMdp)
{
// Do your stuff
}
You can then setup your method from an Angular2 service or component to do an http call to your controller as follow :
auth.service.ts (in my case)
private controllerURL: string = "/APIConnexion/auth";
login(aLogin: string, aMdp: string) {
// Setup parameters to send to ASP controller
let params = new URLSearchParams();
params.set("aLogin", aLogin); // => Left side must match Controller method parameter
params.set("aMdp", aMdp);
// Setup the http request
let lHttpRequestBody = params.toString();
let lControllerAction: string = "/connexion/?";
let lControllerFullURL: string = this.controllerURL + lControllerAction;
// Call the ASP.NET controller : APIController
return this.http.get(lControllerFullURL + lHttpRequestBody)
.map((res: any) => {
let data = res.json();
// Manage cases
switch (data.status) {
case "success":
this.isLoggedIn = true;
this.lcLogin = aLogin;
break;
case "error":
this.isLoggedIn = false;
throw new Error("Failure : " + data.message);
}
}
).catch(this.handleError);
}
And then just display the data you have from your Angular2 component to your template HTML, or execute some actions just like my login() method :
// Manage authentication
login(username, password) {
this.authService.login(username, password)
.subscribe(() => {
// Call the service Method
if (this.authService.isLoggedIn) {
// Redirect the user to master component
this.router.navigate(['/master/dashboard']);
}
});
}
I have overridden the built in WebClient as below. Then I call it
public class HttpWebClient : WebClient
{
private Uri _responseUri;
public Uri ResponseUri
{
get { return _responseUri; }
}
protected override WebResponse GetWebResponse(WebRequest request)
{
WebResponse response = base.GetWebResponse(request);
_responseUri = response.ResponseUri;
return response;
}
}
Then I consume it like this:
using (HttpWebClient client = new HttpWebClient())
{
client.Headers[HttpRequestHeader.Authorization] = $"Bearer { _token }";
client.Headers[HttpRequestHeader.ContentType] = "application/json";
client.UploadData(_url, Encoding.UTF8.GetBytes(_data));
string queryString = client.ResponseUri.Query.Split('=').Last();
}
The response uri comes back with "https://login.microsoftonline" rather than url returned from the MVC controller with a query string, as it is authenticating first with that bearer token using AzureAd/OpenId. If i call it twice it returns the original _url but not the redirected one. If I remove AzureAd authentication it works fine. Is there a way to force the response uri to come back as what the MVC controller sets it to?
Assuming you use the 'UseOpenIdConnectAuthentication' and configuring it to use AAD authentication, you can modify the redirect uri by setting Notifications.RedirectToIdentityProvider, something like:
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = async _ =>
{
_.ProtocolMessage.RedirectUri = _.Request.Uri.ToString();
}
}
If you use something else , or maybe I didn't understand your problem - please supply more information
in my ZF2 application I am adding the following event listener, however I want to make the execution of the action actually stop, however this doesnt happen.
public function setEventManager(EventManagerInterface $events)
{
parent::setEventManager($events);
$controller = $this;
$events->attach('dispatch', function ($e) use ($controller) {
$request = $e->getRequest();
$method = $request->getMethod();
$headers = $request->getHeaders();
// If we get here, based on some conditions, the original intended action must return a new JsonViewModel()...
return new JsonViewModel([]); // However, this doesn't do anything.
}, 100); // execute before executing action logic
}
Based on your comments, I am assuming you are doing some sort of authentication. You can perfecty use the event manager for this. However, I would not tie the listener to a single controller. If your API increases, you might want to split the API over several controllers and you get into trouble with your authentication.
My solution is to create a listener which listens to the dispatch event of the Zend\Mvc\Application. This is an event which is triggered before the event in the controllers itself.
use Zend\Mvc\MvcEvent;
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$em = $app->getEventManager();
$sm = $app->getServiceManager()->getSharedManager();
$listener = new Listener\Authentication();
$identifier = 'MyModule\Controller\ApiController';
$em->attach($identifier, MvcEvent::EVENT_DISPATCH, $listener, 1000);
}
This way, the listener is attached to all controllers which are identified with MyModule\Controller\ApiController. The listener will be triggered on every dispatch call of those controllers. Your listener can short-circuit the complete dispatch loop in case you need it:
use Zend\Http\Request as HttpRequest;
use Zend\Mvc\MvcEvent;
use Zend\Json\Json;
use Zend\View\Model\JsonModel;
class Authentication
{
public function __invoke(MvcEvent $e)
{
$request = $e->getRequest();
if (!$request instanceof HttpRequest) {
// Don't run on CLI requests
return;
}
if ($result->isValid()) {
// Say you get auth result and all is OK
return;
}
// Case 1: short-circuit and return response, this is the fast way
// The response I use here is the HTTP problem API
$message = array(
'httpStatus' => 401,
'title' => 'Unauthorized',
'detail' => 'You are unauthorized to perform this request',
);
$response = $e->getResponse();
$response->setStatusCode(401);
$response->getHeaders()->addHeaderLine('Content-Type', 'application/json');
$response->setContent(Json::encode($message);
return $response;
// Case 2: don't short circuit and stop propagation, you're using the JSON renderer
$e->getResponse()->setStatusCode(401);
$message = array(
'httpStatus' => 401,
'title' => 'Unauthorized',
'detail' => 'You are unauthorized to perform this request',
);
$model = new JsonModel($message);
return $model;
}
}
I would advice you to use the first method (returning the response yourself) because you'll short circuit the complete dispatch process and skip the complete finishing of the request. If you really rely on the view and response senders, use the second case.
Now if you need a controller which is authenticated via this system, add the identifier to this controller:
namespace MyModule\Controller;
use Zend\Mvc\Controller\AbstractActionController;
class MyFooBarApiController extends AbstractActionController
{
protected $eventIdentifer = 'MyModule\Controller\ApiController';
// your code here
}
If you need to allow certain requests without validation (I would always use a whitelist!), you can do this in your listener:
use Zend\Mvc\Route\RouteMatch;
$routematch = $e->getRouteMatch();
if (!$routematch instance of RouteMatch) {
// It's a 404, don't perform auth
return;
}
$route = $routematch->getMatchedRouteName();
if (
($request->isPost() && 'foo/bar' === $route)
|| ($request->isGet() && 'baz/bat' === $route)
) {
// We allow these requests to pass on without auth
return;
}
In your code you can explicitly check request method and route name. If you need parameters of the route, you can access it with $routematch->getParam('id').
Use the following in your event:
$e->stopPropagation();
I have this api controller action that takes an object "ContentList" as parameter.
[HttpPost]
public List<string> SendList(string param, ContentList list)
{
List<string> testReturn = new List<string> { "test1", "test2", "test3", "test4" };
return testReturn ;
}
What I have tried so far is to call a controller action like this:
Uri _uri = new Uri("http://localhost:xxxxx/api/FakeTest/SendList?param=test");
var serializer = new JavaScriptSerializer();
string requestData = serializer.Serialize(new
{
list = ContentList,
});
using (var client = new WebClient())
{
client.Headers[HttpRequestHeader.ContentType] = "application/json";
var result = client.UploadData(_uri, Encoding.UTF8.GetBytes(requestData));
var tempString = Encoding.UTF8.GetString(result);
}
In this example, tempString = ["test1","test2","test3","test4"] as reurned by the controller action..
In the controller action, I can access the properties of the passed in ContentList, and return their values (changing the actions return value accordingly ofcourse).
However, in the controller action, I need to send off the ContentList object for further processing, and this seems to fail. I get a 500 internal server error, and I can't put a breakpoint in the controller to follow the values passed in. The debugger never hits it...
I expect this has something to do with sending json to the controller action.
Anyway, it seems that the ContentList is rejected by the code it is sent to from the controller action, so I figure I need to do some sort of de-serializing, right?
Bottomline, the question is, what is the correct way to call a controller action from code, pass in a C# object, and make it usable from the controller action?
If you are using MVC 3 your controller should be able to reveive and parse json data in a direct way. If you are using MVC 2 you'll need to register a new factory on your application to take care of json parsing on the controller
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ValueProviderFactories.Factories.Add(new JsonValueProviderFactory());
}
More info on the subject here:
http://haacked.com/archive/2010/04/15/sending-json-to-an-asp-net-mvc-action-method-argument.aspx