I'm building the 2nd iteration of a web-based CRM+CMS for a franchise service business in ASP.NET MVC 2. I need to control access to each franchise's services based on the roles a user is assigned for that franchise.
4 examples:
Receptionist
should be able to book service jobs in for her "Atlantic Seaboard" franchise, but not do any reporting.Technician
should be able to alter service jobs, but not modify invoices.Managers
should be able to apply discount to invoices for jobs within their stores.Owner
should be able to pull reports for any franchises he owns.
Where should franchise-level access control fit in between the Data - Services - Web
layer?
If it belongs in my Controllers, how should I best implement it?
Partial Schema
Roles
class
int ID { get; set; } // primary key for Role
string Name { get; set; }
Partial Franchises
class
short ID { get; set; } // primary key for Franchise
string Slug { get; set; } // unique key for URL access, eg /{franchise}/{job}
string Name { get; set; }
UserRoles
mapping
short FranchiseID; // related to franchises table
Guid UserID; // related to Users table
int RoleID; // related to Roles table
DateTime ValidFrom;
DateTime ValidUntil;
Controller Implementation
Access Control with [Authorize]
attribute
If there was just one franchise involved, I could simply limit access to a controller action like so:
[Authorize(Roles="Receptionist, Technician, Manager, Owner")]
public ActionResult CreateJob(Job job)
{
...
}
And since franchises don't just pop up over night, perhaps this is a strong case to use the new Areas feature in ASP.NET MVC 2? Or would this lead to duplicate Views?
Controllers, URL Routing & Areas
Assuming Areas aren't used, what would be the best way to determine which franchise's data is being accessed? I thought of this:
{franchise}/{controller}/{action}/{id}
or is it better to determine a job's franchise in a Details(...) action and limit a user's action with [Authorize]:
{job}/{id}/{action}/{subaction}
{invoice}/{id}/{action}/{subaction}
which makes more sense if any user could potentially have access to more than one franchise without cluttering the URL with a {franchise} parameter.
Any input is appreciated.
Edit:
Background
I built the previous CRM in classic ASP and it runs the business well, but it's time for an upgrade to speed up workflow and leave less room for error. For the sake of proper testing and better separation between data and presentation, I decided to implement the repository pattern as seen in Rob Conery's MVC Storefront series.
How to arrange services and repositories?
It makes sense to have a JobService
that retrieves any service jobs based on available filters, eg. IQueryable<Job> GetJobs();
. But since a job can only belong to one franchise, a function like IQueryable<Job> GetJobs(int franchiseID);
could belong in either FranchiseService
or in JobService
. Should FranchiseService
act as a CatalogService
(like in MVC Storefront)?