What's the appropriate way to make a Servant handler respond with a redirection? I am working in a navigation REST app and I would like to respond to POST requests that create resources with a redirection to the corresponding GET resource list paths. So for instance POST /foos should redirect to GET /foos after creating a foo. I could not find a clear way to do that in the documentation.
There is one simple (but slightly hacky) answer, and a lead for making the first option obsolete (EDIT: and a third, better option, actually).
The current typical solution for this is to simply use the fact that the Handler monad has a MonadError ServantErr instance, and that ServantErr is a very general "response type" that can indeed describe the HTTP response for an application error, but also a redirect or many other things really. So you can do something like throwError $ err301 { errHeaders = [("Location", "https://haskell.org/")] }. It's ugly because we hijack the "erroring out" bit for some successful workflow. But it works, and is a single line of code.
I have explored alternative approaches, that let you mix type-safe links with redirects to easily redirect to other endpoints/pages of your app. This is outdated now but could probably be made to work without toooo much trouble.
And upon seeing this question, I just now thought of a third option. Took a bit of time to experiment with it and looks like it works! You can see the code with an example of using it in this gist. Let me know if you have any question. We might want to add some of this to servant. Feel free to bring it up on the issue tracker if you think this is good enough. The gist of this approach is to define a custom PostRedirect that has the right shape (no response body, a Location header with a type of your choice, and parametrized by the status code that you want your redirect to use), and a little function that wraps the location appropriately before returning.
Related
completely rewritten to include improved understanding
The Yesod typeclass contains the function isAuthorized which you can adapt so that different routes are only accessible for different user groups.
The scaffolded site shows examples of how to do that, including making the authorization subsite available to everyone:
isAuthorized (AuthR _) _ = return Authorized
The scaffolded site also helpfully includes a subsite for static content. But: that static subsite does not honor what you do in isAuthorized. You can check that by adding a pattern match like
isAuthorized (StaticR _) _ = error "this error is never reached"
You can still access all static content (including newly created one) and you will never encounter this pattern match.
It does make a bit of sense to give everyone access to content like bootstrap or jquery. Still, the same result could be achieved by honouring isAuthorized and always returning Authorized, the same way it's done with the authorization subsite or the favicon handler.
I personally would like to go one step further with dispatch like
isAuthorized (StaticR (StaticRoute ("public":_) _)) _ = pure Authorized
isAuthorized (StaticR (StaticRoute ("admin" :_) _)) _ = checkIsAdmin
isAuthorized (StaticR (StaticRoute ("cats" :_) _)) False = checkIsAllowedToViewCats
:
It seems the only missing bit is to make the static subsite honour the check or to add a shim that does.
Sadly the subsite is a huge amount of complicated code with template haskell and a lot of magic doing complicated things like embedding files in the executable. The way it's included in the scaffolding has yet more magic. I'm also just learning about subsites and my training to see types as documentation fails in contexts like type families or Q Def. For these reasons I couldn't figure out how to add the check. Any pointers would be appreciated.
So I found an imperfect answer that's good enough for my purposes for now.
I couldn't figure out how to make the static site behave differently. It seems it somehow uses the underlying capabilities of the wai server, so it never even touches the Yesod part of it. And as subsites seem to do the dispatch before the core system does, that was the only place where I could have changed something. Strange choice to have a core system be last in line, but whatever, there's probably a reason for this.
The solution, then, is to duplicate what the static subsite does. But with as little work as possible, please. So here's the most basic handler you can create:
getStaticCatContentHtmlR :: Text -> Handler Value
getStaticCatContentHtmlR path = do
let filePath = "static/cats/html" </> unpack path
sendFile "text/html" filePath
Just point your static subsite at static/public instead, create three new subfolders for html, css and js per subsite, add three corresponding handlers with suitable permissions and be done with it. The routing system makes sure that users can't request a path like /static/cats/html/../../../.
There are several drawbacks, though.
Yesod cannot do any optimizations.
Errors from missing files or wrong routes do not crash the server, but the error response is horrible. Of course you could catch the error yourself.
You don't get any routing tools inside Yesod, so you must do it manually and unchecked.
All routes in this subsite should be relative so you get less errors when you move the server from development to deployment. That might not be a drawback, but it limits your choices.
Is it worth the effort to create a subsite to bundle these three handlers? Well, maybe as a training exercise...
Most times I've seen urls written to open an XPage in read-mode using action=openDocument, but occasionally, I've see action=readDocument used. Just curious if there are pros/cons in using one vs. the other.
I don't know of a difference. Honestly I never use these URL's anymore myself. So it's really not a big deal very likely.
Keep in mind, you don't need to use these at all of you don't want. You can pass your own parameter in the URL and then via SSJS access any parameters with the param object. You can also get the parameters in Java easily enough.
Just a thought.
Hey guys! Working on a new Cake app and wondering if there is anyway for me to remove the ID-in-URL routing from Cake. Perhaps by passing the ID in POST somehow? Having the ID passed in as a URL param just seems really shoddy and unsafe. Thanks!
"Shoddy"? It's standard practice and a perfectly fine solution to have ids in the URL. Look at the URL of your question:
http://stackoverflow.com/questions/4638262/removing-id-from-cakephp-url
^^^^^^^
id
Also, there's absolutely nothing unsafe about showing an id in a URL. It's just a number that doesn't mean anything. If a user can do something "bad" only by knowing this id, your app is broken and insecure, not the id-passing mechanism.
Trying to work around this scheme means working around the fundamental principle of the HTML protocol and opens up a whole new can of worms.
Some people prefer using slugs instead of primary key ids. This is the removing-id-from-cakephp-url part of the URL from this page. Take a look at the SluggableBehavior.
However, slugs can change. Hence, having the primary key in your URL is useful if you want to have a permalink. StackOverflow does both so that it can support both permalinking from other sites, as well as for SEO reasons. :)
Regarding security issues, I guess the other answers have already pointed out that there are other ways to make your application secure.
Why do you care? URL-s are optimized for SEO reasons, an ID won't matter if it's ain't too long. If the latter, consider using a shorter one with numbers and letters in them instead, it will be as difficult to guess as a long one with just numbers.
If you are not using GET and you do not supply the params in the URL, your users won't be able to copy-paste the location.
I've been on a mission to learn everything about Yesod, and I'm (somewhat) stuck on the routing system and it's relation to subsites and cross-route linking in general. The first thing I would like to address is the "ResourceR" pattern found throughout the route definitions and Hamlet links.
I notice that the "type" itself (ResourceR) is never addressed or referenced outside of Yesod's TH DSL's. Does this mean that it's only really used as a dummy type, meant only for leveraging Haskell's type safety in referencing Yesod links? I also notice that the functions getResourceR, postResourceR etc. are vital for the app to work, yet it's not explicit where their definitions are used in boilerplate app code. Does Yesod simply reduce the calls to #{ResourceR} to the appropriate function?
I keep feeling like I should be defining ResourceR myself as a datatype, when in fact it's generated and reduced internally by Yesod.
So my question is: do the "resource types" referenced in Hamlet and Route code get automatically generated and reduced by Yesod's DSL's?
Thank you in advance!
Yesod indeed expands what it sees in the the routes file (or what you type in the parseRoutes function in a quasiquotes section), and gives the server the appropriately named get or post function (by prefixing "get" or "post" to the resource name). All that you need to do is create the get/post function, and the framework will use the routes to call the function for you. You need only specify the path and request type.
Centralizing all path information in one location like this makes it easy for you to debug where an individual request will go (think of what the alternative spaghetti code would look like). The forced name standards also keep your code understandable, and code generation removes some of the repetition (ie- it helps you adhere to the dry principle).
I've been rewriting my (fairly simple) website using Yesod as a way to get familiar with the framework. Part of that involves serving some simple static (but formatted) content. To do that I decided to use the nicHtml field that is described in the Yesod book:
http://www.yesodweb.com/book/forms
It allows simple formatting and, as the book says, "thanks to the xss-sanitize package, all user input is validated and ensured to not have XSS attacks."
However, all is not well. Some formatting seems to work when you enter it into the field, but gets wiped out somewhere between entry and submission. In particular, the form uses css embedded in 'style' attributes to do things like center text, and it is these css based formatting elements that seem to get wiped out.
I used print statements to check that it wasn't my code which was somehow messing it up. Since it doesn't seem to be, I assume that xss-sanitize doesn't like any embedded css and removes it. Modifying Yesod.Form.Nic to remove the call to sanitizeBalance appears to fix the problem, so that would seem to be the cause.
Now, I can just leave it like that, since editing these static pages requires being a trusted user anyway (i.e. me at the moment), so I don't care too much about validating out nastiness. But it feels like what it is, a hack, so my question is - is there any other way around this? Or is there another package I don't know about that provides a non-broken HTML editor field for Yesod?
Will you file a bug on the Yesod issue tracker for this? I think we are going to have to allow basic css through the editor no matter which editor we use. In your case of a trusted user, right now you could find the NicEdit field type and create a similar type that won't get filtered at all. Perhaps we should create such a field.
We're actually looking at other possible rich text editors right now for use in the Yesod website, so most likely whatever we use there will end up with a module in yesod-form. Most recently Greg pointed out Aloha editor which on first glance looks pretty cool.