Overriding 404 header in extended ErrorPage[_Controller] with SilverStripe 3.1 - silverstripe

I have an "intelligent error page" class that extends ErrorPage and ErrorPage_Controller, Basically what it does it a) detect if it's a 404, then b) tried to locate a potential redirect page based on some custom search logic. If the page is found elsewhere, the user is redirected automatically to that location. I know SilverStripe has a basic version of this already based on renamed / moved SiteTree elements, however this is more advanced.
Anyway, since 3.1, it seems impossible to override the 404 header sent (although this worked fine for 3.0).
class IntelligentErrorPage_Controller extends ErrorPage_Controller {
public function init() {
parent::init();
$errorcode = $this->failover->ErrorCode ? $this->failover->ErrorCode : 404;
if ($errorcode == 404) {
... some search logic ...
if ($RedirectSiteTreePage)
return $this->redirect($RedirectSiteTreePage->Link());
}
}
}
As of 3.1 the above returns both a "HTTP/1.1 404 Not Found" as well as the "Location: [url]" header - however it seems impossible to override the 404 status.
Any idea how I can restore the intended "HTTP/1.1 302 Found" header?
PS: I've tried $this->getResponse()->setStatusCode(302) etc with no luck either.

The init() function is called by ModelAsController, and as this class is not able to find a suitable old page for a random url segment, it rebuilds the http response after you built your own response, and therefore overrides the 302 with a 404. This happens at line 130 of ModelAsController. A way to circumvent that is to change the approach and throw an exception, that will prevent the call to getNestedController. Handily, there's such an exception, called SS_HTTPResponse_Exception.
This snippet works for me (redirects to the contact us page with a 302):
<?php
class IntelligentErrorPage extends ErrorPage {
}
class IntelligentErrorPage_Controller extends ErrorPage_Controller {
public function init() {
parent::init();
$errorcode = $this->failover->ErrorCode ? $this->failover->ErrorCode : 404;
if ($errorcode == 404) {
//... some search logic ...
$response = new SS_HTTPResponse_Exception();
$response->getResponse()->redirect('contact-us');
$this->popCurrent();
throw $response;
}
}
}

Related

Handle Unauthorized Request and Return Status Code 404

I am developing a standalone .Net Core API targeting framework .Net Core 2.2.The authentication scheme is JWTBearerTokens connecting to our ADFS Identify server.
When I call an API endpoing decorated with the [Authorize] attribute I am getting a 401 Unauthorized response, which is expected and default behaviour.
What I want to do next is instead of having that same call return a 401, I would like to return the status code to be 404. (I don't want to get into great details of why 404. Simply, I do not want to expose that the endpoint exists if a valid token is not included in request)
In previous .Net Framework WebAPI you could create your own attribute and override the HandleUnauthorizedRequest method and return the status code you want.
I have reviewed the documentation on policy-based authorization, but have not tried the sample or tried implementing it. The policy handler looks more to do with handling (return success or fail) if a policy is not fulfilled. I do not see anywhere where you can return a different status code on failure. So that only would make sense if I start checking against actual Policies.
Any insights?
Returning 404 instead of 401 is bad practice(as mentioned in the comments by #Chris Pratt) and must be avoided. Consider these cases,
You're leaving the project to someone else and they can't figure why 404 is returned
A 404 is returned when you call the homepage/Index page. Poor ideology.
Later on in the project, you decide to allow post requests without authentication. So on and so forth.
Anyways, as part of the community, I'll give you the answer...
Add this to your global.asax
void Application_EndRequest(object source, System.EventArgs args)
{
if (Response.StatusCode == 401)
{
Response.ClearContent();
Response.RedirectToRoute("ErrorH", (RouteTable.Routes["ErrorH"] as Route).Defaults);
}
}
And in routeConfig, create a route for your errorHandler :
routes.MapRoute(
"ErrorH",
"Error/{action}/{errMsg}",
new { controller = "CustomController", action = "Change401To404", errMsg = UrlParameter.Optional }
);
And in your custom controller :
public class CustomController : Controller //or Base
{
public ActionResult Change401To404(){
//Do whatever you want
}
}
PS: This is not the only way, there are many other ways to do it. But at least in this method, you can differentiate real 404 responses from 401 responses.

SilverStripe 4 : FunctionalTest "get" method returns 404 status although the page is there.

I am trying to test a controller with this Test class,
<?php
use SilverStripe\Dev\FunctionalTest;
class SitePageControllerTest extends FunctionalTest
{
protected static $fixture_file = 'site/tests/fixturesSitePage.yml';
public function testViewSitePage()
{
$obj = $this->objFromFixture('SitePage', 'page1');
$page = $this->get('page-one/');
$this->assertEquals(200, $page->getStatusCode());
}
}
and Fixture.
SitePage:
page1:
Title: Page One
CanViewType: true
But "$this->get('page-one/');" returns a 404 page.
Pages are versioned, and this one isn't published at the point where you ask for it, so the functional test emulates a frontend web request which is served from the live (published) stage by default.
You can use the draft site by appending ?stage=Stage to your request URL, or by using protected static $use_draft_site = true in your functional test (this is deprecated in 4.2).
Note that FunctionalTest doesn't log a user in, so you may also need to log in with some level of permission i.e. $this->logInWithPermission('ADMIN')
Another option is to publish it using something like: $obj->publishRecursive(); before the get()

action method not fired in mvc 5

I have a controller like bellow
public class MenuController : Controller
{
//
// GET: /Menu/
public ActionResult Index()
{
return View();
}
public RedirectResult logout()
{
return RedirectPermanent("http://www.google.com");
}
}
and I set a break-point on logout for first time if I hit URL in address-bar localhost:(port number)/menu/logout code stop at breakpoint as expected but after that each and time code not stop breakpoint.
I spend around 2-3 hours and found some stack overflow link where some body write clear browser cache I do that and its working but only once ,that means I have to clear cache each time before debug .I use fire-fox(50.1.0) .try above code to replicate .please help I am stuck badly .
That's what RedirectPermanent() does. It basically tells the browser, "This resource will never be working again, so always request this redirected URL instead." So the browser remembers that and doesn't bother requesting a resource that it's been told will never work again.
If you want the redirect to be temporary, don't make it permanent:
return Redirect("http://www.google.com");
Because you're calling RedirectPermanent
Your browser is (correctly) caching the fact that visiting /logout permanently redirects to (in this case) Google.
It's performing a 301 redirect.
Use Redirect instead
public RedirectResult logout()
{
return Redirect("http://www.google.com");
}

Symfony redirect to external URL

How can I redirect to an external URL within a symfony action?
I tried this options :
1- return $this->redirect("www.example.com");
Error : No route found for "GET /www.example.com"
2- $this->redirect("www.example.com");
Error : The controller must return a response (null given).
3- $response = new Response();
$response->headers->set("Location","www.example.com");
return $response
No Error but blank page !
Answer to your question is in official Symfony book.
http://symfony.com/doc/current/book/controller.html#redirecting
public function indexAction()
{
return $this->redirect('http://stackoverflow.com');
// return $this->redirect('http://stackoverflow.com', 301); - for changing HTTP status code from 302 Found to 301 Moved Permanently
}
What is the "URL"? Do you have really defined route for this pattern? If not, then not found error is absolutelly correct. If you want to redirect to external site, always use absolute URL format.
You have to use RedirectResponse instead of Response
use Symfony\Component\HttpFoundation\RedirectResponse;
And then:
return new RedirectResponse('http://your.location.com');

ASP.NET: After returning RedirectResult, URL does not update in browser when https is in use

I'm confused about the behavior of RedirectResult - in some cases (with https), the redirect doesn't seem to happen, but something more like a transfer.
If a user tries to access an internal page without being logged in, they are directed to the login page. After logging in, they're directed back to my app, with query string parameter
schema://host:port/myApp?returnUrl=Inspections.mvc/Edit/43523
The code in the HomeController that handles this looks for the redirectUrl, and does this:
if (returnUrl != null)
{
return Redirect(returnUrl);
}
In my dev environment and one QA environment, I see that a redirect response goes back to the browser, which makes another request, as expected.
But in production and another QA environment (both of which use https), the last redirect doesn't happen. The browser continues to show the url
schema://host:port/myApp?returnUrl=Inspections.mvc/Edit/43523
and displays the content that would be returned by the page Inspections.mvc/Edit/43523.
I'm perplexed - is this expected behavior when RedirectResult is used? Is https the relevant difference?
EDIT: Checking traffic, I see that in the environments using https there IS a redirect (301- moved permanently), but it is back to exactly the same address:
schema://host:port/myApp?returnUrl=Inspections.mvc/Edit/43523
This additional information leaves the mystery as puzzling as ever.
Looking at the source code of RedirectResult class you can see that it should do either a 302 or 301 depending on the kind of redirect you want:
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (context.IsChildAction)
{
throw new InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction);
}
string destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);
context.Controller.TempData.Keep();
if (Permanent)
{
context.HttpContext.Response.RedirectPermanent(destinationUrl, endResponse: false);
}
else
{
context.HttpContext.Response.Redirect(destinationUrl, endResponse: false);
}
}
It should be working as expected no matter what schema you are using. Did you look at the actual request/response with a http sniffer such as Fiddler?
Maybe your browser is choosing not to update the URL for some reason and the problem is not in the actual redirect/rewrite.

Resources