How to use service account to impersonate users in Azure Devops - c#

I created a service account to impersonate users in my organization in order to make changes to work items in the users’ name. I already added that service account to the group “Project Collection Service Accounts”, which has “Make requests on behalf of others” set to “Allow”. The service account has Visual Studio Subscription.
I then used my code, which is working with our on-premise TFS, to execute the impersonation and I got an error 500 saying that “Access Denied: X needs the following permission(s) to perform this action: Make requests on behalf of others”
What should I do to make it works? Here the code I’m using:
var credential = new VssAadCredential("X#myoganization", "password");
var collection = new TfsTeamProjectCollection(new Uri("my_devops_uri"), credential);
MyTfsConnection.ProjectCollection = collection;
MyTfsConnection.IdentityService = collection.GetService<IIdentityManagementService>();
MyTfsConnection.WIStore = collection.GetService<WorkItemStore>();
var adAccount = "someone#myoganization";
var identity = MyTfsConnection.IdentityService.ReadIdentity(IdentitySearchFactor.AccountName, adAccount, MembershipQuery.None, ReadIdentityOptions.None);
using (var impersonatedCollection = new TfsTeamProjectCollection(new Uri("my_devops_uri"), credential, identity.Descriptor))
{
var impersonatedWIStore = impersonatedCollection.GetService<WorkItemStore>();
}

It's not able to do this, Service account is not intended to connect the client with server either check in code or change work items.
Service account is used to run various services related to TFS. It should have the minimum privileges possible on the machine.
The client should not connect to the server with a service account, they should be using their own account which you grant access to the relevant repositories in TFS. For example, if you connect all clients with the service account, how will you know who checked in each changeset, who should assign work items to?
You will also not able to assign work items to a service account.

I've been attempting to migrate code to DevOps Services that uses impersonation to alter work items in another users name as well. Uncanny how similar it is to yours. It's almost like we all grabbed it from this old post on TFS impersonation. Like yours, this code works with on-premises DevOps Server 2019.1.1 but I ran into the same issue trying to get it working with DevOps Services.
During my search for an answer that works with the TFS / DevOps (SOAP) client API and DevOps Services, I ran across this SO question where the question quotes the following.
"For security reasons (and compliance and a number of other reasons),
the impersonation header isn't supported on Visual Studio Online"
I've yet to find this same information anywhere in the documentation. However, it appears to be true. Impersonation is disabled in Azure DevOps Services. Drat!
Not giving up, my search also turned up the following from the November 2017 release notes, which appears promising.
Grant the bypassrule permission to specific users
Often, when migrating work items from another source, organizations
want to retain all the original properties of the work item. For
example, you may want to create a bug that retains the original
created date and created by values from the system where it
originated.
The API to update a work item has a bypassrule flag to enable that
scenario. Previously the identity who made that API request had to be
member of the Project Collection Administrators group. With this
deployment we have added a permission at the project level to execute
the API with the bypassrule flag.
However, I find no such permission in the collection / organization or project permissions on either DevOps Server or DevOps Services. Drat again! That led me to this SO answer where a work item update is crafted in JSON using the REST API directly. So the bypass rule option must still be valid, just not exposed as a settable permission.
So I started looking at the TFS / DevOps (SOAP) client API again and found the WorkItemStoreFlags.BypassRules flag to pass to the WorkItemStore when it is created. The following should provide the basic mechanics.
// Use a personal access token with Work Items scope
var credentials = new VssBasicCredential(String.Empty, "Your PAT");
// Connect with TFS / DevOps client libs.
// Older SOAP based client but still works with DevOps Services.
var teamProjectCollection = new TfsTeamProjectCollection(new Uri("https://dev.azure.com/your-org"), credentials);
// Don't use teamProjectCollection.GetService<WorkItemStore>().
// New up WorkItemStore using the collection and explicitly specify the BypassRules flag.
// This allows you to set the CreatedBy field later.
var workItemStore = new WorkItemStore(teamProjectCollection, WorkItemStoreFlags.BypassRules);
// Get the project, work item type, and create the new work item.
var project = workItemStore.Projects["YourProject"];
var workItemType = project.WorkItemTypes["Product Backlog Item"];
var workItem = new WorkItem(workItemType);
// Set the work item fields
workItem.Title = "The Title";
// Without the BypassRules flag the CreatedBy value set here will be ignored on
// Save() and replaced with the user account attached to the PAT used to authenticate.
workItem.Fields[CoreField.CreatedBy].Value = "NotYourUser#yourdomain.com";
workItem.Fields[CoreField.AssignedTo].Value = "NotYourUser#yourdomain.com";
workItem.Save();

Related

How to start using Service principal already created to update WorkItem in devops

I am looking forward to setup a Service Principal in the main Devops of my company to use as Token access to update or create work items with the Devops Api inside an application in C#...
We are already using the api but with personal tokens, as we know this is not the best practice, because in case any person goes off work their personal access tokens will expires...
So, in order with that I followed this guide: https://cann0nf0dder.wordpress.com/2020/09/27/programmatically-connecting-to-azure-devops-with-a-service-principal-subscription/
Then I added the service principal into the azure active directory group that has all of our users ( the ppl who access into devops )
public void UpdateAzureDevopsPullReviewed(List<int> user_story_numbers, string assigned_to)
{
#region Azure DevOps data connection
Uri orgUrl = new Uri("https://dev.azure.com/nfpnso/");
String tokenWrite = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
//create a connection
VssConnection connection = new VssConnection(orgUrl, new VssBasicCredential(string.Empty, tokenWrite));
#endregion
PullReviewedWorkItem(connection, user_story_numbers, assigned_to).Wait();
}
What I did in Azure was create a new APP registration, in Active Directory, there I got Application (client) ID, Directory (tenant) ID, Object ID and then I created a new secret, this means the ID and the Value ... probe with all these strings and the connection is not executed, it tells me that I am not authorized to access the devops .
I don't think you can use a service principal to call the Azure DevOps API.
Please see Choosing the right authentication mechanism.
Pay attention to the Note tip:
The Azure DevOps API doesn't support non-interactive service access
via service principals.
The only Non-interactive client-side type is Device Profile Authentication mechanism.

What permission is required to get the WorkHours of a calendar?

Background
I am trying to connect to Office 365, to read the calendars of users that have granted permission. I have tried two options:
Exchange Web Services (EWS)
Microsoft Graph
EWS works but has the downside that I use login/password combinations to connect. Even though I store them encrypted, I'd rather not store them at all.
Microsoft Graph works as well, but has a gigantic downside; any updates I make to an item using the API is sent to all attendees. This behavior can be turned off when using the EWS API, but not (yet?) for Graph.
I'd like to take the OAuth implementation I have for Microsoft Graph, and use the EWS service to connect. No updates to attendees unless users want them, and no stored credentials.
The problem
For my application to work properly, I need to;
Get the timezone of the calendar, which I do by reading the person's work hours;
Read and write calendar items, which is the purpose of the application.
I have already established a connection with OAuth to Office365, using OAuth.
I cannot figure out the smallest subset of permissions I need. I have not found any documentation regarding this. Any subset of rights I tried, I get a 401 when I ask for WorkHours.
Minimal Code sample
This will work when I enable 38 non-admin permissions that the app registration for Exchange Online supports, but will fail for every subset I have tried.
[TestMethod]
public void ConnectUsingEws()
{
var accessToken = "eyJz93a...k4laUWw";
var credentials = new OAuthCredentials(accessToken);
var service = new ExchangeService(TimeZoneInfo.Utc);
service.Url = new Uri("https://outlook.office365.com/EWS/exchange.asmx");
service.TraceEnabled = true;
service.TraceFlags = TraceFlags.All;
service.Credentials = credentials;
// This next line is where the service will always throw a 401.
var workHours = UserConfiguration.Bind(service, "WorkHours",
WellKnownFolderName.Calendar, UserConfigurationProperties.All);
// Do some XML magic on workHours to get the timezone.
}
TLDR
I'm sure it's one permission that needs to be enabled, and I'm also fairly certain it's not one that's very obvious.
EWS doesn't support the same level of Permission scopes that REST does with Oauth (which is a big downside of using EWS for a security perspective).
OAuth authentication for EWS is only available in Exchange as part of
Office 365. EWS applications require the "Full access to user's
mailbox" permission.
ref https://msdn.microsoft.com/en-us/library/office/dn903761(v=exchg.150).aspx

Why am i getting authorization errors while using a personal access token?

I'm using the Visual Studio Team Services .NET libraries to perform source control through a class library of mine. I'm also using a personal access token for authentication, previously set through the VSTS web client.
I'm trying to perform basic actions like check in, out, adding pending changes, create folder mappings etc, and i'm getting an "attempted to perform an unauthorized operation" error when i previously add no problem doing these tasks. I was running my .dll on the server it's supposed to be running when it's done and because i was getting this error i tweaked a few lines of code and then just tested the whole thing on my machine again. It doesn't work anymore at all.
What's weird, though, is that when i try to change the working folder mapping for example, i get this error but the new local path gets assigned just fine.
Any reason why an authenticated VSTS user would have these problems?
Using PAT (personal access token) to authorize your .NET libraries, you should VssBasicCredential, such as below example:
string personalAccessToken = "bnsz6p2efh3vljhjoay4rnaznliygu9vngoqgcwel7gwlati8cxq";
VssBasicCredential credentials = new VssBasicCredential("", personalAccessToken);
More details, you can refer .NET client libraries.
Besides, you can also use Alternate authentication credentials. VSTS account -> security -> Alternate authentication credentials (https://account.visualstudio.com/_details/security/altcreds) -> Enable alternate authentication credentials -> set secondary username and password -> save.
Then you can authorize your .NET libraries by:
NetworkCredential credentials = new NetworkCredential("secondary username", "password for secondary username");
TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri("https://account.visualstudio.com"), credentials);

Make Changes to a TFS Work Item as a specific user

I am working on creating a Web application, which the users in my team will use to
make changes to TFS Work Items. I am using TFS API for this..
In order to access the TFS Server , I used my credentials within the Web Application.
Now each time someone uses the application and makes changes to TFS work items, it shows
as if I have made changes to these items since my credentials are being used in the application.
Is there a way I can use the credentials of the person logging into my application to show up on TFS as the person making the changes ?
You need to use the 'make requests on behalf of others' functionality. You can impersonate another user by following:
public void Impersonation(Uri serverUri,string userToImpersonate)
{
// Read out the identity of the user we want to impersonate
TeamFoundationIdentity identity = ims.ReadIdentity(IdentitySearchFactor.AccountName,
userToImpersonate,
MembershipQuery.None,
ReadIdentityOptions.None);
tfs_impersonated = new TfsTeamProjectCollection(serverUri, identity.Descriptor);
GetAuthenticatedIdentity(tfs_impersonated);
// Use this tfs_impersonated object to communicate to TFS as the other users.
}
And make sure your account running the website has the permission to "make requests on behalf of others":
http://www.codeproject.com/Articles/104019/TFS-API-Part-TFS-Impersonation

TFS API - how do I return only projects that an authenticated user has permissions to, instead of the whole list?

I'm currently returning a list of projects from TFS using the api.
var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("some URI"));
var store = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
var projects = store.Projects
This works fine. However, it returns our full list of TFS Team Projects for every user. Is there a way to return or filter the list such that only the projects a particular user has access to are returned?
This is using TFS 2010.
In TFS 2010, I believe you can do this by impersonating the user you are interested in when making your calls.
The TFS 2010 API allows (properly authorized) applications to "impersonate" any valid user you want and take action as that user. This is "authorization" impersonation -- you are not authenticating as another user, so there's no password entry, but you are taking action "on behalf of" another user. There's a specific permission you need to have to do this, so your application would need to be actually run as a user with the "Make requests on behalf of other users" permission.
Once that's done, the code is pretty simple. You extract the identity you want from your TPC then create a second "impersonated" one under a different context, and use that second context for your actual work:
var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("some URI"));
var identityService = tfs.GetService<IIdentityManagementService>();
var identity = identity = identityService.ReadIdentity(
IdentitySearchFactor.AccountName,
"someuser",
MembershipQuery.None,
ReadIdentityOptions.None);
var userTfs = new TfsTeamProjectCollection(tfs.Uri, identity.Descriptor);
Any action you take on userTfs will be done as if the specified username did it; this allows you to query for projects, queue builds, etc. on behalf of other users.
If you add using System.net then you can use the credential cache and pass the default credentials of the current user to TFS when getting the collection
using (var tfs = new TfsTeamProjectCollection(tfsUri, CredentialCache.DefaultCredentials))
{
var store = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
var projects = store.Projects
}

Categories

Resources