I'm trying to programmatically retrieve a Team's Administrator Users.
For example in a setup like in the picture how can I get 'Billy' as being the Administrator of Team 'QC Manager'?
I already have the code that gets all users in a Team via IIdentityManagementService's ListApplicationGroups, getting the group using FirstOrDefault ... and then getting its users via ReadIdentities.
I've done some poking around in the Team Web Access assemblies, and this seems to be only available on the server side at the moment (no Rest or Client Object Model option available). The code looks like this:
TeamFoundationIdentity identity;
string token = service.GetSecurableToken(requestContext, teamIdentity.Descriptor, out identity);
AccessControlList list = requestContext.GetService<SecurityService>().QueryAccessControlLists(requestContext, FrameworkSecurity.IdentitiesNamespaceId, token, null, false, false).FirstOrDefault<AccessControlList>();
List<IdentityDescriptor> list2 = new List<IdentityDescriptor>();
if (list != null)
{
foreach (AccessControlEntry entry in list.AccessControlEntries)
{
if ((entry.Allow & 8) == 8)
{
list2.Add(entry.Descriptor);
}
}
}
return service.ReadIdentities(requestContext, list2.ToArray());
Where GetSecurableToken looks like:
internal static string CreateSecurityToken(TeamFoundationIdentity group)
{
return (group.GetAttribute(IdentityAttributeTags.LocalScopeId, string.Empty) + FrameworkSecurity.IdentitySecurityPathSeparator + group.TeamFoundationId.ToString());
}
From here you should be able to piece together the code to read and writes these lists. To go poking around yourself, look for the Microsoft.TeamFoundation.Server.Core.dll, Class Microsoft.TeamFoundation.Server.Core.TeamFoundationTeamService to be specific.
If you're able to rewrite it into something useful I'd be grateful and might stick it into the TfsTeamTools, at the moment I don't have much time on my hands to pick this up.
Found this post TFS11 API: Managing Team Administrators; I duplicate code for easy reference, see original post for complete info.
static void Main(string[] args)
{
// Connect to the TFS server and get the team project URI.
var collection = GetServer("server_uri");
var projectUri = GetProjectUri(collection, "project_name");
// Retrieve the default team.
TfsTeamService teamService = collection.GetService<TfsTeamService>();
TeamFoundationTeam defaultTeam = teamService.GetDefaultTeam(projectUri, null);
// Get security namespace for the project collection.
ISecurityService securityService = collection.GetService<ISecurityService>();
SecurityNamespace securityNamespace = securityService.GetSecurityNamespace(FrameworkSecurity.IdentitiesNamespaceId);
// Use reflection to retrieve a security token for the team.
MethodInfo mi = typeof(IdentityHelper).GetMethod("CreateSecurityToken", BindingFlags.Static | BindingFlags.NonPublic);
string token = mi.Invoke(null, new object[] { defaultTeam.Identity }) as string;
// Retrieve an ACL object for all the team members.
var allMembers = defaultTeam.GetMembers(collection, MembershipQuery.Expanded).Where(m => !m.IsContainer);
AccessControlList acl = securityNamespace.QueryAccessControlList(token, allMembers.Select(m => m.Descriptor), true);
// Retrieve the team administrator SIDs by querying the ACL entries.
var entries = acl.AccessControlEntries;
var admins = entries.Where(e => (e.Allow & 15) == 15).Select(e => e.Descriptor.Identifier);
// Finally, retrieve the actual TeamFoundationIdentity objects from the SIDs.
var adminIdentities = allMembers.Where(m => admins.Contains(m.Descriptor.Identifier));
}
Related
I'm switching an API from .NET Framework to .NET Core and running into some differences in the behavior of Directory.Services.AccountManagement vs System.IO.FileSystem.AccessControl in .NET Core 3.1.
The issue in the .NET Core code is I'm trying to get the Active Directory group name (or user name) for each rule for a file. I can get the sid, but the FileSystemAccessRule.IdentifyReference.Translate() method throws an exception for AD groups.
Here's what I have: I can get the SID in the id variable, but I need the actual group name (or user name)
using System.Security.AccessControl;
using System.Security.Principal;
namespace ConsoleCoreAclsPoc
{
class Program
{
static void Main(string[] args)
{
var fn = args[0];
var rules = new FileSecurity(fn, AccessControlSections.All | AccessControlSections.Access)
.GetAccessRules(true,true,typeof(NTAccount));
foreach (AuthorizationRule rule in rules)
{
FileSystemAccessRule fileRule = rule as FileSystemAccessRule;
if (fileRule != null)
{
var id = fileRule.IdentityReference;//.Translate(typeof(NTAccount));
var read = fileRule.FileSystemRights.HasFlag(FileSystemRights.ReadAndExecute);
var write = fileRule.FileSystemRights.HasFlag(FileSystemRights.Modify);
var admin = fileRule.FileSystemRights.HasFlag(FileSystemRights.FullControl);
if (!admin)
{
// do stuff to the non-admins
}
}
}
}
}
}
I tested your code and found an exception in Access Control Sections. The compiler does not allow access to all security When you want to access the permissions of a file. Change AccessControlSections to AccessControlSections.Access. Then I did not find any error.
var rules = new FileSecurity(fn, AccessControlSections.Access)
.GetAccessRules(true, true, typeof(NTAccount));
I need to read all users from the AD. Here is code that I am using:
using Novell.Directory.Ldap;
using Novell.Directory.Ldap.Controls;
using System.Linq;
namespace LdapTestApp
{
class Program
{
static void Main()
{
LdapConnection ldapConn = new LdapConnection();
ldapConn.SecureSocketLayer = true;
ldapConn.Connect(HOST, PORT);
try
{
var cntRead = 0;
int? cntTotal = null;
var curPage = 0;
ldapConn.Bind(USERNAME, PASSWORD);
do
{
var constraints = new LdapSearchConstraints();
constraints.SetControls(new LdapControl[]
{
new LdapSortControl(new LdapSortKey("sn"), true),
new LdapVirtualListControl("sn=*", 0, 10)
});
ILdapSearchResults searchResults = ldapConn.Search(
"OU=All Users,DC=homecredit,DC=ru",
LdapConnection.ScopeSub,
"(&(objectCategory=person)(objectClass=user))",
null,
false,
constraints
);
while (searchResults.HasMore() && ((cntTotal == null) || (cntRead < cntTotal)))
{
++cntRead;
try
{
LdapEntry entry = searchResults.Next();
}
catch (LdapReferralException)
{
continue;
}
}
++curPage;
cntTotal = GetTotalCount(searchResults as LdapSearchResults);
} while ((cntTotal != null) && (cntRead < cntTotal));
}
finally
{
ldapConn.Disconnect();
}
}
private static int? GetTotalCount(LdapSearchResults results)
{
if (results.ResponseControls != null)
{
var r = (from c in results.ResponseControls
let d = c as LdapVirtualListResponse
where (d != null)
select (LdapVirtualListResponse)c).SingleOrDefault();
if (r != null)
{
return r.ContentCount;
}
}
return null;
}
}
}
I used this question Page LDAP query against AD in .NET Core using Novell LDAP as basis.
Unfortunatelly I get this exception when I am trying to recieve the very first entry:
"Unavailable Critical Extension"
000020EF: SvcErr: DSID-03140594, problem 5010 (UNAVAIL_EXTENSION), data 0
What am I doing wrong?
VLVs are browsing indexes and are not directly related to the possibility or not to browse large numbers of entries (see generic documentation). So even if this control would be activated on your AD, you wouldn't be able to retrieve more than 1000 elements this way :
how VLVs work on AD
MaxPageSize is 1000 by default on AD (see documentation)
So what you can do:
use a specific paged results control, but it seems that the Novell C# LDAP library does not have one
ask you the question: "is this pertinent to look for all the users in a single request?" (your request looks like a batch request: remember that a LDAP server is not designed for the same purposes than a classic database - that can easily return millions of entries - and that's why most of LDAP directories have default size limits around 1000).
The answer is no: review your design, be more specific in your LDAP search filter, your search base, etc.
The answer is yes:
you have a single AD server: ask your administrator to change the MaxPageSize value, but this setting is global and can lead to several side effects (ie. what happens if everybody start to request all the users all the time?)
you have several AD servers: you can configure one for specific "batch like" queries like the one you're trying to do (so large MaxPageSize, large timeouts etc.)
I had to use approach described here:
https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard/issues/71#issuecomment-420917269
The solution is far from being perfect but at least I am able to move on.
Starting with version 3.5 the library supports Simple Paged Results Control - https://ldapwiki.com/wiki/Simple%20Paged%20Results%20Control - and the usage is as simple as ldapConnection.SearchUsingSimplePaging(searchOptions, pageSize) or ldapConnection.SearchUsingSimplePaging(ldapEntryConverter, searchOptions, pageSize) - see Github repo for more details - https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard and more specifically use the tests as usage samples.
I have been searching for quite some time for a solution using C# code that can query an Active Directory user for all the attributes it has registered to it, whether or not they have a NULL Value. These attributes are visible through the Attribute editor tab in the properties of the user in ADSI Edit on the domain server.
AD user attributes in ADSI edit
I need to dynamically retrieve these attributes, which means I probably can't reliably get these attribute names through the ADSI documentation on MSDN and because not all of these attributes might be user object specific:
https://msdn.microsoft.com/en-us/library/ms675090(v=vs.85).aspx
Here is what I have tried so far, but only got a fraction of the attributes of the user object:
PS command Get-ADUser -Identity administrator -Properties: This retrieved a good part of the attributes, but not nearly all of them and I do not know what .NET Classes and methods are invoked during this command, since TypeName = Microsoft.ActiveDirectory.Management.ADUser, which does not exist in the .NET framework. How can I see the specific methods that are using from .NET in PS?
C# calling this method:
public bool GetUserAttributes(out List<string> userAttributes, string userName)
{
userAttributes = new List<string>();
var valueReturn = false;
try
{
const string pathNameDomain = "LDAP://test.local";
var directoryEntry = new DirectoryEntry(pathNameDomain);
var directorySearcher = new DirectorySearcher(directoryEntry)
{
Filter = "(&(objectClass=user)(sAMAccountName=" + userName + "))"
};
var searchResults = directorySearcher.FindAll();
valueReturn = searchResults.Count > 0;
StreamWriter writer = new StreamWriter("C:\\LDAPGETUSERADEXAMPLE.txt");
foreach (SearchResult searchResult in searchResults)
{
foreach (var valueCollection in searchResult.Properties.PropertyNames)
{
userAttributes.Add(valueCollection.ToString() + " = " + searchResult.Properties[valueCollection.ToString()][0].ToString());
try
{
writer.WriteLine("Bruger attribut:" + valueCollection);
}
catch (Exception)
{
throw;
}
}
}
C# calling this method:
public List<string> GetADUserAttributes()
{
string objectDn = "CN=testuser,OU=TEST,DC=test,DC=local";
DirectoryEntry objRootDSE = new DirectoryEntry("LDAP://" + objectDn);
List<string> attributes = new List<string>();
foreach (string attribute in objRootDSE.Properties.PropertyNames)
{
attributes.Add(attribute);
}
return attributes;
}
What should I do to not filter out any attributes of the user object I am trying to retrieve from?
I am aware that Active Directory by default will only shows attributes that are default or have a value in them, I am trying to overcome this limitation.
EDIT 1:
I have temporarily postponed the specific question.
I have been trying to benchmark which of these methods are the fastest at retrieving (READ Operation) the SAM account name of 10.000 individual AD users called for example "testuser", the methods I benchmark are the following:
Time to complete: about 500 msec : ADSI - system.directoryservices
Time to complete: about 2700 msec: Principal - searcher system.directoryservices.accountmanagement
Time to complete: about NOT WORKING :LDAP - System.DirectoryServices.Protocols
Time to complete: about 60 msec : SQL - System.Data.SqlClient
I am querying for the user information from a workstation - Windows 10 machine in the domain I am querying. the workstation (4 vcpu), DC (2vpu) and DB (2vcpu) server is run as Hyper V vm's.
All attributes that any class can have are defined in Active Directory Schema
Use this to query for the user class. Then just call GetAllProperties method
var context = new DirectoryContext(DirectoryContextType.Forest, "amber.local");
using (var schema = System.DirectoryServices.ActiveDirectory.ActiveDirectorySchema.GetSchema(context))
{
var userClass = schema.FindClass("user");
foreach (ActiveDirectorySchemaProperty property in userClass.GetAllProperties())
{
// property.Name is what you're looking for
}
}
However AD schema may vary from one AD environment to another. For example, third party programs or Exchange Server may extend schema with custom attributes. It means that the solution with pre-defined columns will work only for a specific environment.
We are having an issue with searching a custom record through SuiteTalk. Below is a sample of what we are calling. The issue we are having is in trying to set up the search using the internalId of the record. The issue here lies in in our initial development account the internal id of this custom record is 482 but when we deployed it through the our bundle the record was assigned with the internal Id of 314. It would stand to reason that this internal id is not static in a site per site install so we wondered what property to set up to reference the custom record. When we made the record we assigned its “scriptId’ to be 'customrecord_myCustomRecord' but through suitetalk we do not have a “scriptId”. What is the best way for us to allow for this code to work in all environments and not a specific one? And if so, could you give an example of how it might be used.
Code (C#) that we are attempting to make the call from. We are using the 2013.2 endpoints at this time.
private SearchResult NetSuite_getPackageContentsCustomRecord(string sParentRef)
{
List<object> PackageSearchResults = new List<object>();
CustomRecord custRec = new CustomRecord();
CustomRecordSearch customRecordSearch = new CustomRecordSearch();
SearchMultiSelectCustomField searchFilter1 = new SearchMultiSelectCustomField();
searchFilter1.internalId = "customrecord_myCustomRecord_sublist";
searchFilter1.#operator = SearchMultiSelectFieldOperator.anyOf;
searchFilter1.operatorSpecified = true;
ListOrRecordRef lRecordRef = new ListOrRecordRef();
lRecordRef.internalId = sParentRef;
searchFilter1.searchValue = new ListOrRecordRef[] { lRecordRef };
CustomRecordSearchBasic customRecordBasic = new CustomRecordSearchBasic();
customRecordBasic.recType = new RecordRef();
customRecordBasic.recType.internalId = "314"; // "482"; //THIS LINE IS GIVING US THE TROUBLE
//customRecordBasic.recType.name = "customrecord_myCustomRecord";
customRecordBasic.customFieldList = new SearchCustomField[] { searchFilter1 };
customRecordSearch.basic = customRecordBasic;
// Search for the customer entity
SearchResult results = _service.search(customRecordSearch);
return results;
}
I searched all over for a solution to avoid hardcoding internalId's. Even NetSuite support failed to give me a solution. Finally I stumbled upon a solution in NetSuite's knowledgebase, getCustomizationId.
This returns the internalId, scriptId and name for all customRecord's (or customRecordType's in NetSuite terms! Which is what made it hard to find.)
public string GetCustomizationId(string scriptId)
{
// Perform getCustomizationId on custom record type
CustomizationType ct = new CustomizationType();
ct.getCustomizationTypeSpecified = true;
ct.getCustomizationType = GetCustomizationType.customRecordType;
// Retrieve active custom record type IDs. The includeInactives param is set to false.
GetCustomizationIdResult getCustIdResult = _service.getCustomizationId(ct, false);
foreach (var customizationRef in getCustIdResult.customizationRefList)
{
if (customizationRef.scriptId == scriptId) return customizationRef.internalId;
}
return null;
}
you can make the internalid as an external property so that you can change it according to environment.
The internalId will be changed only when you install first time into an environment. when you deploy it into that environment, the internalid will not change with the future deployments unless you choose Add/Rename option during deployment.
I'm using the Business Objects Web Services SDK to access our Business Objects data. I've successfully got a list of reports, and from that found the LastSuccessfulInstance of a report that has been previously run. However, I can't seem to get the LastRunTime to be populated. When I do a query with no attributes specified it comes back as not set, and I get the same result when I ask for that attribute in particular. I've looked at the report itself and the instance and they both don't have this information. Does anyone know where I can get it from?
Here's my code (hacked from one of SAP's demos):
var sessConnUrl = serviceUrl + "/session";
var boConnection = new BusinessObjects.DSWS.Connection(sessConnUrl);
var boSession = new Session(boConnection);
// Setup the Enterprise Credentials used to login to the Enterprise System
var boEnterpriseCredential = new EnterpriseCredential
{
Domain = cmsname,
Login = username,
Password = password,
AuthType = authType
};
// Login to the Enterprise System and retrieve the SessionInfo
boSession.Login(boEnterpriseCredential);
/************************** DISPLAY INBOX OBJECTS *************************/
// Retrieve the BIPlatform Service so it can be used to add the USER
var biPlatformUrl = boSession.GetAssociatedServicesURL("BIPlatform");
var boBiPlatform = BIPlatform.GetInstance(boSession, biPlatformUrl[0]);
// Specify the query used to retrieve the inbox objects
// NOTE: Adding a "/" at the end of the query indicates that we want to
// retrieve the all the objects located directly under the inbox.
// Without the "/" Path operator, the inbox itself would be returned.
const string query = "path://InfoObjects/Root Folder/Reports/";
// Execute the query and retrieve the reports objects
var boResponseHolder = boBiPlatform.Get(query, null);
var boInfoObjects = boResponseHolder.InfoObjects.InfoObject;
// If the reports contains a list of objects, loop through and display them
if (boInfoObjects != null)
{
// Go through and display the list of documents
foreach (var boInfoObject in boInfoObjects)
{
var report = boInfoObject as Webi;
if (report == null)
continue;
if (!string.IsNullOrEmpty(report.LastSuccessfulInstanceCUID))
{
var instanceQuery = "cuid://<" + report.LastSuccessfulInstanceCUID + ">";
var instanceResponseHolder = boBiPlatform.Get(instanceQuery, null);
var instance = instanceResponseHolder.InfoObjects.InfoObject[0];
}
}
}
Both report.LastRunTimeSpecified and instance.LastRunTimeSpecified are false and both LastRunTime are 01\01\0001, but I can see a last run time in the Web Intelligence UI.
With a little help from Ted Ueda at SAP support I figured it out. Not all the properties are populated by default you need to append #* to the query string to get everything, i.e. change the line:
const string query = "path://InfoObjects/Root Folder/Reports/";
to:
const string query = "path://InfoObjects/Root Folder/Reports/#*";