In a Publishing site I have web part that has to show news items from the list that has Audience Targeting field. I am using CAML query to retrieve small number of last news items.
Is it possible to specify Target Audience in the CAML query ? If not, how should I do it ? Retrieve all results and than apply filter in a loop ?
I am practically duplicating Content Query Web Part and I need Audience Targeting in my custom web part.
No, it is not possible to specify audience targeting in a CAML query. I think this has to do with CAML queries being a WSS thing and Audiences being a MOSS Shared Service. What you have to do is to include the audience field in the CAML query, i.e. add a <FieldRef Name='Target_x0020_Audiences'/> to the SPQuery.ViewFields property. Then filter the results code wise by audience on each list item. Use the AudienceManager class to test if the current user is a member of an audience.
Well i found a workaround for this, i was experiencing an issue when trying to check if the current user was a memeber of the audience for a specific publishing page and what the name of that audience was. Here is the workaround i came up with.
// Run through the pages building the list items
foreach (SPListItem li in pages)
{
// Get a reference to the publishing page
PublishingPage p = PublishingPage.GetPublishingPage(li);
// Make sure the page has been approved
if (li.ModerationInformation.Status == SPModerationStatusType.Approved)
{
// Check if this page has an audience
if (string.IsNullOrEmpty(p.Audience))
// Add to everyone list
else
{
// Split the audiences
string[] Audiences = p.Audience.Split(';');
// Check each audience to see if this user can see it
foreach (string audPart in Audiences)
{
AudienceManager audienceManager = new AudienceManager();
// Get a reference to the audience
// IsGuid is an extenstion method i wrtoe
if (audPart.IsGuid())
{
if (audienceManager.Audiences.AudienceExist(new Guid(audPart)))
aud = audienceManager.Audiences[new Guid(audPart)];
}
else
{
if (audienceManager.Audiences.AudienceExist(audPart))
aud = audienceManager.Audiences[audPart];
}
// Ensure we have a reference to the audience
if (aud != null)
{
// store the item in the temp variables
switch (aud.AudienceName)
{
case "All site users":
// Add to everyone list
break;
case "Some List":
if (audienceManager.IsMemberOfAudience(web.CurrentUser.LoginName, aud.AudienceID))
{
// Add to other list
}
break;
case "Other List":
if (audienceManager.IsMemberOfAudience(web.CurrentUser.LoginName, aud.AudienceID))
{
// Add to other list
}
break;
}
}
}
}
}
}
As you can see its really just a mater of checking if the audience exists by using AudienceManager.Audiences.AudienceExist and the getting a reference to it by just using the default accesor AudienceManager.Audiences[GUID];
Related
I have a sharepoint field in a list that can be either a user or a group. Using the Server Object Model, I can identify easily whether the user is a group or not.
However, I cannot find a way to achieve this using the Managed Client Object model. Is there a way to know.
I only managed to make it work by looping the list of groups and checking if the there is a group with the name. Howver, this is not exactly correct or efficient. Maybe there is a way to find out using the ListItem of the user. But I did not see any fields that show that user is administrator. I have also tried EnsureUser. This crashes if the user is not a group. So I could work out by using a try/catch but this would be very bad programming.
Thanks,
Joseph
To do this get the list of users from ClientContext.Current.Web.SiteUserInfoList and then check the ContentType of each item that is returned to determine what it is.
Checking the content type is not very direct though, because all you actually get back from each item is a ContentTypeID, which you then have to look-up against the content types of the user list at ClientContext.Current.Web.SiteUserInfoList.ContentTypes. That look-up will return a ContentType object, and you can read from the Name property of that object to see what the list item is.
So an over simplified chunk of code to do this would be:
using Microsoft.SharePoint.Client;
...
ClientContext context = ClientContext.Current;
var q = from i in context.Web.SiteUserInfoList.GetItems(new CamlQuery()) select i;
IEnumerable<ListItem> Items = context.LoadQuery(q);
context.ExecuteQueryAsync((s, e) => {
foreach (ListItem i in Items) {
//This is the important bit:
ContentType contenttype = context.Web.SiteUserInfoList.ContentTypes.GetById(i["ContentTypeId"].ToString());
context.Load(contenttype); //It's another query so we have to load it too
switch (contenttype.Name) {
case "SharePointGroup":
//It's a SharePoint group
break;
case "Person":
//It's a user
break;
case "DomainGroup":
//It's an Active Directory Group or Membership Role
break;
default:
//It's a mystery;
break;
}
}
},
(s, e) => { /* Query failed */ }
);
You didn't specify your platform, but I did all of this in Silverlight using the SharePoint client object model. It stands to reason that the same would be possible in JavaScript as well.
Try Microsoft.SharePoint.Client.Utilities.Utility.SearchPrincipals(...):
var resultPrincipals = Utility.SearchPrincipals(clientContext, clientContext.Web, searchString, PrincipalType.All, PrincipalSource.All, null, maxResults);
The return type, PrincipalInfo, conveniently has a PrincipalType property which you can check for Group.
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!
From code I've automatically created a lot of similar sites (SPWeb) in my site collection from a site template (in Sharepoint Foundation). Every site has a home page on which I've added the "what's new" web part (found under "Social collaboration").
Even though the web part has several "target lists" (I'd have called it "source lists") added to it on the template site, this connection is lost on the sites created from the template. So I need to programmatically find all these web parts and add the target lists to them. Looping the web parts is not an issue - I've done that before - but I can't seem to find a word on the net on how to go about modifying this particular web part. All I have is a brief intellisense.
I've found out that it recides in the
Microsoft.SharePoint.Applications.GroupBoard.WebPartPages
namespace, but on the lists provided on MSDN this is one of very few namespaces that doesn't have a link to a reference documentation.
Does anyone have any experience of modifying this web part from code? If not, how would you go about to find out? I can't seem to figure out a method for this..
Here is how I did it. It worked really well. I had a feature that created several list instances and provisioned the What's New web part. In the Feature Receiver, I looped through all of the list instances, indexed the Modified field, and then added the list to the web part:
private void ConfigureLists(SPWeb web, SPFeatureReceiverProperties properties)
{
List<Guid> ids = new List<Guid>();
SPElementDefinitionCollection elements =
properties.Feature.Definition.GetElementDefinitions(new CultureInfo((int)web.Language, false));
foreach (SPElementDefinition element in elements)
{
if ("ListInstance" == element.ElementType)
{
XmlNode node = element.XmlDefinition;
SPList list = web.Lists[node.Attributes["Title"].Value];
SPField field = list.Fields[SPBuiltInFieldId.Modified];
if (!field.Indexed)
{
field.Indexed = true;
field.Update();
}
ids.Add(list.ID);
}
}
string targetConfig = string.Empty;
foreach (Guid id in ids)
{
targetConfig += string.Format("'{0}',''\n", id);
}
SPFile file = web.GetFile("Pages/default.aspx");
file.CheckOut();
using (SPLimitedWebPartManager manager = file.GetLimitedWebPartManager(PersonalizationScope.Shared))
{
WhatsNewWebPart webpart = null;
foreach (System.Web.UI.WebControls.WebParts.WebPart eachWebPart in manager.WebParts)
{
webpart = eachWebPart as WhatsNewWebPart;
if (null != webpart)
{
break;
}
}
if (null != webpart)
{
webpart.TargetConfig = targetConfig;
manager.SaveChanges(webpart);
}
}
file.CheckIn("ConfigureWebParts");
file.Publish("ConfigureWebParts");
file.Approve("ConfigureWebParts");
}
If you are unsure about the property, export the web part from the browser, then open the .webpart/.dwp file with a text editor. Somewhere in the xml will be a reference to the source list.
*.webparts are usually easier to modify, just set the property.
*.dwps are harder because you sometimes have to get the property (eg ViewXML), then load it into an XmlDocument, then replace the property, and write the xml document string value back to ViewXML.
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.
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.