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
Related
I am trying to implement a role based authorization in my web API's and Azure Functions. So there are certain roles and corresponding AD groups looking something like this
Role AD Group
-----------------------------------
myapp.data.ro GBL ROL IT myapp data RO
myapp.data.rw GBL ROL IT myapp data write
myapp.logs.read GBL ROL IT myapp logs read
myapp.file.read GBL ROL IT myapp file read
myapp.contributor.all GBL ROL IT myapp admin
------------------------------------
So I have implemented authorization using Authorize attribute at various controller level.
But the challenge is when the requirement changed and I need to create teams level roles.
So it would be like
myapp.team1.data.ro or myapp.team2.data.ro or myapp.team2.file.read like that
So how can I handle this situation and when a new team comes how can I onboard that teams into the authorization without any code change?
Ultimately my requirement is to filter the data for different teams and at different levels like file,data,logs etc.
I have got a suggestion to use some middleware to manage the roles. But again that requires code change when new roles added, isnt it?
I thought about to have ROLES in DB and the middleware checks the request roles against that ROLES MAPPING and respond with success or forbidden (403)
Please share a good approach than this. I am a very beginner with these AD groups and roles thing in Azure.
Or is there is any service in Azure itself for the dynamic Role management and authorization
I think first you have to be sure about what is storing the role information in your deployment, and what is going to handle modifying this information.
It seems like you're using Active Directory for STORING this information.
Now, what is handling modifications? Like, who/what initially creates those Active Directory groups you already have? And what kind of event would trigger changes (how do you/someone else knows to add a new group to AD, or rather how do you/someone else knows there's a new team?).
If these groups are created for you - there's nothing else needed. When a new group is added to AD the application is going to work just fine with it (with certain caveats, like propagation time and limited OU scopes for the service account/credntials used by your service itself).
If you have to create these groups yourself... there are many ways, you need to decide how and what works best for you/your stack. Microsoft documentation has details on creating groups in Azure Active Directory (I assume that's what you're using, for self-hosted/on-prem Active Directory it might be slightly different): https://learn.microsoft.com/en-us/azure/active-directory/roles/groups-create-eligible
You can call a PowerShell script, make a web request from your C# application, or trigger an Azure Function that does it. I wouldn't be surprised if there's a NuGet package with library for interacting/adding groups to Azure AD as well. There are many options.
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();
I am just starting out with the c# Google.Apis.Gmail.V1 classes and using a service account to read a mailbox.
My code works fine when I use the following snippet
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(ServiceAccountEmailAddress)
{
User = "abc#test.domain.com",
Scopes = new[] { "https://www.googleapis.com/auth/gmail.readonly" }
}.FromCertificate(certificate));
With that code I can call the following successfully
if (credential.RequestAccessTokenAsync(CancellationToken.None).Result)
{
}
But I need to modify the email messages so I changed the scope from
https://www.googleapis.com/auth/gmail.readonly
to
https://www.googleapis.com/auth/gmail.modify
I now get an exception when requesting the access token
{"Error:\"unauthorized_client\", Description:\"Unauthorized client or scope in request.\", Uri:\"\""}
I have checked the service account (*.iam.gserviceaccount.com) in the Google Developers Console and I have tried all options for permissions including OWNER which should give me Full access to all resources but no joy.
I think I am just missing a simple step but I am unsure of where to look next.
TL;DR
I would read through this, but here is the short version. I know this is an older post, but hopefully it finds you!
If you have not updated/white-listed the service account's privileges/scopes in the Google Admin Console you will need to do that, make sure the domain has API access enabled, make sure the service account is setup properly, when creating the "certificate" object be aware its parameters so that it is being instantiated correctly, check the permissions on the account being impersonated and finally make sure you've made an appropriate Google Apps service account key (could have easily made an inappropriate key type.)
White-listing Google APIs in the Admin Console
This gives the Google Apps service account the abilityto use whatever scopes you provide in your Google Apps domain.
Login to the Google Apps Admin Console by using the following link.
https://admin.google.com/
The Google Apps user account must have sufficient privileges to modify domain related settings. It does not have to be the account used to create the Google Apps project in the developer console. If the account does not have privilege you will be directed to a completely different screen with no options to click on varying domain controlling web apps like "Security", "Roles", "Support", "Groups" and etc. Instead you'll dumped onto a page that shows things like "Gmail", "Drive", "Docs" and etc. that is typical user apps. The current link it drops you at is https://apps.google.com/user/hub
Click “Security.”
Click “Show more” option at the bottom of the security options list.
Click “Advanced Settings” to get the more options.
Select the “Manage API client access” link.
Now certain API scopes must be white-listed for the desired service account. In the “Client Name” text box provide the service account’s client ID. The client ID is obtained in the developer console. In the “One or more API scopes” add the desired scopes; comma delimited.
Note, if there are existing scopes they will be removed so be sure to re-add any that will be needed.
Enable Domain Wide API Access
Login to the Google Apps Admin Console by using the following link.
https://admin.google.com/
Go to the “Security” page.
Under “API reference” section
Make sure that “Enable API access” is enabled.
Creating an Appropriate Google Apps Service Account Key (Probably this)
Go to the Google Developer Console. Login as the Google Apps user that created the Google Apps project/service account. https://console.developers.google.com/
Navigate to the particular project with which you created the service account.
Click the "Service Account" button on the left of the project's page to bring up a page with all of the project's service accounts.
Click the vertical ellipse widget all the way to the right of the desired service account's row. Select “Create Key.”
Select .p12 key as it looks like this is what you're trying to use. Click "Create." Be sure to protect this key.
I have found that if the key is not created this way then it leaves open the possiblity for making either an API key or an OAuth 2.0 client/user key. These are the wrong types of keys to use in this case you would need to have created a service account key. The way outlined above forces you to create a service account key.
Modifying the Existing Google Apps Service Account's Settings
I'm not going over how to setup the actual service account, one thing you may need in your case is to make sure that the service account has domain wide delegation enabled. This is toggled in the Google Developer Console. Should be pretty easy to find.
Code
You do not provide your entire code base for creating the token, so just want to add a few things you might be doing improperly.
Make sure when you create the certificate that the secret you provide is the default "notasecret" string. This secret is currently the default value provided by all keys distributed by Google and is immutable during key creation. I had a link to prove this, but have since lost it.
X509Certificate2 certificate = new X509Certificate2(certificateFilePath, "notasecret", X509KeyStorageFlags.Exportable);
Just trying to advocate proper coding. While I have found some bugs in the past with Google's constant values that required additional string manipulation (adding additional slashes.) You should really be using the string constants that they provide in place of literals. I only say to use these because it provides a layer of abstraction, who is to say Google will never change the literal; unlikely.
In your case the new scope is:
GmailService.Scope.GmailModify
While the old scope was:
GmailService.Scope.GmailReadonly
Otherwise, everything code wise looks good to me.
Another thing to try would be to make sure that the actual Google Apps user account being impersonated by the service account has sufficient privileges. I would suspect a different error if this were the case, would be getting a 403 in the response instead. Anyway, in your case this is the "abc#test.domain.com" account. Once again you would go to the Google Admin Console, check its roles make sure it has sufficient roles checked for whatever it is you're trying to do. I don't know what specifically you'll need in this case, best bet would be to give it the same permissions as the "Super Admin" role then remove permissions as you go to see what it might actually need. Otherwise, if possible just give it "Super Admin."
If I was a gambler I would put my money on an inappropriately created service account key. I just recently ran into this and it was the only thing that produced the same exact error you're receiving. Other things would get me the same "Description" value in the response token, but not the same "Error" value. I'm not really even sure how the culprit key was made, because I didn't make it. I just know the fix was to recreate a new key with the steps above and that fixed the issue.
Is it possible to grant a role the ability to view/get the users list? I ask this because I am working on a C# application that lets developpers automatically create JIRA issues (using the SOAP api) from within the program, and I'd like to be able to let them select the assignee from a dropdown list. However, the account I connect to JIRA with needs to be an administrator, and I do not want to have an administrator account's credentials in plain text inside the codebase, since it is open to everyone.
This is what I am doing:
JiraSoapServiceClient jira = new JiraSoapServiceClient();
string token = jira.login("non_admin_account", "password");
...
//This call throws an exception saying the account needs administrative rights
var projectRoleActors = jira.getProjectRoleActors(token, projectRole,
jira.getProjectByKey(token, "EX"));
If I could give "non_admin_account" some kind of permission to grab the users list that would be perfect.. but I'm not sure that's possible.
Any solutions?
Give your use Project Administration permission instead of JIRA administration. See the method hasProjectRolePermission in DefaultProjectRoleService
One solution to your question might be to create two users, one with project admin permissions, and another as a normal user:
Use the admin user to get the list of users - create a CLI program, or use the remote SOAP C# program to get the list of users and store them in a text file. Make sure that the program isn't visible to the other users (for example, put it on your Jira server). You can either run this using the cron/scheduler every 5 minutes or run it trough the other program.
Use the other account for everything else in your C# program, let it read the list of users from the first program.
I am new to MVC and actually new to web development all together. I have about 7 years of development experience but in services, database, object models, etc.. basically middle-tier and back-end development. I am trying to learn ASP.NET and decided to build a site using MVC3 for a personal site for myself. I will be hosting this from an account at dotnet-hosts.com. Here is my question... I don't have a domain and I will be using the built in membership provider. I noticed in the auto generated code that was created when I added the project template that in the AccountController in the method ChangePassword (ChangePasswordModel model) there is this line of code...
MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */);
My question is specifically around User.Identity.Name, this looks like it would be returning the Windows user name just like Environment.UserName would. The Visual Studio template I used is the (Mobile Ready HTML5 MVC.NET) as I want to be able to support clients from any device...Windows PC, Apple, Windows Phone, iPhone, etc... If the call to User.Identity.Name is correct then I would like to ask how does this work on devices that are not Windows like an iPhone? If my assumption is correct that this will only work for Windows computers with a domain then how can I achieve this? would I need to perhaps use some caching? If so could I maybe grab the user name and their IP address to be used as the cache key from the Authentication page?
My high level question is... How do I get the current logged in user's userName regardless of the device/platform? I know this question is probably not written well and may be hard to understand... I apologize for that. I am new to web development and trying to get my feet wet and would like to start to the latest technology.
The call is correct. The User.Identity.Name is filled out by whatever authentication provider is in use - Windows authentication, Forms authentication, some custom authentication provider, or whatever. It isn't bound to a specific user "type". The authentication provider has the responsibility of making sure the Identity object corresponds to the current user on every request. Usually that part is taken care of using a combination of cookies and database.
The MVC template (although I haven't had a look at the template since MVC 2) uses ASP.NET's Membership class, which in turn uses a membership provider - for example SqlMembershipProvider or ActiveDirectoryMembershipProvider - the former stores your users' credentials (username and password etc.) in an SQL Server database, the latter uses Active Directory (i.e. primarily Windows logons). SqlMembershipProvider is the default, and MVC is set up to use a local SQLExpress database file as its user store.
The authentication provider that's implemented in the template project uses FormsAuthentication, which does the login procedure through a simple HTML form (the one in the LogOn view) and keeps the user signed in by way of an encrypted cookie. Works on any platform.
The setup for both FormsAuthentication and SqlMembershipProvider can be found in web.config (the one in the root of the site). There you can find the connection strings for the SQLExpress database (and e.g. change them to use a "real" SQL Server if needed), the timeout for logins etc.
(Note that you can do a lot of that configuration easily in a GUI through the "ASP.NET Configuration" button in the toolbar of Solution Explorer in Visual Studio - it also provides an easy way to set up the first users).
In short, it's all ready to go - and doesn't lock out non-Windows users.
Like you said User.Identity.Name is indeed correct. for returning the logged in users name. But the membership section like you said, provides only windows accounts. You can use similar without the user of windows accounts, to work in every scenario, and can still verify against windows if present. If you call it without membership, and follow the default MVC3 template it should work fine.
String Username = User.Identity.Name;
When you log on, using the template MVC3, it creates an authcookie. See account controller code. Here, two parameters are passed into it. The username, and to persist (when browser is closed - login is still cached).
The username is a string field, which is what is called by User.Identity.Name and infact, anything can be put into it, and is not in anyway linked to Windows login.
You could test the login via method you desire, and if yes, set a cookie using the authcookie method. (its encripted). And set the username to what ever you want. And if your verification of the user fails, dont create one, and redrect back to page.
See the example code. This is all from memory, as I dont have code infront of me for reference. But its all in the account controller, Login Action.
When the cookie is set, The users login state is cached for the session. You will need to ensure the user is logged in when visiting a webpage. Otherwise loggin in will be pointless. This is a simple attribute on the controller/action.
Note: dont do this to the Account/logon controller, as you wont be able to visit the logon page, as you are not logged in.
[Authorize]
public ActionResult DoSomething()
{
// ...
}
Hope I have helped.