I have designed an application which brings the users from the active directory to an MySQL database, and shows them on GUI. It also brings the groups of which a user is a member of.
So, my program works this way:
for(String domain : allConfiguredADomains) {
LdapContext domainCtx = getDomainCtx(domain);
// Bring all users from this domain and store them in DB
getAllUsersForDomain(domain, domainCtx);
// Bring all the groups for every user
getAllGroupsForUsersInTheDomain(domain, domainCtx)
}
void getAllUsersForDomain(String domain, LdapContext domainCtx) {
String filter = "(objectClass=User)"
NamingEnumeration<SearchResult> result = domainCtx.search(domain, filter, ..);
while(result.hasMoreElements()) {
SearchResult searchResult = (SearchResult) result.nextElement();
// Process and store in database
storeUserInDatabase(searchResult);
}
}
void getAllGroupsForUsersInTheDomain(String domain, LdapContext domainCtx) {
List<String> userDistinguishedNames = getAllUsersFromDatabase("distinguishedName");
for(String userDn : userDistinguishedNames) {
String filter = "(&(objectClass=Group)(distinguishedName=" + userDn + "))";
NamingEnumeration<SearchResult> result = domainCtx.search(domain, filter, ..);
List<String> allGroupsOfUser = new List<String>();
while(result.hasMoreElements()) {
SearchResult searchResult = (SearchResult) result.nextElement();
String groupDistinguishedName = searchResult.getAttributes().get("distinguishedName").get();
allGroupsOfUser.add(groupDistinguishedName);
}
// Store them in database
storeAllGroupsOfUserInDatabase(userDn, allGroupsOfUser);
}
}
This application, however, takes lot of time, when there are too many users in the active directory. So, I decided to implement parallelism (using Threading). I divided this using search filter on distinguishedName of a user.
String filter = "(&(objectClass=User)(distinguishedName=a*"))";
and so on.. in each thread while fetching users.
I got better performance, but still not so good. Can someone suggest
a better way ?
Also, I don't have an idea how can I introduce
parallelism while fetching groups ?
If someone has any suggestions to do this better with powershell or C#, please suggest, I am open to technology.
Please note: reading user attribute memberOf does not provide all groups, hence I am fetching groups separately.
I'm not an Active Directory expert - just wanted to share some thoughts.
Threading by alphabet letter allows a maximum of 26 threads. Have you considered creating search threads by some other attributes, group membership etc? This might let you create more threads.
Review the Active Directory docs to see whether there is a way to improve search performance (for example, with a database we could create an index).
Related
I have a custom product type that gets displayed in a custom listing web part. I was trying to cache the items for performance reasons, but it's also important to check user permissions as not all products are visible to all users.
private static IList<TreeNode> GetUniqueProducts(string clientId, string path)
{
var pages = CacheHelper.Cache(cs => GetProducts(cs, path), new CacheSettings(10, "cus|" + clientId));
return GetUniqueProductNamesItems(pages);
}
private static IList<TreeNode> GetProducts(CacheSettings cacheSettings, string rootPath)
{
var pages = DocumentHelper.GetDocuments().Types("CUS.Product")
.Path(rootPath, PathTypeEnum.Children)
.Published().CheckPermissions().ToList();
if (cacheSettings.Cached)
{
cacheSettings.CacheDependency = CacheHelper.GetCacheDependency("nodes|custom|cus.product|all");
}
return pages;
}
However I realise that this is caching the first user's list of products. When in fact I want to store the full list of products in cache - but then check permissions before they get displayed.
The only way of checking permission seems to be as part of a DocumentQuery as per above - but I don't know how to apply that to a cached list of products - or on an individual node.
So is there a good way to achieve what I want? Without having to loop through each node and individually check user is authorised to access the node ?
You are missing the caching part in your code and I am not quire sure about your cache dependencies.
var pages = CacheHelper.Cache(cs =>
{
var result = CMS.DocumentEngine.DocumentHelper.GetDocuments().Types("CUS.Product").Path(rooPath, CMS.DocumentEngine.PathTypeEnum.Children).Published().ToList();
if (cs.Cached) { cs.CacheDependency = CacheHelper.GetCacheDependency("cus.product|all"); }
return result;
},
new CacheSettings(CacheHelper.CacheMinutes(CurrentSite.SiteName), "custom_products"));
If you checking the user read permissions it means you kinda caching per user. Then your cache should be done per user i.e. cachename should be "custom_products"+ UserID.ToString() or something like this.
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!
I am quite stumped with this. In enhancing an existing feature to a SharePoint solution, I found that they were querying the Wss_Content directly. Knowing I should not be using that stored procedure, I used the SharePoint object model to retrieve the users group information from the userName. What's burning me is that it is slower then the stored procedure. Is there a smarter/faster way to get this information? We are using SharePoint 2007. Below is roughly what the function does:
private string GetTitle(string userName)
{
string results = string.Empty;
try
{
SPSite spSite = new SPSite("http://devvm");
SPWeb spWeb = spSite.AllWebs[""];
SPUser user = spWeb.AllUsers["aspnetsqlmembershipprovider:" + userName];
SPGroupCollection groups = user.Groups;
results = groups[0].Name;
}
catch (Exception ex)
{
Console.WriteLine("Unable to find user" + userName);
results = "No Group Found";
}
return results;
}
and the Stored Procedure code is:
SELECT #role=Groups.Title
FROM WSS_Content.DBO.UserInfo Info
LEFT JOIN WSS_Content.DBO.GroupMembership Membership
ON Info.tp_ID=Membership.MemberID
LEFT JOIN Wss_Content.DBO.Groups Groups
ON Membership.GroupID=Groups.ID
WHERE tp_Login='aspnetsqlmembershipprovider:' + #username
FYI the reason I have this in a try-catch is that this is a sub-query to another list that might not have a user associated to the item.
Any help in this would be greatly appreciated.
How are you calling that code? If you are using this inside a console application then yes it will be slower, it needs to fire up the supporting objects that are normally live when using this inside a native sharepoint context.
Also you are referencing an SPSite and SPWeb object without disposing of the object, this has inherent performance issues as well.
I would like to build a ‘User’ Object model for a somewhat typical web application…however I cannot decide how best to design the object model & role system.
Basically I plan to have about 4 user types…which will correspond to user ‘roles’ in the membership provider.
These types will be:
• Worker
• Employer
• Guest
• Admin
The super type is:
• User
In addition – a User could be both a ‘Worker’ & an ‘Employer’ at times.
I would like to use the MS Roles & Membership provider & have navigation UI set to respond to User Role.
My question is:
How can I best design these Users to be flexible (User can be Worker & Employer).
How do I handle the Login / Roles Procedure?
(I am thinking about a User with a Factory for ‘Behavior’ objects (worker behavior, Employer Behavior ) )
For Login-User logins in … finds its role and Casts to its subtype.
Is this how it should be done?
Using just the concept of role by itself has always proven to be in adequate for me. It doesn't provide low enough granularity to control permissions. AS an example you may have a worker role and and an admin role and then in code you use principal.IsInRole("Admin") to check their role to determine if they can modify some value (say salary). Then someone changes their mind and says that supervisors can change salaries but still aren't admins. Now you have to go change you access check to add in another role check. Painful and routine.
So what I do is make a list of all the features in the application and then allow them to be associated in to a role all in the database. The my access checks look like principal.HasPermission("CHANGESALARY"). I load up the users permissions based on the role they are attached to when they log in. This way the business can create as many groups of features they want and name them. They can then be applied to any user.
I create a custom principal object and attach it to the thread so that I can use it in any code throughout the page life cycle. This object has the code for loading the permissions from the database and the methods for checking permissions.
I generally find that the "providers" in the framework are good for a small class of applications and come up short for most needs. By the time you are done bending them to your will, it would have been easier to just write it from scratch.
To be honest, this is probably not a very good solution, but it might help to generate some other ideas.
My Roles are all of the possible combinations of permissions:
Worker, Employee, Guest, Admin, WorkerEmployee, etc
In my code I have an enum for the individual permissions
[Flags]
public enum RolePermissions
{
Guest = 1,
Worker = 2,
Employee = 4,
Admin = 8
}
and I have an enum that corresponds to the Roles in the database. The integer values are the bitwise OR of permissions:
public enum AvailableRoles
{
None = 0,
Guest = RolePermissions.Guest, //1
Worker = RolePermissions.Worker, // 2
Employee = RolePermissions.Employee, // 4
WorkerEmployee = RolePermissions.Worker | RolePermissions.Employee, // 6
Admin = RolePermissions.Admin, // 8
}
Then there's a set of methods I can use to look up permissions and whatnot:
// Used to determine if the currently logged in user has a particular permission (Guest, Worker, Employee, Admin)
public static bool UserHasPermission( RolePermissions rolePermssion )
{
foreach( string role in Roles.GetRolesForUser() )
{
AvailableRoles availableRole = Parse( role );
if( ( (RolePermissions)availableRole & rolePermssion ) == rolePermssion )
return true;
}
return false;
}
// Used to determine whether the currently logged in user is in a specific role
public static bool UserIsInRole( AvailableRoles requestedRole )
{
return UserIsInRole( Membership.GetUser().UserName, requestedRole );
}
// Used to determine whether a specific user is in a specific role
public static bool UserIsInRole( string username, AvailableRoles requestedRole )
{
foreach( string role in Roles.GetRolesForUser( username ) )
{
AvailableRoles actualRole = Parse( role );
if( actualRole == requestedRole )
return true;
}
return false;
}
// Helper method to parse enum
private static AvailableRoles Parse( string role )
{
return (AvailableRoles)Enum.Parse( typeof( AvailableRoles ), role );
}
If you come up with a better method or make improvements, please let me know so I can incorporate it back into my own code. :-)