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.
Related
I have to loop through all Rows in a table that contain a user field. I have to retrieve those users and do nasty stuff with them:
private void GetUsrInfo(FieldUserValue user, ClientContext clientContext) {
int id=user.LookupId;
Web web = clientContext.Web;
User spuser = web.GetUserById(id);
clientContext.Load(spuser);
clientContext.ExecuteQuery();
Mail = spuser.Email;
}
This works. However these are "old" entries and a lot of these persons do not even exist anymore. The user-field still contains the data of that now abandoned user, but when I try to retrieve the userdata by GetUserById() I retrieve the following exception:
Microsoft.SharePoint.Client.ServerException: User cannot be found.
at
Microsoft.SharePoint.Client.ClientRequest.ProcessResponseStream(Stream
responseStream) at
Microsoft.SharePoint.Client.ClientRequest.ProcessResponse()
Currently I just catch these Exceptions and proceed to the next user.
But this is bad and very slow.
Is there a more smart way? Anything like "if web.UserExists(id)..."?
EDIT
One possible way to check whether or not the user exists, without throwing an error or creating a new user (as result of the web.EnsureUser(#"domain\username") method) is to load the complete collection of users locally and use a LINQ statement to lookup the user by Id.
For example:
UserCollection collUser = ctx.Web.SiteUsers;
ctx.Load(collUser);
ctx.ExecuteQuery();
var user = collUser.Cast<User>().FirstOrDefault(u => u.Id == 1);
if (null != user)
{
Console.WriteLine("User: {0} Login name: {1} Email: {2}",
user.Title, user.LoginName, user.Email);
}
If there is a record where the ID == 1, it will be returned, if not the return value will be null.
Depending on the number of users in the site, this may have performance concerns, however, based on the number of exceptions you expect to generate checking the user ID, this solution may be feasible.
Reference: Csom or rest to verify user
In CiviCRM webform, you can 'enable tag and groups'. Configuring those allows you to create option elements in the webform.
This creates one 'widget', one dropdown or set of checkboxes. I have two field instances where I want the user to select a group - say for example
which mailing lists do you want to receive (a,b,c)
what food are you interested in (d,e,f)
a,b,c,d,e and f are all groups. I can not change that.
How could I do that ?
A technical suggestion below, but first, I'd suggest that your real solution is to not use groups for the second question. Groups are set up nicely to handle mailing lists, but if it's to track interests, you'd be better off setting those up as custom fields. It'll solve this immediate issue, and it'll make it easier to deal with tandem searches and so forth (on list b and likes food d).
Now if you must have them as groups, you can create a fake field and move checkboxes into it using jQuery. Create the fake field with one option that you don't care about, but label it "What food are you interested in", or equivalent. Then, edit the Groups field that CiviCRM generated: label it more specifically as "which mailing lists...", and choose Static Options so it doesn't start offering up just any group for someone to choose.
Now, add the following javascript:
// first remove the dummy checkboxes in your fake field
$('#yourdummyfield .form-item').each( function() { $(this).remove(); });
// now move things into the dummy field
$('#yourdummyfield').append( $('#groupsfield .form-item-d');
$('#yourdummyfield').append( $('#groupsfield .form-item-e');
$('#yourdummyfield').append( $('#groupsfield .form-item-f');
From the form processing perspective, they'll all be evaluated as the "groups" field. However, they'll look separate. For better or worse, this will have to be adjusted as you add new groups fields.
After using Andrew Hunts suggestion for a while, I finally solved this on the server side, in a custom module, using webform logic as described here
http://www.webomelette.com/drupal-webform-submission-presave-hook
Basicly, on presave, I look for 2 custom fields containing group ids (mailing and food in the example). Then I add these to the CiviCRM groups field.
I'll add the code below, which has some more logic:
to make it flexible, I use one hidden field to contain the fieldkey
of the civicrm groups selector to add the other fields in. that
field is called 'the_groups_element' (but its not the groups element, it contains the key of the groups element)
there is only one foods group allowed, so before it adds you to a food group, it removes all other food groups from the groups selector.
You could probably make it even more generic, but since I had different logic for the different groups, this was suitable for me.
function getFoodGroups() {
// return foodgroups
}
function getMailGroups() {
// return mailgroups
}
function MYMODULE_webform_submission_presave($node, &$submission) {
$groupselm = '';
$groups_cid = false;
$foods_cid = false;
$mailings_cid = false;
// http://www.webomelette.com/drupal-webform-submission-presave-hook
foreach($node->webform['components'] as $cid=>$comp) {
if ($comp['form_key']=='the_groups_element') {
$groupselm = $comp['value'];
break;
}
}
if ($groupselm) {
foreach($node->webform['components'] as $cid=>$comp) {
if ($comp['form_key']==$groupselm) $groups_cid = $comp['cid'];
if ($comp['form_key']=='the_foods') $foods_cid = $comp['cid'];
if ($comp['form_key']=='the_mailings') $mailings_cid = $comp['cid'];
}
$group_gids = $submission->data[$groups_cid];
if (!$group_gids) $group_gids=array();
if ($foods_cid!==false && $submission->data[$foods_cid]) {
// remove all current foods
foreach ($group_gids as $gidx=>$group_gid) {
foreach (getFoodGroups() as $foodgroup) {
if ($group_gid==$foodgroup['gid']) {
if ($debug) drupal_set_message('removing foodgroup '.$foodgroup['gid']);
unset($group_gids[$gidx]);
}
}
}
// validate and add submitted regions
$foodsgids = $submission->data[$foods_cid];
if (!is_array($foodsgids)) $foodsgids = array($foodsgids);
foreach ($foodsgids as $foodsgid) {
foreach (getFoodGroups() as $foodgroup) {
if ($foodsgid==$foodgroup['gid']) {
$group_gids[]=$foodsgid;
break; // only one food allowed
}
}
}
}
if ($mailings_cid!==false && $submission->data[$mailings_cid]) {
// just add submitted mailings, dont remove any
$mailinggids = $submission->data[$mailings_cid];
if (!is_array($mailinggids)) $mailinggids = array($mailinggids);
foreach ($mailinggids as $mailinggid) {
foreach (getMailGroups() as $mailing) {
if ($mailinggid==$mailing['gid']) {
if ($debug) drupal_set_message('adding mailing '.$mailing['gid']);
$group_gids[]=$mailinggid;
}
}
}
}
$submission->data[$groups_cid] = array_unique($group_gids);
}
I need to write a service that connects to CRM, and returns with a list of all of the entity available on the server (custom or otherwise).
How can I do this? To be clear, I am not looking to return all data for all entities. Just a list of every type, regardless of whether any actually exist.
You need to use RetrieveAllEntitiesRequest
RetrieveAllEntitiesRequest request = new RetrieveAllEntitiesRequest()
{
EntityFilters = EntityFilters.Entity,
RetrieveAsIfPublished = true
};
// service is the IOrganizationService
RetrieveAllEntitiesResponse response = (RetrieveAllEntitiesResponse)service.Execute(request);
foreach (EntityMetadata currentEntity in response.EntityMetadata)
{
string logicalName = currentEntity.LogicalName;
// your logic here
}
note that you will get also system or hidden entities, like wizardpage or recordcountsnapshot
You will probably find these sections of the MSDN useful:
Customize Entity Metadata (lookout for the samples linked on that page).
Retrieve and Detect Changes to Metadata.
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!
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];