I have Members in my Umbraco 7 website. Each member can login to the front-end site and edit pages which only the member can edit. I use UmbracoIdentity for managing Members.
I have a custom property Page (MNTP) on my Member doc Type which specify which pages a Member can edit. On user profile page I show a list with the alloed pages for edit from this custom property like this:
var pages = profileModel.MemberProperties.FirstOrDefault(p => p.Alias == "pages").Value;
foreach (var item in pages.Split(','))
{
DetailsPage obj = Umbraco.TypedContent(item) as DetailsPage;
#obj.H1Title
}
When a member clicks on the link (domain.com/edit-page?pageId=3242) I have action method which loads data for the page:
#Html.Action("GetPageDetails", "Edit", new { id = pageId })
What I want to prevent is if a Member change pageId query string manualy and his custom property Pages does not contain this pageId to redirect him to user profile page with the list with his allowed pages.
I need an idea how to restrict logged in member to edit only the pages that are assigned in the property Pages.
I have successfully added the pages IDs in Claims and I can perform the check in each method. But my methods are a lot. Is there some best practice or a solution which does not require to go over all methods.
Thanks
Related
Say I have a website with a wide variety of different pages, each tailored for an individual user and accessible only by that user. When a user accesses a page, the URL for that page will be UserStuff/Pages/11, where 11 is the page's ID. Right now, because users can have multiple pages, I am using an ActionLink element to direct the user to their selected page, like so:
// In Index.cshtml
// ...
#Html.ActionLink("View Page", "Pages", new { id = page.ID }, new { #class=...})
The controller intercepts the given id:
public ActionResult Pages(int id)
{
// ...
Page page = db.Pages.Find(id);
return View(page);
}
This works well and delivers exactly how I want the application to behave. However, I wish for the ID to be hidden in the URL. How do I pass information from the View to the controller via a parameter without having it exposed in the URL? Say that I don't want the Page's ID visible in the URL, how can I link to a different Action that takes the given parameter?
I believe that keeping the request a GET request is important due to other actions redirecting to the resulting page. Wrapping it in a form/POST request would make said actions unable to access the information without having to go through the same process as the original user, which is not something that I want in this implementation.
I am developing a ZF2 based site. I have a main navigation which stays same regardless of the visitor/user status. Need to add another component/nav, which will depend on the user's status and role. For a visitor the items will be
Register
Login
EN (Actually a drop-down, with other available language)
For a logged-in normal user, it will display
Profile
Logout
EN (Language selector as mentioned above)
And for some users with specific roles/permission there will be additional items
I want to use RBAC, as ACL seems bloated, and also just to check if the current logged in user/role has additional items, I need to load the complete ACL (and we got around 15+ different types of roles).
I spent some time thinking how I have achieve this, so following are some ideas I have.
I create an empty navigation container, and create a factory. In the factory, I access the Authentication and RBAC and add the pages depending on the the user's status/role.
I create a fully loaded navigation with all the possible pages, then in the factory, with the help of Authentication and RBAC I hide the pages I don't want to show.
rd option is to use a view helper which will get RBAC via ServiceLayer and generate the navigation. (As discussed in ZF2 how to display tweets in layout and ZF2 : Add a Login widget in the template.
Or I can create a controller-plugin or just a method in module.php, and listen to the MVC_Render or MVC_Dispatch event and generate the desired navigation and add the output to a view variable.
PS: I need to use a partial as I need to add some CSS class to the language selection section. Also the navigation will be displayed in the layout.
I am using ZfcRbac and I am doing it as the following, you can display the navigation based on user roles and the navigation items permission as the following:
First add a permission to your navigation item as the following:
'permission' => 'edit-profile',
Then attach a listener in the onBootstrap as the following:
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$eventManager->getSharedManager()->attach(
'Zend\View\Helper\Navigation\AbstractHelper',
'isAllowed',
array('\Application\Listener\RbacListener', 'accept')
);
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
}
Then create a class Application\Listener\RbacListener as the following:
public function accept(Event $event) {
$event->stopPropagation();
$accepted = true;
$serviceLocator = $event->getTarget()->getServiceLocator()->getServiceLocator();
$rbac = $serviceLocator->get('ZfcRbac\Service\Rbac');
$params = $event->getParams();
$page = $params['page'];
$permission = $page->getPermission();
if ($permission) {
$accepted = $rbac->isGranted($permission);
}
return $accepted;
}
and by this when you display the menu it will be filtered based on the permission and roles, for example if you do echo $this->navigation('navigation')->menu() then only the menu items that the user has permission on will be displayed.
How do I make the links on the site lead to the current user's page(s)? I dont want them to get lists of every user but only themselves. I'm thinking something like this for my menu links:
#Html.ActionLink("My Details", "Details", "Customers", Membership.GetUser().ProviderUserKey)
I think it's better if you send user to "My Details" page without any parameter or route values.
The page is going to show current users data. So why to pass it like this?
Just Redirect user to "My Detail" page, and after that, when user is in that page, you can get current user using: HttpContext.CurrentUser.
Don't put user data in route values. Instead you can get it in that page's controller and pass it to the page.
If you want the "Links" to display to show when the page is viewed by an authenticated user, create a ChildAction that returns the data you want like:
[Authorize]
[ChildActionOnly]
public ActionResult UserLinks() {
var items = someRepository.GetLinks(User.Identity.Name);
return PartialView(items,"SomePartialView");
}
Then, use the RenderAction in the view like so:
#{ Html.RenderAction("UserLinks");}
Secondly, I agree with the comment by "Afshin Gh". Rather than passing data in your "ActionLink(s)", you could code it in your Controller to filter the data as required - like I'm showing in the "UserLinks" method above. This way, someone can't just manipulate the Url to display data for another customer.
Working on an ASP.NET MVC 3 (Razor) application, that's mainly concerned with UGC (user generated content).
I'm working on a "Q&A" area - where users can ask questions, others can answer, vote, etc.
As such, i'm trying to figure out a clean way to handle the available operations a user can do on any given page, based on their role and other factors.
Take the scenario of the "Question Detail Page" (like this page on Stack Overflow).
Any (authenticated) user can:
Vote for question/answer
Answer
Question owner can:
Edit
Delete
Mark answer
And so forth.
Now, i have a QuestionViewModel, that is used to display the question and relevant answers for this particular View.
I create it using AutoMapper.
How can i display "stickies" (e.g hyperlinks) on the page, based on the available operations?
My current thinking is:
I create an enum: QuestionOperation (Answer, Edit, Disable, Vote, Answer, etc)
I add a property of type IEnumerable<QuestionOperation> to my ViewModel
I set this property in my action method (HTTP GET), checking if the user is authenticated and the roles they are a part of.
I then use an editor template to render out each operation as a hyperlink, using Html.ActionLink
Is that considered a clean approach - or can anyone suggest a better one?
Keeping in mind i am re-using this QuestionViewModel on three pages:
The question detail page
The "Ask a question" page
The "Edit a question" page
So because these operations are page/user dependant, it can't really be done with AutoMapper.
I would setup a separate controller and action which will be returning a partial view containing the necessary links. Then I would use the Html.Action helper to include it from the main view.
Something among the lines:
public class UserLinksController: Controller
{
// TODO: ctor DI of a repository, etc...
public ActionResult Index(string questionId)
{
string username = User.Identity.IsAuthenticated
? User.Identity.Name : string.Empty;
var roles = _repository.GetRolesForQuestion(username, questionId);
var model = Mapper.Map<UserRoles, RolesViewModel>(roles);
return PartialView(model);
}
}
and in the corresponding partial you would check the view model and render the necessary links:
#model RolesViewModel
#if(Model.CanEdit)
{
#Html.ActionLink("Edit", "Edit", "Questions")
}
#if(Model.CanDelete)
{
#Html.ActionLink("Delete", "Delete", "Questions")
}
...
Now somewhere in your main view simply include this action using the Html.Action method:
#Html.Action("Index", "UserLinks", new { questionId = Model.QuestionId })
i have this code in my membership service class (taken from the asp.net-mvc sample app)
public MembershipUserCollection GetUnapprovedUsers()
{
MembershipUserCollection users = Membership.GetAllUsers();
MembershipUserCollection unapprovedUsers = new MembershipUserCollection();
foreach (MembershipUser u in users)
{
if (!u.IsApproved)
{
unapprovedUsers.Add(u);
}
}
return unapprovedUsers;
}
i now need a view to show this list of information and allow someone to approve them which will go back to the controller and set the IsApproved property to true.
Create a view which will generate a form containing label and checkbox for each member of the collection. You need to be able to get from the id of the checkbox to the user.
In the HTTP.POST Action method, iterate through the submitted fields looking for set checkboxes, when you find one set the corresponding user to approved.
Obviously the form can display arbitrary details for each user.
To use the inbuilt control helpers takes a bit more effort because you don't have a fixed size model to work with. To achieve something similar I:
Used a non-strongly typed view
populated ViewData["ids"] with IEnumerable<IdType> (which the view would loop over)
For each entry populated ViewData["field" + id] for each field I was displaying in the entity
In the view looped over the ids using ViewData["ids"] to call the HTML helpers with the id of the field.
(That was V1, in V2 I used model state so I could use the inbuilt validation error display support, but that doesn't really apply if you just want to select users.)
The POST processing was similar, repopulating the id list from the database and the looking up in the passed FormCollection.