Summary: I need to authorize pages based upon the data present in the query string of a url, not just the page name.
Background:
Let's say I'm building a library inventory system. Users can be created and assigned to a single library in either an Admin or User role. There are hundreds of competing libraries in the same database, so it's important to ensure that users of one library cannot view inventory from another library.
Right now I'm using a pretty standard ASP.NET setup: Forms Authentication using the SqlMembershipProvider. Authorization using the SqlRoleProvider, configured via <authorization>
sections in the web.config. Security trimming with the SiteMap provider to hide unauthorized pages.
To control the inventory information from leaking, I'm manually checking a user's associate libra开发者_运维技巧ry ID with every inventory query. It works, but it's tedious and prone to errors. There has to be a better way.
Question:
Now users have the ability to create arbitrary "collections" within a library. (e.g. Collection A has Books 1, 2, & 3 in it.) Admins want the ability to grant Admin / User access on individual collections, not just the entire library.
So, if a user goes to www.com/Book.aspx?BookId=1
, the system needs to ensure that user has permissions for the collection that "Book 1" is in before showing the page. If they go to www.com/Reviews.aspx?ReviewId=23, I need to make sure the Review is for a book that is in a collection that they have permission to view.
1) How can I implement this in the most standard ASP.NET way possible?
Manual checking within a base page? A custom HttpModule? A custom Role Provider? I'm not interested in how to store the admin/user permissions, but rather how/where to authorize based on those permissions. (examples on how to implement any of those are appreciated)2) To further complicate it, I'd still like security trimming to check if the user has Admin rights on any collection or library and hide the admin pages if he doesn't.
I wouldn't handle this anywhere near the UI (ASP.NET) layer but rather within the application services. Something like:
- Build services which take an
IPrincipal
(or your custom user object) as a constructor parameter. - When requesting a book/review/whatever, the service is responsible for looking to checking to see if the user has access to the resource.
- If the user doesn't have access, do some predetermined thing (pass a message, throw an exception, return null).
This will be alot more testable and usable in the long run then worrying about it from the ASP.NET UI side.
If you have to handle it on the ASP.NET side, I'd consider using a custom IPrincipal
and custom RoleProvider
to wrap up each library as a role to access, then you could use most of the LoginView
, etc. controls.
Normally, this sort of thing is handled at the data level. It has little to do with ASP.NET, other than ultimate you need a user-id (which comes from Membership). What you do is find some part of your entity that you want to control access on, then you create all your queries to filter on that.
For instance, if you make access at the Library level, then you would add an association between the user and the library. This can be a 1:1, 1:many, many:many, whatever your data model requires. The key is that joining through this level will always return no records, thus your entire query will return no records.
Example, assuming a user can only belong to one library.
User table has LibraryID, Books table has LibraryID, This makes an effective many-to-many join between Users and Books. So you join User and Library on LibraryID, and join Library and Books on LibraryID, then only books that belong to a library that the user is associated with will be returned by the query.
In this way, it becomes impossible for a user to query anything they are not directly associated with. The security is entirely in the database, and no business logic is required.
精彩评论