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...
Related
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.
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 would like to know if there is a way to prevent the use of userscripts on my browser game site?
Many use Greasemonkey to have advantages over other players, and I would like a way to disable these scripts.
I found this old article, "How To Disable Greasemonkey On Your Web Site", but it's from 2005 and doesn't seem to work.
Combating a smart scripter is tough. They have the upper hand since their script can touch the page before your server does, and can block or replace just about anything. See this answer to a very similar question.
Your smartest, and most cost-effective countermeasure is to sanction the users who are "gaming" the game. Attack the burglar, not the lock-pick.
If you insist in a tech war with your users, nothing you do will block everybody, but you can make them work for it.
Here are some things you can do make life harder for scripters:
Frequently change the structure of the page, especially element ID's and CSS class names. If you can, periodically insert or remove elements, so that the key <div> is not always the 3rd one in the second <table>, for example.
Every time you make a change, monitor your logs for users who get a sudden decrease in performance or usage -- for however many hours or days it takes them to adapt their scripts.
Likewise, frequently change your javascript filenames, and change the names of any variables or functions that the scripter may use.
Write your click and keyboard event-handlers to only work for trusted events, for browsers that support it.
You can put key text, including countdown timers, in images with unpredictable names. Making it hard for the script to detect key events. Needing to do OCR ramps up the skill-level required by a Greasemonkey scriptwriter, considerably. (At least for now.)
If you move the key game action into Flash, it becomes an order of magnitude harder to script for. They may even have to reverse engineer your flash and replace it with one that has scriptable hooks. Switching to Flash will annoy and drive off users (like me), though.
See that answer for more but, again, the best and most cost-effective approach is to sanction the offending user(s). Be sure that your Terms of Service specifically forbids what they are doing, though.
As addition to Brock Adams' own answer, here's couple methods for finding a possible scripter.
Timed function that checks DOM tree and search for added elements that are not your code's creation, or look for missing elements.
Primarily finds scripter who alters UI, yet haven't read/understood the game's js-source.
Client-side.
"Missing element"-search may get false positives from people who use something like AdBlock Plus. Not really false positive, if aim is to rank them out, too.
Inspect cookie content and look for hints of user added content.
If scripter has to transfer information from page/session to another, and has/knows no other method, he may attempt to use cookies for this.
Inspect query/hash in URL for content not added by your code.
It's possible to try to transfer information to other pages by altering links.
Hash-content (# in URL) is accessible only client-side.
Inspect session/localStorage.
Client-side.
Disable access thru anonymizing services, like anonym.to.
Circumventable, but makes life harder for people using unwanted online-tools.
Allow access to game-page only if referer is correct, otherwise redirect to login-page.
Another method to limit access to game-pages from outside sources.
If you want to be a pain, kill active session when redirecting.
NOTE: All client-side functions can be circumvented by scripter who understands the code.
NOTE: Usage of these requires some wisdom and good planning. If doing things wrong, then with client-side stuff you risk of kicking users' browsers on knees or DDoSing your own server. Or you may end up banning least half of the userbase after an update on your own code, if you use too much automation.
Here's one of my scripts. It could definitely still use some work, but the framework is there (though you may need to wrap everything in a big function to make variables private)
var secureElements,secureTags,secureTagLoop,secureLoop,var secureReporter = secureAnalyzationFunction = 0;
function analyze(secureAnalyzation){
if(secureAnalyzation.indexOf("function ")!=-1){
secureAnalyzationFunction = secureAnalyzation.substring(secureAnalyzation.indexOf("function ")+9,secureAnalyzation.indexOf("()"));
secureAnalyzationFunction = secureAnalyzationFunction+"=undefined;";
eval(secureAnalyzationFunction);
}
}
function secure(){
var secureTags = ["script","link","meta","canvas"];
for(secureTagLoop=0;secureTagLoop!=secureTags.length;secureTagLoop++){
secureElements = document.getElementsByTagName(secureTags[secureTagLoop]);
for(secureLoop=0;secureLoop!=secureElements.length;secureLoop++){
if(secureElements[secureLoop].outerHTML.indexOf("verified")==-1){
analyze(secureElements[secureLoop].outerHTML);
secureElements[secureLoop].parentElement.removeChild(secureElements[secureLoop]);
secureLoop--;
secureReporter++;
console.log("Deleted "+secureReporter+" foreign elements.")
}
}
}
}
window.onload = function() {
secure();
setInterval(secure,1500);
};
I am trying to set up a MySite allowing individuals to create and modify multiple projects, each which will contain a partial copy of a master Document Library. I have already created the Library and uploaded the documents, and I have a page on which I have been experimenting with how to do this.
I need to be able to display a list of the documents in the document library, allow the user to select which ones they want, and then when they click on a button, I need to initialize the database with some information about that prjoect, some information about each of the files, and the documents need to be copied to a new location, so that the user can pull them up and edit and save the changes that are specific to the one project. Also we will need to be able to be able to add/remove them through a similar interface after the project has already been created, if possible.
And, unfortunately, anything at all that uses code has been restricted; so, we can't use anything that will require anything to be installed in the GAC, and I can't even use any server-side scripts within the ASPX file. It seems like almost everything I find when I search for "Sharepoint 2007 clone Master Document Library" or the like involves using code.
I have been playing around with things in SharePoint designer for creating the UI, and I figured out how to get a list of the documents to show, although I'm having a hard time duplicating it. It created a <WebPartPages:DataFormWebPart> with a <SharePoint:SPDataSource> in it. However, when I've tried to pull over things like a Data View CheckBoxList to the document list, either it just won't let me drop it (this includes anywhere on the form, not just in the document list), or, as expected actually, it doesn't actually bind to each line of the list. It's not entirely clear to me what to do to create this.
I do get (more or less) that you can bind the controls via the .xslt in the form, but I'm figuring it's got to be simpler than writing them by hand since the designer looks so much like the Visual Studio designer. I am having a very hard time figuring out what exactly to do to get any of the controls to work, though; most of them have the same issue as the CheckBoxList and won't even drop on the form. I'm assuming there's a panel of some type, or some other thing, that I need to add to the form first, but this is so different from what I'm used to that I'm having a hard time even getting my bearings.
So, can someone point me in the right direction here? I'm going to need to be able to create the projects, select the documents, copy them over with some other information into a new project, including creating some items in the database. I have been developing software for a long time, on many different platforms with many different languages, although lately it's mainly been WinForms, with a fair bit of .aspx stuff (but nothing too fancy), and it's never been this hard. I figure there's just something I'm not getting about the model here; like, how do you tell the form you want to use x/y/z control connected to particular datasource? How do you tell sharepoint to create a new location for the files? How can you submit the selected files once you have them to Sharepoint and tell them where to copy to?
Any help would be so appreciated I have been tearing my hair out for days. :)
You might have had a change with SharePoint 2010, as there is a javascript/jQuery programming model, but without that you don't have a realistic of coding what you will need. You may get lucky and be able to call the SharePoint web services using jQuery, but it is going to be a lot of work to get around a "no code" requirement.
Okay, well, I was finally able to get this mostly working, although there are still some issues with displaying the data once I get it back; I will try to update this question once I have a better answer for that.
There's a cool library called SPServices which combines with JQuery to give an interface to the web services for SharePoint 2007. It seems to more or less provide an interface to anything that you need to do to use the web services without a lot of extra work on your part.
For example, to query a document library, I set up a function something like this in a helper class (based upon the examples from the SPServices documentation).
ServiceClass.prototype.getSpecialDocLib = function (onComplete) {
var sc = this;
$().SPServices({
operation: "GetListItems",
async: false,
listName: "Special Document Library",
CAMLViewFields: "<ViewFields><FieldRef Name='Title' /></ViewFields>",
completefunc: function (xData, Status) {
var outTable = new DocTable();
$(xData.responseXML).find("[nodeName='z:row']").each(function () {
var i;
var outRow = new DocTableRow();
for (i = 0; i < this.attributes.length; ++i) {
outRow.addField(this.attributes[i].localName, this.attributes[i].value);
};
outTable.addRow(outRow);
});
sc.onDocumentListComplete(xData, Status);
if (onComplete != null)
onComplete(resultTable);
}
});
};
For copying a document library from one place to another, I believe you can use Lists.AddList to create it, (not specifically documented in SPServices but is based upon this Web Service call) either from scratch or a template, and I believe you can use the SPServices Copy.CopyIntoItemsLocal method.
Anyway, I seem to be making progress with this so far; I will update and accept this answer once I have a little more experience and time to make sure everything works, but so far I seem to be making progress toward the "no code" goal.
I need to rename the content type I created a couple months back and
have been using for a document library, assigned site columns to, created
document library views for and have added workflow to.
It appears that this is quite simple to do under the site settings for content type. However it is often the simple, apparently straight forward things that cause unforeseen
issues.
Does anyone know of any gotchas when it comes to renaming a content type?
Here's some guidelines, the main statement though is:
The general guideline for evolving the content type IA is to never change or rename content types or their aspects, make new ones and hide the old ones.
You can set the SPContentType.Name property and then call SPContentType.Update(). Considering the guidelines linked to in Colin's answer you should test this in a non-production environment first.