I have a document library in my SharePoint page and there are 10 documents in it.
If User A is logged in I want him to only see 5 of those documents in that document library.
How can I create some custom document library for this to work?
I have MOSS installed.
Thanks in advance!
You could configure different permissions on each document in the document library. Just select the "Manage Permissions" option on each item and break the permission inheritance from the document library level. Just note that having too many documents with item level permissions can create a maintenance nightmare for you. Another option could be to create two document libraries with different permissions.
Write an ItemEventReceiver that breaks the permissions based on a field in the library, i.e. a column that holds the different roles .
We have done this by creating a list that holds all roles coupled to sharepoint groups.
i.e.
Administrator -> Owners of website (SPGroup), Company Administrators (SPGroup)
Managers -> Managers (SPGroup)
then in our content type we have a lookup column to this list.
Here's the code for the ItemEventReceiver:
public override void ItemUpdated(SPItemEventProperties properties)
{
lock (_lock)
{
try
{
using (SPSite site = new SPSite(properties.SiteId,
properties.ListItem.ParentList.ParentWeb.Site.SystemAccount.UserToken))
using (SPWeb web = site.OpenWeb(properties.RelativeWebUrl))
{
web.AllowUnsafeUpdates = true;
var item = web.Lists[properties.ListId].GetItemById(properties.ListItemId);
var roles = item["Roles"] as SPFieldLookupValueCollection;
var rolesList = web.Site.RootWeb.Lists["Company Roles"];
var groupsToAdd = new List<SPFieldUserValue>();
if (item.HasUniqueRoleAssignments)
{
item.ResetRoleInheritance();
item = item.ParentList.GetItemById(item.ID);
}
if (roles != null && roles.Count > 0)
{
// Iterate over the roles and see if there is a group associated
foreach (var role in roles)
{
var roleItem = rolesList.GetItemById(rol.LookupId);
if (roleItem != null)
{
// This is the SPgroup field in the rolesList
var groups = roleItem["Groups"] as SPFieldUserValueCollection;
if (groups != null)
{
groupsToAdd.AddRange(from g in groups
where g.User == null
select g);
}
}
}
if (groupsToAdd.Count > 0)
{
item.BreakRoleInheritance(false);
foreach (var value in groupsToAdd)
{
var group = web.Groups[value.LookupValue];
var assignment = web.RoleAssignments.GetAssignmentByPrincipal(group);
item.RoleAssignments.Add(assignment);
}
}
}
DisableEventFiring();
item.SystemUpdate(false);
EnableEventFiring();
}
}
catch (Exception ex)
{
//LOG ERROR
}
}
}
If the coding doesn't work for you, and you'd rather not set permissions on each file, then there is a third option. We use folders with permissions set on them.
e.g.
Create a folder called "Managers", break permissions, and set rights to only the managers.
Create another folder called "Employee 1", break permissions, and set Contribute rights to the Employee and the Employe's manager.
Place the files in the appropriate folders and it will inherit rights from the folder.
This way, managers can see the manager files, and all files of their employees. Users can only see their own files.
Similar logic can be done for Headquarters, Region 1, Region 2, etc ... and creating different Groups for each region and then assigning the group to the folder's permissions.
Note, there's always concern in using this design on maintaining all the permissions and on performance, but we've been doing similar things for 750+ user populations and thousand of docs and it's been working fine for us so far.
Related
I'm building a custom workflow where all users that are members of a specific role will receive email notifications depending on certain state changes. I've begun fleshing out e-mail templates via Sitecore items with replaceable tokens, but I'm struggling to find a way to allow the setting of the recipient role in Sitecore. I'd like to avoid having users enter a string representation of the role, so a droplink would be ideal if there were a way to populate it with the various roles defined in sitecore. Bonus points if I can filter the roles that populate the droplink.
I'm aware that users/roles/domains aren't defined as items in the content tree, so how exactly does one go about configuring this droplink?
Sitecore 6.5.
I'm not sure if there is a module for this already made, but you can use this technique: http://newguid.net/sitecore/2013/coded-field-datasources-in-sitecore/
It explains how you can use a class as data source. So you could create a class that lists all user roles.
You might want to take a look at http://sitecorejunkie.com/2012/12/28/have-a-field-day-with-custom-sitecore-fields/ which presents a multilist to allow you to select a list of users.
Also take a look at the Workflow Escaltor Module form which you can borrow the AccountSelector control which allows you to select either individual person or roles.
This is the module I previously used to do this exact thing. The following code gets all the unique email addresses of users and only for those users that have read access to the item (it was a multisite implementation, the roles were restricted to each site but the workflow was shared).
protected override List<string> GetRecipientList(WorkflowPipelineArgs args, Item workflowItem)
{
Field recipientsField = workflowItem.Fields["To"];
Error.Assert((recipientsField != null || !string.IsNullOrEmpty(recipientsField.Value)), "The 'To' field is not specified in the mail action item: " + workflowItem.Paths.FullPath);
List<string> recepients = GetEmailsForUsersAndRoles(recipientsField, args);
if (recepients.Count == 0)
Log.Info("There are no users with valid email addresses to notify for item submission: " + workflowItem.Paths.FullPath);
return recepients;
}
//Returns unique email addresses of users that correspond to the selected list of users/roles
private List<string> GetEmailsForUsersAndRoles(Field field, WorkflowPipelineArgs args)
{
List<string> emails = new List<string>();
List<User> allUsers = new List<User>();
AccountSelectorField accountSelectorField = new AccountSelectorField(field);
List<Account> selectedRoles = accountSelectorField.GetSelectedAccountsByType(AccountType.Role);
List<Account> selectedUsers = accountSelectorField.GetSelectedAccountsByType(AccountType.User);
foreach (var role in selectedRoles)
{
var users = RolesInRolesManager.GetUsersInRole(Role.FromName(role.Name), true).ToList();
if (users.Any())
allUsers.AddRange(users);
}
selectedUsers.ForEach(i => allUsers.Add(Sitecore.Security.Accounts.User.FromName(i.Name, false)));
foreach (var user in allUsers)
{
if (user == null || !args.DataItem.Security.CanRead(user)) continue; //move on if user does not have access to item
if (!emails.Contains(user.Profile.Email.ToLower()))
{
if(user.Profile.Email != null && !string.IsNullOrEmpty(user.Profile.Email.Trim()))
emails.Add(user.Profile.Email.ToLower());
else
Log.Error("No email address setup for user: " + user.Name);
}
}
return emails;
}
Assume we have a report called SalesSummary for a large department. This department has many smaller teams for each product. People should be able to see information about their own product, not other teams' products. We also have one domain group for each of these teams.
Copying SalesSummary report for each team and setting the permission is not the best option since we have many products. I was thinking to use a code similar to below on RS, but it doesn't work. Apparently, System.Security.Principal.WindowsPrincipal is disabled by default on RS.
Public Function isPermitted() As Boolean
Dim Principal As New System.Security.Principal.WindowsPrincipal(System.Security.Principal.WindowsIdentity.GetCurrent())
If (Principal.IsInRole("group_prod")) Then
Return true
Else
Return false
End If
End Function
I also thought I can send the userID from RS to SQL server, and inside my SP I can use a code similar to below to query active directory. This also doesn't work due to security restriction.
SELECT
*
FROM OPENQUERY(ADSI,'SELECT cn, ADsPath FROM ''LDAP://DC=Fabricam,DC=com'' WHERE objectCategory=''group''')
Is there any easier way to achieve this goal?
Thanks for the help!
The first option you suggested (using embedded code to identify the executing user) will not be reliable. SSRS code is not necessarily executed as the user accessing the report, and may not have access to that users credentials, such as when running a subscription.
Your second approach will work, but requires the appropriate permissions for your SQL server service account to query Active Directory.
Another approach is to maintain a copy of the group membership or user permissions in a SQL table. This table can be updated by hand or with an automated process. Then you can easily incorporate this into both available parameters and core data queries.
So I ended up with this code:
PrincipalContext domain = new PrincipalContext(ContextType.Domain, "AD");
UserPrincipal user = UserPrincipal.FindByIdentity(domain, identityName);
//// if found - grab its groups
if (user != null)
{
PrincipalSearchResult<Principal> _groups = null;
int tries = 0;
//We have this while because GetGroups sometimes fails! Specially if you don't
// mention the domain in PrincipalContext
while (true)
{
try
{
_groups = user.GetGroups();
break;
}
catch (Exception ex)
{
logger.Debug("get groups failed", ex);
if (tries > 5) throw;
tries++;
}
}
// iterate over all groups, just gets groups related to this app
foreach (Principal p in _groups)
{
// make sure to add only group principals
if (p is GroupPrincipal)
{
if (p.Name.StartsWith(GROUP_IDENTIFIER))
{
this.groups.Add((GroupPrincipal)p);
this.groupNames.Add(p.Name);
}
}
}
}
Now, that you have a list of related group you can check the list to authorize the user!
I have a sharepoint list like that:
List
---------Folder 1
-----------------Item 1
-----------------Item 2
---------Folder 2
-----------------Item 1
-----------------Item 2
---------Folder 3
-----------------Item 1
-----------------Item 2
How can I get all Folders in List?
After that checking if current user has Contribute permission on Folder 1, Folder 2, Folder 3?
To get the list of folders of a list you can use the Folders property of the SPList object:
private SPFolderCollection GetListFolders(SPList list) {
return list.Folders;
// you can also do:
// return list.Folders.Cast<SPFolder>().ToList();
// to return a List<SPFolder> instead of a SPFolderCollection
}
To check if a given user has Contribute permission on a folder you need to get the SPListItem associated with the SPFolder, check for a RoleAssignment of the given user and check its RoleDefinitionBindings for the Contribute Role Definition:
private bool HasContributePermissionOnFolder(SPFolder folder, SPPrincipal user) {
var contributePermission = folder.ParentWeb.RoleDefinitions["Contribute"];
var roleAssignementsOfUser = folder.Item.RoleAssignments.Cast<SPRoleAssignment>()
.Where(ra => ra.Member == user);
var hasContributePermission = roleAssignementsOfUser
.Where(ra => ra.RoleDefinitionBindings.Contains(contributePermission)).Count() > 0;
return hasContributePermission;
}
Usage example
//remember to add using System.Linq; for the above code to work
//SPList list = <your list>;
//SPWeb web = <your web>;
var folders = GetAllFoldersOfList(list);
foreach (SPFolder folder in folders) {
if (HasContributePermissionOnFolder(folder, spWeb.CurrentUser)) {
// do stuff
}
private IEnumerable<SPFolder> GetListFolders(SPList list)
{
return list.Folders.OfType<SPListItem>().Select(item => item.Folder);
}
Checking user permissions by checking their membership of role definitions is a bit risky. Who's to say that the role definition won't be renamed or that the base permissions included in the role definition won't have been modified.
If the goal is primarily to check the current user's permissions on a securable object then I think a better way is simply to call one of the overloaded DoesUserHavePermissions methods of the SPSecurableObject (SPListItem, SPList, SPWeb or SPSite) with the desired permission mask.
I need to retrieve all SPUser's from a SPGroup. Unfortunately, the group may contain Active Directory groups, so a simple SPGroup.Users is not enough (I'd just get a single SPUser for the AD group, with the IsDomainGroup property set to true).
Does anyone have a good idea how can I obtain a list of all SPUser's, descending into any Active Directory groups contained in a SPGroup? Is there an alternative to SPGroup.ContainsCurrentUser that takes a SPUser parameter?
Based on a blog post I found, I have written the following code:
private static List<SPUser> ListUsers(SPWeb web, SPPrincipal group)
{
try
{
web.Site.CatchAccessDeniedException = false;
var users = new List<SPUser>();
foreach(SPUser user in web.SiteUsers)
{
using(var userContextSite = new SPSite(web.Site.ID, user.UserToken))
{
try
{
using (var userContextWeb = userContextSite.OpenWeb(web.ID))
{
try
{
if (userContextWeb.SiteGroups[group.Name]
.ContainsCurrentUser)
users.Add(user);
}
catch (SPException)
{
// group not found, continue
}
}
}
catch(UnauthorizedAccessException)
{
// user does not have right to open this web, continue
}
}
}
return users;
}
finally
{
web.Site.CatchAccessDeniedException = true;
}
}
I don't like the fact that I have to impersonate every single user, and this code will only find AD users that have already been imported into SharePoint (so an SPUser exists for them), but that's good enough for me.
Unfortunately, it may be the case that not every member of the AD group has a corresponding SPUser object in the site (yet).
In this scenario, I'd enumerate all the members of the active directory group, and force them into the site with the SPWeb's EnsureUser() method, which returns an SPUser, and creates a new one if it doesn't already exist in the site.
For guidance on enumerating active directory members, see Get List of Users From Active Directory In A Given AD Group.
In SharePoint, we have the 3 predetermined permission groups:
Visitors
Members
Owners
As setup in the /_layouts/permsetup.aspx page.
(Site settings->People and Groups->Settings->Setup groups)
How can a get these group names programmatically?
(The page logic is obfuscated by Microsoft, so no can do in Reflector)
There are properties on the SPWeb class:
SPWeb.AssociatedVisitorGroup
SPWeb.AssociatedMemberGroup
SPWeb.AssociatedOwnerGroup
http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spweb.associatedmembergroup.aspx
I've found the various "Associated..." properties to often be NULL. The only reliable way is to use the property bag on the SPWeb:
Visitors: vti_associatevisitorgroup
Members: vti_associatemembergroup
Owners: vti_associateownergroup
To convert them to an SPGroup object, you could use:
int idOfGroup = Convert.ToInt32(web.Properties["vti_associatemembergroup"]);
SPGroup group = web.SiteGroups.GetByID(idOfGroup);
However as Kevin mentions, the associations may be lost which would throw exceptions in the above code. A better approach is to:
Check that associations have been set on the web by ensuring the property you are looking for actually exists.
Check that the group with ID given by the property actually exists. Remove the call to SiteGroups.GetByID and instead loop through each SPGroup in SiteGroups looking for the ID.
The more robust solution:
public static SPGroup GetMembersGroup(SPWeb web)
{
if (web.Properties["vti_associatemembergroup"] != null)
{
string idOfMemberGroup = web.Properties["vti_associatemembergroup"];
int memberGroupId = Convert.ToInt32(idOfMemberGroup);
foreach (SPGroup group in web.SiteGroups)
{
if (group.ID == memberGroupId)
{
return group;
}
}
}
return null;
}
Hey there, I'm Kevin and I'm the PM for SharePoint permissions at Microsoft.
DJ's answer is completely correct, but I'd warn that depending on what you're doing, this might not be the most robust thing to use. Users could blow away those groups and these associations would be lost. I'd definitely look to build some backup logic into whatever you're fetching these for.