User/Group Permissions in Active Directory - c#

Where can I find an example that does the following?
Pulls a user from Active Directory.
Gets the groups the user is a member of.
Gets a list of permissions assigned to each group.
This seems like a simple task but I can't find a solution.
The overall goal is to assign custom permissions and use them to control rights within an application.

If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// do something here....
}
// find the group in question
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, "YourGroupNameHere");
// if found....
if (group != null)
{
// iterate over members
foreach (Principal p in group.GetMembers())
{
Console.WriteLine("{0}: {1}", p.StructuralObjectClass, p.DisplayName);
// do whatever you need to do to those members
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
The last point: permissions. Those aren't stored in Active Directory - and therefore, you can't retrieve those from any AD code.
Permissions are stored on the individual file system items, e.g. files and/or directories - or other objects (like registry keys, etc.). When you have an AD group or user account, you can read it's SID (Security Identifier) property - that SID will show up in ACL's (Access Control Lists) all over Windows - but from the user or group, there's no mechanism to get all permissions it might have anywhere in the machine/server.
Permissions for files and directories can e.g. be retrieved using the .GetAccessControl() method on the FileInfo and DirectoryInfo classes:
FileInfo info = new FileInfo(#"D:\test.txt");
FileSecurity fs = info.GetAccessControl();
DirectoryInfo dir = new DirectoryInfo(#"D:\test\");
DirectorySecurity ds = dir.GetAccessControl();
Deciphering and making sense of those is a whole different story altogether!

Related

List the content of a given directory for different users, Linux

Using .NET Core, C#, Linux
I've searched around a bit and can't seem to find anything. Maybe it's not possible and I need a different approach?
Can someone kindly point me in the direction of how I can go about getting the directory listing for a given path for a specific username?
I am running a web application service as Root but need to check and return directories and files for a given username (no password is available) - to report directories and files that a given username has read-access to.
Say for example "/opt/mydata/" and in there I will have a number of directories that I will manually create and set the permissions for each user group. I.e. "/opt/mydata/user_1_readable" will be returned when I do a directory listing for user1 (because this user is in the respective permissions group, or is the owner, or it is set for everyone to read) but will not be returned for user2 (this user is not in the correct group).
Essentially, I want to "impersonate" or in Linux, do the equivalent of "sudo su user1" and report what directories/files are readable within "/opt/mydata/" for a given user.
I can get the directory listing and files fine running as Root. What I can't do / don't know how to is getting the directory listing for a specific user. The examples I found and tried are all Windows Identity and Windows Security specific.
E.g. I found this example but it seemed to apply to "Mono" which I am not running, but essentially I really want to do something along the lines of this:
// Impersonate a user
using (WindowsIdentity newId = new WindowsIdentity("user1"))
using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
{
var content = _fileProvider.GetDirectoryContents(uri);
}
Is there some third party library or some other way please?
Resource:
Change current Linux user in a C# application running with Mono?
If you look at this issue on .net core repository, Proposal: Expose POSIX functions , it looks like it won't be implemented in .net core, but only in Mono.Posix.NETStandard.
The library is compatible with .net core 2.0, and it shouldn't be too hard to implement this yourself.
You could try something like this with the package to filter which files the user can read.
public UserHasReadPermission(string username, string file)
{
var user = new UnixUserInfo(username);
var file = new UnixFileInfo(file);
// Everyone has read permission
if (file.FileAccessPermissions.HasFlag(FileAccessPermissions.OtherRead))
return true;
// User owns the file and has read permission
if (file.OwnerUser == user && file.FileAccessPermissions.HasFlag(FileAccessPermissions.UserRead))
return true;
// User group owns the file and has read permission
if (file.OwnerGroup == user.Group && file.FileAccessPermissions.HasFlag(FileAccessPermissions.GroupRead))
return true;
return false;
}
Perhaps you want to read the /etc/passwd file to get users' directories?
Once you have that, you can then get all subdirs inside the folders:
List<string> AllFiles = new List<string>();
void ParsePath(string path)
{
string[] SubDirs = Directory.GetDirectories(path);
AllFiles.AddRange(SubDirs);
AllFiles.AddRange(Directory.GetFiles(path));
foreach (string subdir in SubDirs)
ParsePath(subdir);
}

Modifying an existing access control entry

I had made a logic error in the FileSystemRights interpretation which was causing it to always applied Read permission no matter what else was entered.
I'm making a ps cmdlet which is meant to be fed a list of username and modify the permissions for a folder of the same name as the user. From my testing this script will create the new special acl entry for the user for an allow or deny entry however it will not modify the entry if it already exists. I.e. if a user has read access already and I attempt to grant write access it does not change the entry. I am not sure how I would go about modifying the existing permission without completely removing the old permissions.
DirectoryInfo diDirInfo = new DirectoryInfo(FolderName);
DirectorySecurity dsDirSecurity = diDirInfo.GetAccessControl();
//These just interpet the objects for the rights and the allow/deny entries from the command line
FileSystemRights FSR = genFSR();
AccessControlType ACT = genAct();
dsDirSecurity.AddAccessRule(new FileSystemAccessRule(UserName, FSR, ACT));
diDirInfo.SetAccessControl(dsDirSecurity);
I tried ModifyAccessRule and got the same behavior.
FileSystemAccessRule fsaRule = new FileSystemAccessRule(UserName, FSR, ACT);
dsDirSecurity.ModifyAccessRule(AccessControlModification.Add, fsaRule, out modified);
Use ModifyAccessRule instead of AddAccessRule.
See http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.objectsecurity.modifyaccessrule.aspx

How do I dump users from active directory?

I'm struggling a bit with my simple console-dump-program. I connect to AD using
DirectoryEntry entry =
new DirectoryEntry("LDAP://" + domain, username, password);
and from there I recursively loop thru every child by
foreach (DirectoryEntry child in entry.Children)
{
Traverse(child);
}
Then I start getting mambo jambo data, users popping up more then once and null objects so I wonder if the way I handle the AD that it is just a handle and not a copy so its not loaded completely when I start traversing it?
Any tips/pointers on what to do?
If you can, move up to .NET 3.5 and use the new System.DirectoryServices.AccountManagement namespace - much easier to use.
See: Managing Directory Security Principals in the .NET Framework 3.5
Also: you need to understand Active Directory isn't just a flat list of users and groups - it's a hierachical system of OU's (organizational units) that can be nested into one another, and can contain users, groups, computers and more.
So what exactly do you want to do?? Get the users for a given OU (e.g. "Sales")?? Or really get all users from your AD?? You do understand this could take quite a while, depending on the size of your company's AD.......
If you really want to get ALL users and ALL groups from your entire AD - you should probably set up a DirectorySearcher at the root level:
// set search root
DirectoryEntry deRoot = new DirectoryEntry("LDAP://dc=YourCompany,dc=com");
// declare directory searcher
DirectorySearcher dsUsers = new DirectorySearcher(deRoot);
// scope is full subtree, filter defines to search for users
dsUsers.SearchScope = SearchScope.SubTree;
dsUsers.Filter = "(objectCategory=person)";
// define what properties you want to have loaded into your search results
dsUsers.PropertiesToLoad.Add("givenName");
dsUsers.PropertiesToLoad.Add("surname");
dsUsers.PropertiesToLoad.Add("samAccountName");
// loop through results of search
foreach(SearchResult result in dsUsers.FindAll())
{
if(result.Properties["givenName"] != null)
string givenName = result.Properties["givenName"][0].ToString();
if(result.Properties["surname"] != null)
string surname = result.Properties["surname"][0].ToString();
if(result.Properties["sAMAccountName"] != null)
string samAccountName = result.Properties["sAMAccountName"][0].ToString();
}
When reading out the properties of your SearchResult, you need to check to make sure you did actually get a value back - otherwise your assignment will crash and burn....
For the groups, just use this filter instead:
dsUsers.Filter = "(objectCategory=group)";
If you can narrow your search, e.g. to a given OU, you can get much better performance, since the search tree gets smaller and thus the search would be a lot faster. To do so, just define a different LDAP path for your deRoot directory entry (e.g. LDAP://OU=Sales,DC=YourCOmpany,DC=com or whatever OU you want to search in).
Update: as I said - with .NET 3.5, it gets a lot easier still! You need to add a reference to System.DirectoryServices.AccountManagement, and then you can use code something like this using a sort of "query-by-example" approach:
// create a domain context for the current domain
PrincipalContext domain = new PrincipalContext(ContextType.Domain);
// create a principal object decsribing what to search for
UserPrincipal user = new UserPrincipal(domain);
user.IsActive = true;
// create a principal searcher for running a search operation
PrincipalSearcher searcher = new PrincipalSearcher(user);
// run the query
PrincipalSearchResult<Principal> results = searcher.FindAll();
// iterate over all results
foreach (Principal result in results)
{
Console.WriteLine("name: {0}", result.Name);
}
And for searching for groups, just instantiate a GroupPrincipal, set any properties on it and then pass that into the PrincipalSearcher to search for groups.

C# Active Directory Library

Has anyone seen any solid libraries for working with active directory (mainly user related stuff) in C# and asp.net. Am I better off intergrating with asp membership or building something customised.
I took a look at LINQtoAD but it doesnt seem to be active anymore.
Is the System.DirectoryServices assembly and namespace insufficient?
If you're on .NET 3.5, also check out System.DirectoryServices.AccountManagement for much simpler interface when it comes to handling principals - users, groups, computers etc.
Check out this MSDN article as a great intro into S.DS.AD:
Managing Directory Security Principals in the .NET Framework 3.5
Cheers!
You can refer my OSS project which base on ActiveRecord pattern as following(Because it is open source you can find out how to operate the AD with DirectoryEntry, DirectoryEntry is not only support the LDAP protocol but also IIS, WIN and so on, so I develop this lib):
Eg: Update a user AD object.
using (var userObject = UserObject.FindOneByCN(this.ADOperator, “pangxiaoliang”))
{
if(userObject.Email == "example#landpy.com")
{
userObject.Email = "mv#live.cn";
userObject.Save();
}
}
Eg: Query user AD objects.
// 1. CN end with "liu", Mail conatains "live" (Eg: mv#live.cn), job title is "Dev" and AD object type is user.
// 2. CN start with "pang", Mail conatains "live" (Eg: mv#live.cn), job title is "Dev" and AD object type is user.
IFilter filter =
new And(
new IsUser(),
new Contains(PersonAttributeNames.Mail, "live"),
new Is(PersonAttributeNames.Title, "Dev"),
new Or(
new StartWith(AttributeNames.CN, "pang"),
new EndWith(AttributeNames.CN, "liu")
)
);
// Output the user object display name.
foreach (var userObject in UserObject.FindAll(this.ADOperator, filter))
{
using (userObject)
{
Console.WriteLine(userObject.DisplayName);
}
}
Eg: Custom query.
IFilter filter =
new And(
new IsUser(),
new Custom("(!userAccountControl:1.2.840.113556.1.4.803:=2)")
);
// Output the user object display name.
foreach (var userObject in UserObject.FindAll(this.ADOperator, filter))
{
using (userObject)
{
Console.WriteLine(userObject.DisplayName);
}
}
https://landpyactivedirectory.codeplex.com/documentation
And you will find it easy to operate the AD with it, if you have no interest with it please ignore my answer. Any question about AD please contact me :)

How can I convert from a SID to an account name in C#

I have a C# application that scans a directory and gathers some information. I would like to display the account name for each file. I can do this on the local system by getting the SID for the FileInfo object, and then doing:
string GetNameFromSID( SecurityIdentifier sid )
{
NTAccount ntAccount = (NTAccount)sid.Translate( typeof( NTAccount ) );
return ntAccount.ToString();
}
However, this does not work for files on a network, presumably because the Translate() function only works with local user accounts. I thought maybe I could do an LDAP lookup on the SID, so I tried the following:
string GetNameFromSID( SecurityIdentifier sid )
{
string str = "LDAP://<SID=" + sid.Value + ">";
DirectoryEntry dirEntry = new DirectoryEntry( str );
return dirEntry.Name;
}
This seems like it will work, in that the access to "dirEntry.Name" hangs for a few seconds, as if it is going off and querying the network, but then it throws a System.Runtime.InteropServices.COMException
Does anyone know how I can get the account name of an arbitrary file or SID? I don't know much about networking or LDAP or anything. There's a class called DirectorySearcher that maybe I'm supposed to use, but it wants a domain name, and I don't know how to get that either - all I have is the path to the directory I'm scanning.
See here for a good answer:
The best way to resolve display username by SID?
The gist of it is this bit:
string sid="S-1-5-21-789336058-507921405-854245398-9938";
string account = new System.Security.Principal.SecurityIdentifier(sid).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
This approach works for me for non-local SID's over the active directory.
The SecurityReference object's Translate method does work on non-local SIDs but only for domain accounts. For accounts local to another machine or in a non-domain setup you would need to PInvoke the function LookupAccountSid specifying the specific machine name on which the look up needs to be performed.
System.DirectoryServices.AccountManagement.UserPrincipal class (msdn link) has a static function FindByIdentity to convert an SID to a User object. It should be able to work both against the local machine or an LDAP/Active Directory server. I have only used it against active directory.
Here is an example that I have used in IIS:
// Set the search context to a specific domain in active directory
var searchContext = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", "OU=SomeOU,DC=YourCompany,DC=com");
// get the currently logged in user from IIS
MembershipUser aspUser = Membership.GetUser();
// get the SID of the user (stored in the SecurityIdentifier class)
var sid = aspUser.ProviderUserKey as System.Security.Principal.SecurityIdentifier;
// get the ActiveDirectory user object using the SID (sid.Value returns the SID in string form)
var adUser = UserPrincipal.FindByIdentity(searchContext, IdentityType.Sid, sid.Value);
// do stuff to user, look up group membership, etc.
In C#, get the user SID and assign it to a string variable through:
string strUser = System.Security.Principal.WindowsIdentity.GetCurrent().User.ToString();
You will need to use string because the ability to resolve to the UserName supports string. In other words, using var varUser will result in a namespace error.
string strUserName = new System.Security.Principal.SecurityIdentifier(strUser).Translate(typeof(System.Security.Principal.NTAccount)).ToString();
You can also get account name of special accounts like "Everyone" with code like this that will work regardless of user's language settings:
SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
string everyone = everyoneSid.Translate(typeof(System.Security.Principal.NTAccount)).ToString();
Ooh, then it's possible that the LDAP call is not working because you might not be in an Active Directory environment. If this is the case, then each of your machines is responsible for its own identity store. And your first code sample is not working across the network because the machine on which you are executing your code does not know how to resolve the SID that only makes sense on the remote machine.
You really should check if your machines are a part of an Active Directory. You would know this during the logon process. Or you can check by right clicking on "My Computer", select "Properties", the "Computer Name" tab, then see if your computer is part of a domain.
Great. I cribbed some LookupAccountSid() code from here:
http://www.pinvoke.net/default.aspx/advapi32.LookupAccountSid
And that worked, though I had to provide the host name myself. In the case of a UNC path I can just take the first component of it. When it's a mapped drive, I use this code to convert the path to a UNC one:
http://www.wiredprairie.us/blog/index.php/archives/22
It seems to work, so that's how I'll do it, unless someone comes up with a situation in which the first component of a UNC path isn't the host name...
Thank you all for your help.
This one is a stumper. You are in an Active Directory environment right? Just checking:)
Anyhow, instead of binding with sid.Value,
string str = "LDAP://<SID=" + sid.Value + ">";
I would try converting the SID's byte array to an Octet String and bind with that instead.
There is a sweet example here on page 78. This will get you closer. To be honest, I've not tried binding with a SID before. But I've had success binding with a user's GUID though :)
Good luck and let me know how it goes.
Get the current domain:
System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain();
Get a directory entry from ldap and the domain name:
DirectoryEntry de = new DirectoryEntry(string.Format("LDAP://{0}", domain));
Get the sid from an ActiveDirectoryMembershipProvider ActiveDirectoryMembershipUser:
ActiveDirectoryMembershipUser user = (ActiveDirectoryMembershipUser)Membership.GetUser();
var sid = (SecurityIdentifier)user.ProviderUserKey;
Get the username from the SecurityIdentifier:
(NTAccount)sid.Translate(typeof(NTAccount));
Get directory search done on an activedirectory with the domain directory entry and username:
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = string.Format("(SAMAccountName={0})", username);
search.PropertiesToLoad.Add("Name");
search.PropertiesToLoad.Add("displayName");
search.PropertiesToLoad.Add("company");
search.PropertiesToLoad.Add("homePhone");
search.PropertiesToLoad.Add("mail");
search.PropertiesToLoad.Add("givenName");
search.PropertiesToLoad.Add("lastLogon");
search.PropertiesToLoad.Add("userPrincipalName");
search.PropertiesToLoad.Add("st");
search.PropertiesToLoad.Add("sn");
search.PropertiesToLoad.Add("telephoneNumber");
search.PropertiesToLoad.Add("postalCode");
SearchResult result = search.FindOne();
if (result != null)
{
foreach (string key in result.Properties.PropertyNames)
{
// Each property contains a collection of its own
// that may contain multiple values
foreach (Object propValue in result.Properties[key])
{
outputString += key + " = " + propValue + ".<br/>";
}
}
}
Depending on the data in your active directory, you will get a varied response in the output.
Here is a site that has all the user properties I needed:
For all the Windows developers, the answer is LookupAccountSid
LookupAccountSid(null, Sid, username, userSize, domainName, domainSize, sidType);
I am quite sure you will be able to use the accepted answer from here: Determine the LocalSystem account name using C#
Basically, you can translate an instance of the SecurityIdentifier class to type NTAccount, from which you can get the user name. In code:
using System.Security.Principal;
SecurityIdentifier sid = new SecurityIdentifier("S-1-5-18");
NTAccount acct = (NTAccount)sid.Translate(typeof(NTAccount));
Console.WriteLine(acct.Value);

Categories

Resources