Finding user in TFS Project programmatically - c#

We have more than one collection under one TFS server and each collection have more than one project. I want to be able to check if entered username is part of any project level group on TFS server.
So far I am able to connect to TFS, and get all project names under the collection. I need help in finding the group name and then querying those groups to check if user is part of that group or not.
Here is the code I tried -
static void Main(string[] args)
{
NetworkCredential netCred = new NetworkCredential(#"username", #"pwd");
TfsConfigurationServer configServ = new TfsConfigurationServer(new Uri("https://my-tfs.schwab.com/tfs"), netCred);
var tfsAllCols = new List<KeyValuePair<Guid, string>>();
try
{
configServ.Authenticate();
Console.WriteLine("Autheticated in server with ad creds...");
ReadOnlyCollection<CatalogNode> colNodes = configServ.CatalogNode.QueryChildren(
new[] { CatalogResourceTypes.ProjectCollection },
false,
CatalogQueryOptions.None);
foreach (CatalogNode node in colNodes)
{
var colId = new Guid(node.Resource.Properties["InstanceId"]);
TfsTeamProjectCollection teamProjectCollection =
configServ.GetTeamProjectCollection(colId);
tfsAllCols.Add(new KeyValuePair<Guid, string>(colId, teamProjectCollection.Name));
}
//hardcoding the colname for testing
TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri("https://ruby-tfs.schwab.com/tfs/colname/"), netCred);
tpc.EnsureAuthenticated();
// Get the catalog of team project collections
ReadOnlyCollection<CatalogNode> projNodes = tpc.CatalogNode.QueryChildren(
new[] { CatalogResourceTypes.TeamProject },
false, CatalogQueryOptions.None);
}
catch (Exception ex)
{
throw ex;
}
Console.ReadLine();
}

Instead of hard coding, you could use TFSSecurity command-line tool to retrieve the groups and members in team project:
Use /g to list the groups in a team project, in a team project collection, or across Team Foundation Server:
tfssecurity /g [scope] [/collection:CollectionURL] [/server:ServerURL]
Use /imx to display information about the identities that compose the expanded membership of a specified group:
> tfssecurity /imx Identity [/collection:CollectionURL][/server:ServerURL]
If you insist hard coding, you could follow the blog below to query TFS for groups and memberships:
https://blogs.msdn.microsoft.com/vasu_sankaran/2010/10/11/querying-tfs-for-groups-and-memberships/
Update:

If you want to check the user's groups, you can call ims.ReadIdentity() method directly. Here is the simple code for your reference:
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
TfsConfigurationServer tcs = new TfsConfigurationServer(new Uri("http://xxxx:8080/tfs/"));
IIdentityManagementService ims = tcs.GetService<IIdentityManagementService>();
TeamFoundationIdentity tfi = ims.ReadIdentity(IdentitySearchFactor.AccountName, "domain\\username", MembershipQuery.Direct, ReadIdentityOptions.None);
Console.WriteLine("Listing Groups for:" + tfi.DisplayName);
Console.WriteLine("Total " + tfi.MemberOf.Length + " groups.");
IdentityDescriptor[] group = tfi.MemberOf;
foreach (IdentityDescriptor id in group)
{
TeamFoundationIdentity detail = ims.ReadIdentity(IdentitySearchFactor.Identifier, id.Identifier, MembershipQuery.None, ReadIdentityOptions.None);
Console.WriteLine(detail.DisplayName);
}
Console.ReadLine();
}
}
}
If this does not meet your requirement, you can also use this method to get the members of each project group.

Related

C# .NET Core: translate Identity reference SID to display value for Active Directory groups and users

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

How to get Unique Display name for VSTS/TFS identity?

I am a writing a .Net application using the VSTS/TFS Rest .Net libraries and in one place I need to update workitems' System.AssignedTo field values and while I do want to adhere to the new(ish), unique displayname rules for identity work item fields, I have a hard time finding a method to get the Unique display name(s) for given identities.
The old / client object model does have an explicit helper method to get these unique names, but I have not found any rest endpoint nor client api method that would provide the same.
So I am wondering, given a list of identities, how do I get their corresponding unique display names which I can use to unambiguously set identity work item fields?
String collectionUri = "http://collectionurl/";
VssCredentials creds = new VssClientCredentials();
creds.Storage = new VssClientCredentialStorage();
VssConnection connection = new VssConnection(new Uri(collectionUri), creds);
TeamHttpClient thc = connection.GetClient<TeamHttpClient>();
List<IdentityRef> irs = thc.GetTeamMembersAsync("ProjectName","TeamName").Result;
foreach (IdentityRef ir in irs)
{
Console.WriteLine(ir.UniqueName);
Console.WriteLine(ir.DisplayName);
}
You could try the code below to get unique name:
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
TfsConfigurationServer tcs = new TfsConfigurationServer(new Uri("http://tfsserver:8080/tfs"));
IIdentityManagementService ims = tcs.GetService<IIdentityManagementService>();
TeamFoundationIdentity tfi = ims.ReadIdentity(IdentitySearchFactor.AccountName, "[TEAM FOUNDATION]\\Team Foundation Valid Users", MembershipQuery.Expanded, ReadIdentityOptions.None);
TeamFoundationIdentity[] ids = ims.ReadIdentities(tfi.Members, MembershipQuery.None, ReadIdentityOptions.None);
foreach (TeamFoundationIdentity id in ids)
{
if (id.Descriptor.IdentityType == "System.Security.Principal.WindowsIdentity")
{
Console.WriteLine(id.DisplayName);
Console.WriteLine(id.UniqueName);
}
}
Console.ReadLine();
}
}
}
foreach (var workItem in workItems)
{
if (workItem.Fields.ContainsKey("System.AssignedTo"))
{
var person = (IdentityRef)workItem.Fields["System.AssignedTo"];
string codereview_reviewer = person.DisplayName;
Console.WriteLine(codereview_reviewer);
}
}

Using VsConnection WorkItemTrackingHttpClient patch to add parent relation via VSTS client API

I am trying to programmatically add a parent-child relationship between two work items. I am using the Microsoft Team Foundation and Visual Studio Services libraries to export and import TFS 2015 and VSTS backlog objects.
https://learn.microsoft.com/en-us/vsts/integrate/concepts/dotnet-client-libraries
https://www.visualstudio.com/en-us/docs/integrate/api/wit/samples#migrating-work-items
I have worked through obtaining a VssConnection to my servers and getting a WorkItemTrackingHttpClient to execute Wiql queries and create work items. I also have a query to identify the parent of a target work item.
What I cannot figure out is how to add the link between child work items and their parent. I do not know the correct JsonPatchDocument item path to add the parent, or the correct property or method on an existing WorkItem to update it with a parent link. Does anyone have documentation links or specific information on adding a parent relationship to a work item using these libraries?
Here are some code excerpts for context:
using Microsoft.TeamFoundation.Core.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.WebApi;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
// ...
var sourceConnection = new VssConnection(new Uri(_sourceTsUrl), new VssClientCredentials());
var targetConnection = new VssConnection(new Uri(_targetTsUrl), new VssClientCredentials());
var sourceClient = sourceConnection.GetClient<WorkItemTrackingHttpClient>();
var targetClient = targetConnection.GetClient<WorkItemTrackingHttpClient>();
// ...
var queryResults = sourceClient.QueryByWiqlAsync(query).Result;
var ids = queryResults.WorkItems.Select(x => x.Id).ToList();
var items = sourceClient.GetWorkItemsAsync(ids);
foreach (var item in items.Result)
{
// ...
var patchItem = new JsonPatchDocument();
foreach (var fieldName in item.Fields.Keys)
{ patchItem.Add(new JsonPatchOperation() { Path = $"/fields/{fieldName}", Value = item.Fields[fieldName], Operation = Operation.Add }); }
// TODO - add patch field(?) for parent relationship
var parentResults = sourceClient.QueryByWiqlAsync(parentQuery).Result;
// ...
var task = targetClient.CreateWorkItemAsync(patchItem, targetProject, itemType, validateOnly, bypassRules, suppressNotifications);
var newItem = task.Result;
// TODO - alternatively, add parent via the returned newly generated WorkItem
}
Addendum:
I've tried adding the following code, but the changes do not get committed to the remote object, it only exists in local memory, and I cannot find a method to push the changes/updates.
if (!string.IsNullOrWhiteSpace(mappedParentUrl))
{
if (newItem.Relations == null)
{ newItem.Relations = new List<WorkItemRelation>(); }
newItem.Relations.Add(new WorkItemRelation() { Rel = "Parent", Title = mappedParentTitle, Url = mappedParentUrl });
}
Refer to this code to create task work item with parent link (Update it to meet your requirement):
var url = new Uri("https://XXX.visualstudio.com");
var connection = new VssConnection(url, new VssClientCredentials());
var workitemClient = connection.GetClient<WorkItemTrackingHttpClient>();
string projectName = "[project name]";
int parentWITId = 771;//parent work item id
var patchDocument = new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchDocument();
patchDocument.Add(new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation() {
Operation=Operation.Add,
Path= "/fields/System.Title",
Value="parentandchildWIT"
});
patchDocument.Add(new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/relations/-",
Value = new
{
rel = "System.LinkTypes.Hierarchy-Reverse",
url = connection.Uri.AbsoluteUri+ projectName+ "/_apis/wit/workItems/"+parentWITId,
attributes = new
{
comment = "link parent WIT"
}
}
});
var createResult = workitemClient.CreateWorkItemAsync(patchDocument, projectName, "task").Result;

get specific projects that a user is a part of

Here I am using IIdentityManagementService to retrieve a specified user by name. Now I want to retrieve only those team projects which they are a member of and can create tasks/workitems for in TFS. My program allows a user to create a task in TFS and I only want them to be able to see a list of the projects which they have access to for creating tasks/work items.
var tfsTpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("http://dotnettfs:8080/tfs/"));
identityService = tfsTpc.GetService<IIdentityManagementService>();
userId = identityService.ReadIdentity(
IdentitySearchFactor.DisplayName,
strOutlookUser,
MembershipQuery.Direct,
ReadIdentityOptions.None
);
userTpc = new TfsTeamProjectCollection(tfsTpc.Uri, userId.Descriptor);
cssService = (ICommonStructureService4)userTpc.GetService(typeof(ICommonStructureService4));
wis = userTpc.GetService<WorkItemStore>();
lstAllProjects.AddRange(cssService.ListAllProjects().ToList());
List<string> lstViewProjectNames = lstAllProjects.Select(a => a.Name).ToList();
Right now, the list shows all projects within that software collection when I want it to show only those projects which the retrieved user has access to.
then they are able to create a task and specify the iteration and area for one of those projects.
var store = wis.Projects[0]; //should be a specified project, not the first element.
WorkItem pbi = new WorkItem(store.WorkItemTypes["Product Backlog Item"]);
pbi.IterationPath = lstIterations.Where(a => a.Name == selectedIteration.ToString())
.Select(a => a.Path).First().ToString();
pbi.AreaPath = lstAreas.Where(a => a.Name == selectedArea.ToString())
.Select(a => a.Path).First().ToString();
I only want them to be able to see a list of the projects which they
have access to for creating tasks/work items.
Work items are tied to areas and areas are tied to team projects.
The basic steps are:
1) Connect to TFS as the user in question
2) Retrieve the team project in question
3) Get the areas for the team project in question
4) Check each one for the ability to create work items (you can probably get away with doing the recursive check on just the root area node)
The usings you will need (might not need all):
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Server;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
IIdentityManagementService identityManagementService = tpc.GetService<IIdentityManagementService>();
TfsTeamProjectCollection tpc = GetTfsCollection();
TeamFoundationIdentity identity = identityManagementService.ReadIdentity(IdentitySearchFactor.AccountName, #"Domain\username", MembershipQuery.None, ReadIdentityOptions.None);
TfsTeamProjectCollection impersonatedTPC = new TfsTeamProjectCollection(new Uri(this._tfsUri, this._tfsCollectionName), identity.Descriptor);
WorkItemStore impersonatedWIS = impersonatedTPC.GetService<WorkItemStore>();
ProjectCollection impersonatedProjects = impersonatedWIS.Projects;
foreach (Project p in impersonatedProjects)
{
if (p.Name == "My Team Project")
{
NodeCollection areas = p.AreaRootNodes;
foreach (Node area in areas)
{
if (area.HasWorkItemWriteRightsRecursive)
{
//They do have rights
}
}
}
}
Note that I call GetTfsCollection() which my own user defined function (this is just the class that I constructed with, passing in the root tfs uri and the collection name as string). I also didn't put in any exception handling, just showing the basics:
private TfsTeamProjectCollection GetTfsCollection()
{
return TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(this._tfsUri, this._tfsCollectionName));
}

How to determine all the groups a user belongs to (including nested groups) in ActiveDirectory and .NET 3.5

I have an application that uses ActiveDirecotry authorisation and it has been decided that it needs to support nested AD groups, e.g.:
MAIN_AD_GROUP
|
|-> SUB_GROUP
|
|-> User
So, the user in not directly a member of MAIN_AD_GROUP. I'd like to be able to look for the user recursively, searching the groups nested in MAIN_AD_GROUP.
The main problem is that I'm using .NET 3.5 and there is a bug in System.DirectoryServices.AccountManagement in .NET 3.5 whereby the method UserPrincipal.IsMemberOf() will not work for groups with more than 1500 users. So I can't use UserPrincipal.IsMemberOf() and no, I can't switch to .NET 4 either.
I've worked around this last problem with the following function:
private bool IsMember(Principal userPrincipal, Principal groupPrincipal)
{
using (var groups = userPrincipal.GetGroups())
{
var isMember = groups.Any(g =>
g.DistinguishedName == groupPrincipal.DistinguishedName);
return isMember;
}
}
But userPrincipal.GetGroups() only returns the groups of which the user is a direct member.
How can I get this to work with nested groups?
Workaround #1
This bug is reported here at Microsoft Connect along with the following code that works around this issue by manually iterating through the PrincipalSearchResult<Principal> returned objects, catching this exception, and continuing on:
PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();
var iterGroup = groups.GetEnumerator();
using (iterGroup)
{
while (iterGroup.MoveNext())
{
try
{
Principal p = iterGroup.Current;
Console.WriteLine(p.Name);
}
catch (NoMatchingPrincipalException pex)
{
continue;
}
}
}
Workaround #2
Another workaround found here avoids the AccountManagement class, and uses the System.DirectoryServices API instead:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.DirectoryServices;
namespace GetGroupsForADUser
{
class Program
{
static void Main(string[] args)
{
String username = "Gabriel";
List<string> userNestedMembership = new List<string>();
DirectoryEntry domainConnection = new DirectoryEntry(); // Use this to query the default domain
//DirectoryEntry domainConnection = new DirectoryEntry("LDAP://example.com", "username", "password"); // Use this to query a remote domain
DirectorySearcher samSearcher = new DirectorySearcher();
samSearcher.SearchRoot = domainConnection;
samSearcher.Filter = "(samAccountName=" + username + ")";
samSearcher.PropertiesToLoad.Add("displayName");
SearchResult samResult = samSearcher.FindOne();
if (samResult != null)
{
DirectoryEntry theUser = samResult.GetDirectoryEntry();
theUser.RefreshCache(new string[] { "tokenGroups" });
foreach (byte[] resultBytes in theUser.Properties["tokenGroups"])
{
System.Security.Principal.SecurityIdentifier mySID = new System.Security.Principal.SecurityIdentifier(resultBytes, 0);
DirectorySearcher sidSearcher = new DirectorySearcher();
sidSearcher.SearchRoot = domainConnection;
sidSearcher.Filter = "(objectSid=" + mySID.Value + ")";
sidSearcher.PropertiesToLoad.Add("distinguishedName");
SearchResult sidResult = sidSearcher.FindOne();
if (sidResult != null)
{
userNestedMembership.Add((string)sidResult.Properties["distinguishedName"][0]);
}
}
foreach (string myEntry in userNestedMembership)
{
Console.WriteLine(myEntry);
}
}
else
{
Console.WriteLine("The user doesn't exist");
}
Console.ReadKey();
}
}
}
Use UserPrincipal.GetAuthorizationGroups() instead - from its MSDN docs:
This method searches all groups
recursively and returns the groups in
which the user is a member. The
returned set may also include
additional groups that system would
consider the user a member of for
authorization purposes.
The groups that are returned by this
method may include groups from a
different scope and store than the
principal. For example, if the
principal is an AD DS object that has
a DN of
"CN=SpecialGroups,DC=Fabrikam,DC=com,
the returned set can contain groups
that belong to the
"CN=NormalGroups,DC=Fabrikam,DC=com.
I know this is an old thread, but it's the top result on Google, so in case this helps anyone, here's what I came up with that uses the AccountManagement stuff, but makes this particular query much easier.
public static class AccountManagementExtensions
{
public static bool IsNestedMemberOf(this Principal principal, GroupPrincipal group)
{
// LDAP Query for memberOf Nested
var filter = String.Format("(&(sAMAccountName={0})(memberOf:1.2.840.113556.1.4.1941:={1}))",
principal.SamAccountName,
group.DistinguishedName
);
var searcher = new DirectorySearcher(filter);
var result = searcher.FindOne();
return result != null;
}
}
The efficient way is to do a single AD query by having the right DirectorySearcher filter for e.g.
public bool CheckMemberShip(string userName)
{
bool membership = false;
string connection = "LDAP://"+YOURDOMAIN;
DirectoryEntry entry = new DirectoryEntry(connection);
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=cn=GROUPNAME,OU=Groups,OU=ABC,OU=ABC,OU=IND,DC=ad,DC=COMPANY,DC=com)(|(sAMAccountName=" + userName + ")))";
SearchResult result = mySearcher.FindOne();
// No search result, hence no membership
if (result == null)
{
membership = false;
}
entry.Close();
entry.Dispose();
mySearcher.Dispose();
membership = true;
return membership;
}
You need to replace YOURDOMAIN and GROUPNAME with right values from your AD.
Source : How to Recursively Get the Group Membership of a User in Active Directory using .NET/C# and LDAP (without just 2 hits to Active Directory)
Need to include using System.DirectoryServices;

Categories

Resources