Getting all Active Directory Properties with DirectorySearcher - c#-4.0

I am accessing Active Directory. If I call it like this
DirectorySearcher srch = new DirectorySearcher(de);
//Filter to return only users and deleted users and not system accounts
srch.Filter = "(|(&(objectCategory=person)(objectClass=user)(sn=*))(&(isDeleted=TRUE)(objectClass=user)))";
srch.SearchScope = SearchScope.OneLevel;
srch.ExtendedDN = ExtendedDN.Standard;
srch.FindAll();
then it returns a list of users with some of the properties... I want to see the "whenChanged" property but when i try adding the line
srch.PropertiesLoad.Add("whenChanged");
then it doesn't return any users. Could this be due to deleted user's not having that property and that it can't uniformly apply all the properties so it returns 0 results? How can I view all the users, both deleted and active and see the "whenChanged" property for all even it results in a null

Several points:
To get deleted objects you need to set srch.Tombstone = true;
Deleted objects are stored under "CN=Deleted Objects,DC=domain,DC=com".
So to search for all users plus deleted objects, would better use domain root as search root and use SearchScope.Subtree as scope
Any attributes added to DirectorySearcher.PropertiesLoad should not remove any results.
This may due to reason other than srch.PropertiesLoad.Add("whenChanged");
Why put sn=* in search? this filter out users whose last name is not set.
Is this intended?
Tested following code that can get the users plus deleted user successfully, plus obtain the "whenChanged" property. Please give a try.
DirectoryEntry de = new DirectoryEntry("LDAP://domain.com/dc=domain,dc=com", "user", "pwd");
DirectorySearcher srch = new DirectorySearcher(de);
//Filter to return only users and deleted users and not system accounts
srch.Filter = "(|(&(objectCategory=person)(objectClass=user)(sn=*))(&(isDeleted=TRUE)(objectClass=user)))";
srch.SearchScope = SearchScope.Subtree;
srch.ExtendedDN = ExtendedDN.Standard;
srch.Tombstone = true;
srch.PropertiesToLoad.Add("whenChanged");
srch.PropertiesToLoad.Add("distinguishedName");
using (SearchResultCollection results = srch.FindAll())
{
foreach (SearchResult result in results)
{
string dn = result.Properties["distinguishedName"][0] as string;
Console.WriteLine("- {0}", dn);
ResultPropertyValueCollection prop = result.Properties["whenChanged"];
if (prop != null)
{
Console.WriteLine(" {0}", (DateTime)prop[0]);
}
}
}

Related

How to check if user with a specific ID exists?

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

Sitecore Droplink for User Roles

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;
}

GroupPrincipal throwing "System.Runtime.InteropServices.COMException (0x8007200A): The specified directory service attribute or value does not exist."

I'm using System.DirectoryServices.AccountManagement to query for a user and then find the groups for that user.
var _principalContext = new PrincipalContext(ContextType.Domain, domainAddress, adContainer, adQueryAccount, adQueryAccountPassword);
var user = UserPrincipal.FindByIdentity(_principalContext, IdentityType.SamAccountName, account);
var userGroups = user.GetGroups();
foreach (var group in userGroups.Cast<GroupPrincipal>())
{
//////////////////////////////////////////////////////
// getting the underlying DirectoryEntry shown
// to demonstrate that I can retrieve the underlying
// properties without the exception being thrown
DirectoryEntry directoryEntry = group.GetUnderlyingObject() as DirectoryEntry;
var displayName = directoryEntry.Properties["displayName"];
if (displayName != null && displayName.Value != null)
Console.WriteLine(displayName.Value);
//////////////////////////////////////////////////////
Console.WriteLine(group.DisplayName);// exception thrown here...
}
I can grab the underlying DirectoryEntry object and dump its properties and values but as soon as the GroupPrincipal.DisplayName property (or any property for that matter) is accessed, it throws the following exception:
"System.Runtime.InteropServices.COMException (0x8007200A): The
specified directory service attribute or value does not exist.\r\n\r\n
at System.DirectoryServices.DirectoryEntry.Bind(Boolean
throwIfFail)\r\n at
System.DirectoryServices.DirectoryEntry.Bind()\r\n at
System.DirectoryServices.DirectoryEntry.get_SchemaEntry()\r\n at
System.DirectoryServices.AccountManagement.ADStoreCtx.IsContainer(DirectoryEntry
de)\r\n at
System.DirectoryServices.AccountManagement.ADStoreCtx..ctor(DirectoryEntry
ctxBase, Boolean ownCtxBase, String username, String password,
ContextOptions options)\r\n at
System.DirectoryServices.AccountManagement.PrincipalContext.CreateContextFromDirectoryEntry(DirectoryEntry
entry)\r\n at
System.DirectoryServices.AccountManagement.PrincipalContext.DoLDAPDirectoryInitNoContainer()\r\n
at
System.DirectoryServices.AccountManagement.PrincipalContext.DoDomainInit()\r\n
at
System.DirectoryServices.AccountManagement.PrincipalContext.Initialize()\r\n
at System.DirectoryServices.Account
Management.PrincipalContext.get_QueryCtx()\r\n at
System.DirectoryServices.AccountManagement.Principal.HandleGet[T](T&
currentValue, String name, LoadState& state)\r\n at
System.DirectoryServices.AccountManagement.Principal.get_DisplayName()\r\n
at ConsoleApplication9.Program.Main(String[] args)"
Why would I be able to dump the raw properties of the underlying DirectoryEntry but not be able to call any of the properties directly on the GroupPrincipal? What would cause this exception? Note that this does not happen on the "Domain Users" group but the subsequent groups, it does...
I found the solution. If I pass the context to the GetGroups method, it works.
var user = UserPrincipal.FindByIdentity(_principalContext, IdentityType.SamAccountName, account);
var userGroups = user.GetGroups(_principalContext);
Apparently, this limits the groups retrieved to the domain associated with the context. Although this is not intuitive because the context was used to retrieve the user in the first place!!!
This leads me to believe there must be groups from other domains being returned previously and permissions were as such to prevent accessing that information.
Why are you using the .GetUnderlyingObject() call? Seems totally superfluous... just use the .SamAccountName property of the GroupPrincipal directly...
Try this:
foreach (var group in userGroups.Cast<GroupPrincipal>())
{
Console.WriteLine(group.SamAccountName);
Console.WriteLine(group.DisplayName);
Console.WriteLine(group.IsSecurityGroup);
}
Seems a lot easier - no?

Get Current Users Group using the SP 2010 javascript Client Side Object Model

I am trying to obtain the current user's SharePoint group name that they belong to. I haven't been able to find a method/property that provides that information. I've only been able to get the current user's username. Is there a property that provides me this information that I am not seeing?
There is no direct method for returning the groups for the current user through javascript.
Here is a post to an MSDN discussion group that describes a work around to return this information.
If you want to know the group Name for checking permissions, a workaround is here.
So basically:
context = new SP.ClientContext.get_current();
web = context.get_web();
var value = web.get_effectiveBasePermissions();
If you need the Group Name, unfortunately there is no direct way to do that. But we can get the current user and get user collection for one group. Then you can check the user collection from one group to see whether it contains the current user.
Get current user: example
Get group collection for the current web: example
Get specified group
var groupCollection = clientContext.get_web().get_siteGroups();
// Get the visitors group, assuming its ID is 4.
visitorsGroup = groupCollection.getById(4);
Get users for the group
var userCollection = visitorsGroup.get_users();
Check the user collection to see whether it contains the specified user.
For a simple demo you can see the following document.
As indicated by Vadim Gremyachev here you can get the current user var currentUser = currentContext.get_web().get_currentUser() then get all the groups var allGroups = currentWeb.get_siteGroups();
From here you can loop through the group to see if your user is in the current group. So if you have a list of groups you want to check, Members, Owners, Viewers, then just use this method to detect if they are in each group.
function IsCurrentUserMemberOfGroup(groupName, OnComplete) {
var currentContext = new SP.ClientContext.get_current();
var currentWeb = currentContext.get_web();
var currentUser = currentContext.get_web().get_currentUser();
currentContext.load(currentUser);
var allGroups = currentWeb.get_siteGroups();
currentContext.load(allGroups);
var group = allGroups.getByName(groupName);
currentContext.load(group);
var groupUsers = group.get_users();
currentContext.load(groupUsers);
currentContext.executeQueryAsync(OnSuccess,OnFailure);
function OnSuccess(sender, args) {
var userInGroup = false;
var groupUserEnumerator = groupUsers.getEnumerator();
while (groupUserEnumerator.moveNext()) {
var groupUser = groupUserEnumerator.get_current();
if (groupUser.get_id() == currentUser.get_id()) {
userInGroup = true;
break;
}
}
OnComplete(userInGroup);
}
function OnFailure(sender, args) {
OnComplete(false);
}
}
// example use
window.IsCurrentUserMemberOfGroup("Members", function (isCurrentUserInGroup){
if(isCurrentUserInGroup){
console.log('yep he is');
} else {
console.log('nope he aint');
}
});

Creating a custom Document Library in SharePoint

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.

Resources