Create "pretty" user profile URLs in Grails application - grails

Anybody who already have implemented something similar using Grails could tell me please which are the good pratices (if there are any) to create user profile URLs with the format "http://www.myservice.com/username", as in Facebook, Twitter, Linkedin?
I'm trying to implement it through the UrlMappings and appears to me I'll need to break with the code conventions, at least for the Controllers.
So, any suggestions are welcome, thanks.
UPDATE 1
When I mentioned my concern about breaking the code conventions, what I'm saying is that I want to show the user profile using this mapping, but I do have other objects in my application which I would like to access using the default mapping:
"/$controller/$action?/$id?"()
SOLUTION
Thanks to the great contributions I've received here, I've camed up with this solution, which solves my problem.
As was pointed out, to do this kind of mapping, I'll need to control more closely how my requests are handled. That means I'll need to tell to Grails which controllers I don't want to be mapped to the "username" rule.
Since that will be a very tedious task (because I have several controllers), I did this to automate it:
UrlMappings.groovy
static mappings = {
getGrailsApplication().controllerClasses.each{ controllerClass ->
"/${controllerClass.logicalPropertyName}/$action?/$id?"(controller: controllerClass.logicalPropertyName)
}
"/$username/$action?"(controller: "user", action: "profile")
}
...
}
And of course, I'll need to do something similar in my user registration process to avoid usernames to be equal to some controller name.
That's it, thank you all.

Assuming you have a UserController and you are going to map any domain.com/username to the show action of user controller, your url mapping could be something like this :
In my example, name will become a parameter in your params.
for further details refer to here
Hope this helps.
static mappings = {
"/$name"(controller: "user", action: "show")
...
}

Given your requirements, everything after http://yourdomain.com/ can be a username or one of your other controllers, which can have undesired effects depending on which url mapping is defined first (e.g. user controller vs nonuser controller). But most likely the nonuser controller list will be the smaller list, so you should place that first and filter against it, then treat all other url mappings as user mappings.
Here is an example:
static mapping = {
"/$controller/$action?/$id?" {
constraints {
controller inList: ['nonUserController1', 'nonUserController2',...]
}
}
//this should work for /username and /username/whateveraction
"/$username/$action?"(controller: 'user')
}
Some things to note here:
you need to place the non user controller url mapping first, since everything else after http://yourdomain.com/ may be a username - correct?
second you need to place a constraint to catch all the non user controllers, while ignore the user url mappings
also you need to prevent a user from signing up with a username that matches one of your non user controllers

Related

Grails - how to display result page at a unique URL

I have a grails application that takes user input (create page/method), the user then clicks a Save button (save method that executes service) and then the results are displayed (list method) on a page, for example http://localhost:8080/myApp/myclass/save.
The users would like each results run to be saved to a unique URL so they can share it, bookmark it, save it later, whatever. I have NO idea how to go about this and google searches turn up little to nothing.
For example an application run would result in the data being displayed at http://localhost:8080/myApp/myclass/systemname/datetimestring/someuniquedata/
Is this even possible? Any pointers GREATLY appreciated.
EDIT
Here is my urlMappings contents.
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"/" {
controller = "api"
action = "create"
}
"500"(view:'/error')
}
}
When I display the results it's done through the list method shown here.
def list(Integer max) {
List<Api> api = Api.findAllBySessionId(session.id, [sort:'dateCreated'])
api = api[-2..-1]
[apiInstanceList: api, apiInstanceTotal: api.size()]
}
So I have the unique session ID. How do I need to modify "mappings"?
Every domain object that you're saving will have an autogenerated ID (assuming you're using GORM, which is definitely likely). It sounds like all you're asking for is a /show/id page where you can access a particular object via ID.
A url mapping for "/$controller/$action?/$id?" is a pretty straightfoward way to handle this, and is provided by default (and used by scaffolded controllers also).
If you'd rather not use an autogenerated ID (maybe you're moving objects from one database to another, or updating the ID for some reason?) you can consider using java.util.UUID.randomUUID() to generate a random, unique identifier and save that as a field on your object. You could then use .findByUuid with the input parameter.

ASP.Net MVC with complex routes - how to keep it "sane"?

I have a client who wishes to use a URL naming convention along the lines of:
/{subjectarea}/{subject}/{action}
Which is fine - this works brilliantly, with one controller per subject area, and having the action after the id (subject) is no issue at all.
However, it then gets complicated, as the client then wants to further continue the hierarchy:
/{subjectarea}/{subject}/{action}/{tightlyrelatedsubject}/{tightlyrelatedsubjectvariables}/{tightlyrelatedsubjectaction}
I have a controller for the tightly related subject (its just another subject area) which handles all of the admin side, but the client insists on having the public view hung off of the parent subject rather than its own root.
How can I do this while avoiding breaking the entire principals of MVC, and also avoiding re-implementing a ton of ASP.Net MVC provided functionality in my subject area controller just to be able to handle the related subjects from that same controller?
Is it possible to somehow call the related subjects controller from within the parent subject controller, and return the resulting view (as this would keep the separation of functionality for the subjects to their own controllers)? If that is possible, it would solve a heck of a lot of issues with this.
Here is the solution which solves my given issue - hope it solves someone elses.
As mentioned in my comment to Robert Harvey, all I actually need is another route which doesn't use the first two or three components as the controller, action and id, but instead takes those values from later on - if you hang this off of a static value in the route as well, its much easier to do.
So, here is the url I decided on to simplify the route:
/{subjectarea}/{subject}/related/{tightlyrelatedsubject}/{tightlyrelatedsubjectvariables}/{tightlyrelatedsubjectaction}
The route which satisfies this URL is as follows:
routes.MapRoute(
"RelatedSubjects",
"{parentcontroller}/{parentsubject}/related/{controller}/{id}/{action}",
new { controller = "shoes", action = "view", id = "all" }
);
On the subsequent controller action, I can ask for parameter values for parentcontroller and parentsubject so I can filter out the related item to just be specific to the given parent subject - problem solved!
This route needs to be above the ones which just deal with the first two values, otherwise you run the risk of another route map hijacking the request.
I could do this entirely without the /related/ static portion as the route could easily match on number of values alone, and infact I may indeed do so - however, I consider it better for later administration if there is a static item in there to confirm the use of the route.
I hope this helps someone!
One way you can do it is specify a wildcard route (notice the asterisk):
routes.MapRoute("subjects", "{action}/{*path}",
new { controller = "Subjects", action = "Index" });
This allows the controller to receive the entire path string after the action.
You can then obtain the hierarchy of subjects in the controller method like so:
string[] subjects = path.Split('/');
Once you have that, you can do anything you want, including dispatching different subjects to different handling methods for processing.

ASP.Net MVC: Check if URL is Authorized

I'd like to simply check from a Controller whether another URL is authorized.
So for example, I'd like to call into a Controller like so:
[HttpPost]
public ActionResult IsUrlAuthorized(string url)
{
bool isAuthorized = // What do I put here?
return Json(isAuthorized);
}
So I'd like to know what I could call to check on whether the current user is authorized for the passed-in URL or not. I'm guessing the answer has something to do with Routes, which sit a little bit outside MVC?
This is a somewhat similar question but not quite the same thing:
ASP.NET MVC. Check if user is authorized from JavaScript
Since the user may or may not be authorized in general, but may not have the right permissions or role assignments to see a specific URL.
Ideas?
Update: I use standard MVC authorization attributes to lock down my app, so I'll just give an example of what that looks like here. In MVC Routes map to Controllers. A single method on a Controller can be restricted to one or more Roles:
public class HomeController : Controller
{
[Authorize(Roles = "User, Moderator")]
public ActionResult ListRecentPosts()
{
. . .
}
}
Or, an entire Controller can be restricted to one or more roles:
[Authorize(Roles = "Admin")]
public class AdminController : Controller
. . .
The actual URL that any of these controller methods responds to is based on a default mapping in a standard MVC app:
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
But, you can be nice to your users and make URLs guessable by adding a lot more Routes - as a result, a Controller method can have many names that point to it. You can't just assume and infer the controller name from the URL (even if it maps out that way for half the URLs in the site).
So presumably I either need a way to ask the Routing engine directly whether a URL is authorized for the current user, or a 2-step of asking the Routing engine for which Controller and Method, then ask if those are authorized - hopefully not by using Reflection and matching Roles directly as that again would appear to assume too much.
Update 2: The way this came up is I have an Account strip at the top of my app. Its state can change by selecting one of several accounts you're authorized as. Depending on where you are in the app, the account you chose might have authorization to view this page - and you might be in the middle of filling out a form you don't want to lose. So the naive approach - just refresh when they pick another account - is harmful, and a waste of the user's time even if there is no form and they're just reading a page that's all text.
While that convenience to the user is nice, the user is going to fairly assume that pages they can't see as a user who shouldn't have permission really are denied (and, it would be harmful to leave them on a page that's forbidden - actions taken from it will fail). So I need to know whether to redirect away based on their new permissions.
One of the things I love about .Net is the way many of its best libraries decompose so well, so you can easily recompose things that are part of its normal functionality, or a new twist. Both the Routing module and MVC appear to be very well constructed, so I have to suspect this can be done.
The cheap hack is to ensure that my authorization module returns a consistent redirect status code when a user isn't authorized, and when the user changes their account in the account strip, fire 2 AJAX calls: One to change account, and then a second to the current page over AJAX just to check the HTTP Status Code. 200 OK means leave the page as is, Redirect means follow the redirect. Obviously this is a little ugly, involves an extra HTTP call, creates a false hit in the logs, and makes an assumption about how authorization is handled across the app.
There could be a secondary concern - the page might be authorized, but just change how it works or looks. This particular app has no change in look based on account (besides the account strip itself), and I can handle functionality changes by just providing a custom event that forms listen to - they can reload any relevant data from the server in response to it.
Using UrlAuthorization.CheckUrlAccessForPrincipal only works if you're only using URL authorization. But for MVC using Routing, we highly recommend that you don't use URL authorization to secure an app.
Instead, we recommend using Authorization attributes on the controller class. The reason is there could be multiple URLs that call the same controller action. It's always better to secure the resource at the the resource and not just at the entry ways.
In this particular case, you'd have to get an instance of the controller given the URL. THat's a little tricky as you'll basically have to run the MVC pipeline from the point where you have the URL to the point where you have the controller. It's possible, but seems heavyweight.
I wonder if there isn't a better and simpler way to accomplish your goals. What is it you're really trying to do?
UPDATE: Based on your scenario, it sounds like this is an initial check just for UI purposes. Perhaps all you need to do is make an asynchronous Ajax request to the URL and check the HTTP Status code. If it's a 401 status code, you know the user is not authorized. That seems like the safest bet.
How about UrlAuthorizationModule.CheckUrlAccessForPrincipal method.
UrlAuthorizationModule.CheckUrlAccessForPrincipal Method (System.Web.Security)

Grails with SpringSecurity, check if the current user can access controller / action

I'm currently developing a menu for my application that should be able to display only the controllers that the current user can access (requestmap defined in the database).
How can I check if the current user has access to a specific controller and action?
To check roles in view :
Spring security plugin provides ifAllGranted, ifAnyGranted, ifNoneGranted etc., tags to check roles
For example, to check Admin Role of logged in User :
<sec:ifLoggedIn>
<sec:ifAllGranted roles="ROLE_ADMIN">
Admin resource
</sec:ifAllGranted>
</sec:ifLoggedIn>
(tested in grails-2.2.2 and springSecurityCorePlugin-1.2.7.3)
org.grails.plugins.springsecurity.service.AuthenticateService authenticateService = new org.grails.plugins.springsecurity.service.AuthenticateService()
def isAdmin = authenticateService.ifAllGranted('ROLE_ADMIN')
if(isAdmin) {
println 'I am Admin'
}
This question is pretty old, but I thought I'd post at least an answer that seems to work with Grails 2.0. If you are using the spring security plugin, there's a tag lib included called grails.plugins.springsecurity.SecurityTagLib.
The tag-lib has a protected method, hasAccess() which can take the same params map that you give the g:link tag. So, if you extend SecurityTagLib, you can call hasAccess() and get the behavior you want. Why this isn't externalized into a service that can be injected is beyond me as the functionality seems to fulfill an obvious need.
We use this to wrap the g:link tag and only generate a link of the user has access to the target page:
def link = { attrs, body ->
if( hasAccess(attrs.clone(), "link") ) {
out << g.link(attrs, body)
}
else {
out << body()
}
}
When dealing with permissions in views and taglibs, you can use the AuthorizeTagLib that's provided by the plugin.
For example, if you don't want a menu item to appear in your list for unauthenticated users, you might use:
<g:isLoggedIn>
<li>Restricted Link</li>
</g:isLoggedIn>
If you have more specific roles defined and those roles are tied to your controller/action request mapping, you can use other tags, such as:
<g:ifAllGranted role="ROLE_ADMINISTRATOR">
<li>Administrator Link</li>
</g:ifAllGranted>
In my experience, there's not yet a good way to tie the request mapping to your markup - I think you're going to have to use some of the above tags to limit access to content within a particular GSP.
I think that Burt Beckwith has a future modification (and is currently providing a beta version) to the plugin that integrates some ACL stuff that might solve this problem better in the future, but for now, I think the best approach is a hybrid request map + GSP tags one.
Not sure of the situation when this question was originally asked, but now you can check to see if a user is in a specific role by using SpringSecurityUtils.ifAllGranted() which takes a single String which is a comma delimited list of roles. It will return true if the current user belongs to all of them.
if(SpringSecurityUtils.ifAllGranted('ROLE_ADMIN,ROLE_USER')) {
Obviously, you can simply pass one role to the function if that is all you need. SpringSecurityUtils also has methods like ifAnyGranted, ifNotGranted, etc, so it should work for whatever it is you are trying to accomplish.
SpringSecurityUtils is a static API, so you don't need to create a private member named springSecurityUtils or anything like that.
You have to configure the file config/SecurityConfig.groovy (if it does not exists, create it, this overrides the default Security Configuration)
Add this entry:
requestMapString = """\
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/=IS_AUTHENTICATED_REMEMBERED
/login/auth=IS_AUTHENTICATED_ANONYMOUSLY
/login/authajax=IS_AUTHENTICATED_ANONYMOUSLY
/login/authfail=IS_AUTHENTICATED_ANONYMOUSLY
/js/**=IS_AUTHENTICATED_ANONYMOUSLY
/css/**=IS_AUTHENTICATED_ANONYMOUSLY
/images/**=IS_AUTHENTICATED_ANONYMOUSLY
/plugins/**=IS_AUTHENTICATED_ANONYMOUSLY
/**=IS_AUTHENTICATED_REMEMBERED
"""
This is means that you have to log in to enter the site. But all the resources (css, js, images, etc) is accessed without authentification.
If you want specific role only enter specific controller:
For example, for UserController:
/user/**=ROLE_ADMIN
/role/**=ROLE_ADMIN
For more information: http://www.grails.org/AcegiSecurity+Plugin+-+Securing+URLs
Regards
As far as I can tell, there doesn't look like there's an easy way to do it.
You can inject an instance of the grails AuthenticatedVetoableDecisionManager which is a concrete class of spring's AbstractAccessDecisionManager by doing this:
def accessDecisionManager
This has a "decide" method on it that takes 3 parameters
decide(Authentication authentication, Object object, ConfigAttributeDefinition config)
This is probably the method that you'd need to call and pass in the right things to figure out if the user with the auth creds can access that "object" (which looks like it's normally a request/response). Some additional digging around might prove out something workable here.
Short term, it's probably easier to use the ifAnyGranted taglib as another poster mentions.
I'm not sure about in Groovy, but in Java (so I assume Groovy too...) you could do (minus NPE checks):
GrantedAuthority[] authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
boolean isAdmin = false;
for(GrantedAuthority authority : authorities) {
String role = authority.getAuthority();
if(role != null && role.equals("ROLE_ADMIN")) {
isAdmin = true;
break;
}
}
As for knowing whether or not the action is supported, you'd have to call the RequestMap service to get the roles for the mapping and see if it contains the found user role.

Where should "Master Page" logic go in MVC?

I'm experimenting with MVC, and my question is - where I had Page_Load logic in Master Pages with WebForms, where should it go in MVC? Here's the business case:
Different Host Headers should cause different Page Titles to be displayed on the site's (one) Master Page, therefore all pages. For example, if the host header is hello.mydomain.com, the page title should be "Hello World" for all pages/views, while goodbye.mydomain.com should be "Goodbye World" for all pages/views.
If the host header is different than anything I have in the list, regardless of where in the application, it should redirect to /Error/NoHostHeader.
Previously, I'd put this in the MasterPage Load() event, and it looks like in MVC, I could do this either in every controller (doesn't feel right to have to call this functionality in every controller), or somewhere in Global.asax (seems too... global?).
Edit: I have gotten this to work successfully using the Global.asax method combined with a Controller for actually processing the data. Only problem at this point is, all of the host header information is in a database. I would normally store the "tenant" information if you will in a Session variable and only do a DB call when it's not there; is there a better way to do this?
There is no 1:1 equivalent in MVC for a reason, let's just recapitulate how to think about it the MVC way:
Model: "Pages of this site are always requested in a certain context, let's call it the tenant (or user, topic or whatever your sub domains represent). The domain model has a property representing the tenant of the current request."
View: "Render the page title depending on the tenant set in the model."
Controller: "Set the tenant in the model depending on the host header".
Keep in mind that what we want to avoid is mixing controller, view and business logic. Having controller logic in more then one place or a place, that is not called "controller" is not a problem, as long as it remains separated.
And now the good thing: You could do this "MVC style" even with Web Forms, and the solution still works with ASP.NET MVC!
You still have the request lifecycle (not the page lifecycle), so you could implement a custom HttpModule that contains this part of the controller logic for all requests. It handles the BeginRequest event, checks for the host header, and stores the tenant to something like HttpContext.Current.Items["tenant"]. (Of course you could have a static, typed wrapper for this dictionary entry.)
Then all your model objects (or a model base class, or whatever is appropriate for your solution) can access the HttpContext to provide access to this information like this:
public string Tenant
{
get { return HttpContext.Current.Items["tenant"]; }
}
Advantages:
You have separated cause (host header) and effect (rendering page title), improving maintainability and testability
Therefore you could easily add additional behavior to your domain model based on this state, like loading content from the database depending on the current tenant.
You could easily make more parts of the view depend on the tenant, like CSS file you include, a logo image etc.
You could later change the controller logic to set the tenant in the model not only based on the sub domain, but maybe based on a cookie, a referrer, search term, user agent's language, or whatever you can think about, without modifying any of your code depending on the model.
Update re your edit: I don't like the idea of holding the state in the session, especially if your session cookie might apply not only to each sub domain, but to all domains. In this case you might serve inconsistent content if users visted another sub domain before. Probably the information in the database that is mapping host headers to tenants won't change very often, so you can cache it and don't need a database lookup for every request.
You could create a base controller that supplied the correct ViewData to your MVC Master Page View, then derive each of your actual controllers from that one. If you put the logic into the ActionExecuting method, you should be able to generate an exception or redirect to an error page if necessary.
You are thinking too "WebForms" and not enough MVC. A master page is just a wrapper of your view, and it should only contain layout html. You can send stuff to your master, but it's a one way road and you should strive for agnostic views. Bottom line: forget about the events that WebForms had as they aren't going to be used here.
Since you are dealing with Host headers I suppose you could put it in the Global.asax...great now I'm confused :P
Stolen code from http://forums.asp.net/t/1226272.aspx
protected void Application_BeginRequest(object sender, EventArgs e)
{
string host = string.Empty;
if (this.Request.ServerVariables["HTTP_HOST"] == this.Request.Url.DnsSafeHost)
{
host = this.Request.Url.DnsSafeHost;
}
else
{
Regex regex = new Regex("http://[^/]*.host/([^/]*)(/.*)");
Match match = regex.Match(this.Request.Url.AbsoluteUri);
if (match.Success)
{
host = match.Groups[1].Value;
Context.RewritePath(match.Groups[2].Value);
}
}
// Match the host with the portal in the database
...
}
What you need is here http://www.asp.net/mvc/tutorials/passing-data-to-view-master-pages-cs

Resources