TFS SDK - Getting the network credential prompt - tfs

I am trying to get the network prompt so that user can provide the credentials.
I saw this and It does not help. Could somebody provide a more complete example?
The goal is is to get this from a Word Add-in so that I can create work items in TFS from the function points mentioned in the word document. So, somebody writes the function points in a document, closes it and It would ask for the network credentials so that It can create work items in the TFS.

You want to use the UICredentialsProvider when connecting. Here's an example that shows how you would connect to a TFS 2010 Project Collection:
// Connect to a project collection by Uri
try
{
var projectCollectionUri = new Uri("http://tfs2010:8080/tfs/MyCollection");
var projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(projectCollectionUri, new UICredentialsProvider())
projectCollection.EnsureAuthenticated();
}
catch (TeamFoundationServerUnauthorizedException ex)
{
// handle access denied
}
catch (TeamFoundationServiceUnavailableException ex)
{
// handle service unavailable
}
catch (WebException ex)
{
// handle other web exception
}

Related

MSAL.NET redirect loop when using graphApi in MVC & blazor with multiple instances

I have created a blazor component that aims to simplify managing users and group of an enterprise application in my ASP.NET MVC website. When I run the code locally, everything works just fine. However, when I deploy my code on the dev environment (in AKS) the code only works if I run one replica.
When I use multiple instances and I try to access the page that calls my blazor component, the page ends up in a redirect loop, and finally shows the Microsoft login interface with an error mentioning that the login was not valid.
This is how my code looks like:
# program.cs
var initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
var cacheOptions = builder.Configuration.GetSection("AzureTableStorageCacheOptions").Get<AzureTableStorageCacheOptions>();
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddDistributedTokenCaches();
builder.Services.Configure<MsalDistributedTokenCacheAdapterOptions>(options =>
{
options.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24);
});
builder.Services.AddDistributedAzureTableStorageCache(options =>
{
options.ConnectionString = cacheOptions.ConnectionString;
options.TableName = cacheOptions.TableName;
options.PartitionKey = cacheOptions.PartitionKey;
options.CreateTableIfNotExists = true;
options.ExpiredItemsDeletionInterval = TimeSpan.FromHours(24);
});
builder.Services.AddSession();
...
# The controller that calls the blazor component
[AuthorizeForScopes(Scopes = new[] { "Application.ReadWrite.All", "Directory.Read.All", "Directory.ReadWrite.All" })]
public async Task<IActionResult> UserManagement()
{
string[] scopes = new string[] { "Application.ReadWrite.All", "Directory.Read.All", "Directory.ReadWrite.All" };
try
{
await _tokenAcquisition
.GetAccessTokenForUserAsync(scopes)
.ConfigureAwait(false);
}
catch (Exception ex)
{
_telemetryClient.TrackException(ex);
}
return View();
}
And this is what happens:
If the page loads, I can see this exception in the pod logs:
What am I doing wrong?
The tenant actually needs to provide admin consent to your web API for the scopes you want to use for replicas for the token taken from cache.
Also when AuthorizeForScopes attribute is specified with scopes ,this needs the exact scopes that is required by that api. MsalUiRequiredException gets thrown in case of incorrect scopes for that api and results in a challenge to user.
This error may also occur even when the acquiretokensilent call will not have a valid cookie anymore for authentication in cache .Please check how acquiretokensilent call works from here in msal net acquire token silently | microsoft docs
When valid scopes are given , please make sure the permissions are granted consent by the admin directly from portal or during user login authentication.
Also as a work around try to use use httpContextAccessor to access
token after authentication .
Reference: c# - Error : No account or login hint was passed to the AcquireTokenSilent call - Stack Overflow
So, the culprit was:
#my controller
await _tokenAcquisition
.GetAccessTokenForUserAsync(scopes)
.ConfigureAwait(false);
Which we were using initially to reauthenticate the graph api component when we were using InMemoryCache.
There is no need to get the access token again when using DistributedTokenCache, and actually that was causing the token to get saved / invalidated in an infinite loop.
Also, in my blazor component, I had to do use the consent handler to force a login:
private async Task<ServicePrincipal> GetPrincipal(AzureAdConfiguration addConfiguration)
{
try
{
return await GraphClient.ServicePrincipals[addConfiguration.PrincipalId].Request()
.Select("id,appRoles, appId")
.GetAsync();
}
catch (Exception ex)
{
ConsentHandler.HandleException(ex);
throw;
}
}

How do I connect to Exchange Online using OAuth 2.0 in MailKit?

I have a web application that sends e-mails to users via Exchange Online (Office365) using MailKit and Basic Authentication. Our company is MS partner and therefor is obligated to turn off Basic Authentication for our services by the end of february 2020.
So, I want to use OAuth 2.0 to connect to Exchange Online, similar to this example. In fact, there might be a solution available according to this answer but I'm unable to find anything about it.
Right now I'm playing around with MS Identity Platform v2.0 but I'm unable to figure out how to do it.
Any help would be appriciated.
UPDATE 1
I do not want to send mails on behalf of signed-in users but instead there is a single Office365 user account that shall be used to send mails (notifications and so on) to others.
UPDATE 2
I managed to get a little closer to what I want to do using Microsoft Graph SDK and the Username/Password Provider.
Our user account requires multifactor-authentication and therefor I get an error when using the user's password since I cannot satisfy the second factor. When I'm using an app-password authentication fails because of incorrect password.
UPDATE 3
I switched to mail relaying for now. But I will update this question if I'll ever find an answer to it.
Using the Microsoft.Identity.Client you can generate a token and pass though then authentication using that.
I spotted the below for IMAP, POP3 and SMTP so adapted for my project to get a working solution. Although the example show the interactive method, where as I am was trying to use the the client credentials flow with an app secret.
MailKit - Using OAuth2 With Exchange (IMAP, POP3 or SMTP)
Microsoft - Authenticate an IMAP, POP or SMTP connection using OAuth
From #hB0 comment
Setting up Service Principles via client credentials grant flow (non-interactive)
https://techcommunity.microsoft.com/t5/exchange-team-blog/announcing-oauth-2-0-client-credentials-flow-support-for-pop-and/ba-p/3562963
My choice would be to look into Microsoft Graph API . It is a single endpoint for all Microsoft services including Email. Email specific endpoints document is here
Microsoft provides SDK in different languages to develop client applications using Graph API.
At a high level you would need to do the following.
i) Register an application in Azure Active Directory. See here
ii) Use the Oauth2 'authorization code grant' flow to get a refresh token . See here
iii) Exchange the refresh token for an access token and use the access token to call Microsoft Graph API.
iv) You also need to store the refresh token , if you have use cases where you application needs to perform actions even if the user is offline. In this case make sure you include scope 'offline' in step ii)
I would suggest looking into DotNetOpenAuth or a similar library and reading their samples. You'll probably need to know the Windows Live URLs to use for this if the DotNetOpenAuth library doesn't have them built-in.
Samples can be found here: https://github.com/DotNetOpenAuth/DotNetOpenAuth.Samples
I know this is an old post but with Microsoft progressively rolling modern authentication on all Office 365 tenants. Here's what I cobbled together.
I haven't worked with MFA setups.
I use it to fetch attachments via POP3 from automated mails coming in a mailbox of our tenant, the app runs from a scheduled task so it needs to be able to run without interaction.
First, you need to get the TenantID and ClientID the tenant admin gets when registering an app on the tenant. Credits to #jstedfast for the bootstrap doc to use those informations elegantly.
Then, setup a cache for the authentication token (following this article and the wiki page linked to it).
Then handle the logic whether to use interactive or silent authentication and avoid prompting for sign-in everytime. (straight copy/paste from documentation, but rather than leaving a link that might break...)
I wrapped it all together in a function that I call later to handle the authentication.
private static async Task<AuthenticationResult> GetMSALTokenAsync()
{
var scopes = new string[] {
"email",
"offline_access",
"https://outlook.office.com/POP.AccessAsUser.All"
};
var options = new PublicClientApplicationOptions
{
ClientId = Settings.Default.MSALClientId,
TenantId = Settings.Default.MSALTenantId,
RedirectUri = Settings.Default.MSALRedirectURI
};
var storageProperties = new StorageCreationPropertiesBuilder(
Settings.Default.MSALTokenCache,
MsalCacheHelper.UserRootDirectory)
.Build();
var publicClientApplication = PublicClientApplicationBuilder
.CreateWithApplicationOptions(options)
.Build();
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.RegisterCache(publicClientApplication.UserTokenCache);
var accounts = await publicClientApplication.GetAccountsAsync();
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
AuthenticationResult authToken;
try
{
authToken = await publicClientApplication.AcquireTokenSilent(scopes, accounts.First(o => o.Username == Settings.Default.LoginPop)).ExecuteAsync();
return authToken;
}
catch (MsalUiRequiredException ex) when (ex.ErrorCode == MsalError.InvalidGrantError)
{
switch (ex.Classification)
{
case UiRequiredExceptionClassification.None:
break;
case UiRequiredExceptionClassification.MessageOnly:
// You might want to call AcquireTokenInteractive(). Azure AD will show a message
// that explains the condition. AcquireTokenInteractively() will return UserCanceled error
// after the user reads the message and closes the window. The calling application may choose
// to hide features or data that result in message_only if the user is unlikely to benefit
// from the message
try
{
authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
return authToken;
}
catch (MsalClientException ex2) when (ex2.ErrorCode == MsalError.AuthenticationCanceledError)
{
// Do nothing. The user has seen the message
}
break;
case UiRequiredExceptionClassification.BasicAction:
// Call AcquireTokenInteractive() so that the user can, for instance accept terms
// and conditions
case UiRequiredExceptionClassification.AdditionalAction:
// You might want to call AcquireTokenInteractive() to show a message that explains the remedial action.
// The calling application may choose to hide flows that require additional_action if the user
// is unlikely to complete the remedial action (even if this means a degraded experience)
case UiRequiredExceptionClassification.ConsentRequired:
// Call AcquireTokenInteractive() for user to give consent.
case UiRequiredExceptionClassification.UserPasswordExpired:
// Call AcquireTokenInteractive() so that user can reset their password
case UiRequiredExceptionClassification.PromptNeverFailed:
// You used WithPrompt(Prompt.Never) and this failed
case UiRequiredExceptionClassification.AcquireTokenSilentFailed:
default:
// May be resolved by user interaction during the interactive authentication flow.
authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
return authToken;
}
}
catch (InvalidOperationException)
{
authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync(token);
return authToken;
}
log.Error("Authentication failed.");
return null;
}
Then you can just roll on with the actual logic to do your stuff with the Exchange server.
private static async Task PopDownloadAsync()
{
using (var client = new Pop3Client())
{
try
{
await client.ConnectAsync(Settings.Default.SrvPop, 995, SecureSocketOptions.SslOnConnect);
}
catch (Pop3CommandException ex)
{
// do stuff
return;
}
catch (Pop3ProtocolException ex)
{
// do stuff
return;
}
try
{
var result = await GetMSALTokenAsync();
if (result != null)
{
var oauth2 = new SaslMechanismOAuth2(result.Account.Username, result.AccessToken);
await client.AuthenticateAsync(oauth2);
}
else
{
throw new AuthenticationException("Something went wrong during authentication...");
}
}
catch (AuthenticationException ex)
{
// do stuff
return;
}
catch (Pop3CommandException ex)
{
// do stuff
return;
}
catch (Pop3ProtocolException ex)
{
// do stuff
return;
}
if (client.Capabilities.HasFlag(Pop3Capabilities.UIDL))
{
try
{
// do stuff
}
catch (Pop3CommandException ex)
{
// do stuff
}
catch (Pop3ProtocolException ex)
{
// do stuff
if (!client.IsConnected)
return;
}
catch (Exception e)
{
// do stuff
return;
}
}
if (client.IsConnected)
{
await client.DisconnectAsync(true);
}
}
}

How to remove the custom checkin policy while uninstalling?

I have a custom checkin policy written in c# and I used VSIX project with custom action enabled on install. Installation is working great. If I have the custom checkin policy applied to the team project in TFS 2010 and I uninstalled my policy from the same installer, it is cleaning up the registry and the files, but the source control still have the policy enabled and throws an error Error loading the policy. I want my installer to remove the policy from the source control while uninstalling the policy. How can I achieve this?
I tried to write the following code in OnAfterUninstall event, but it is not doing what I need:
protected override void OnAfterUninstall(IDictionary savedState)
{
base.OnAfterUninstall(savedState);
RemovePolicy();
}
private void RemovePolicy()
{
try
{
TfsTeamProjectCollection projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(projectCollectionUri, new UICredentialsProvider());
projectCollection.EnsureAuthenticated();
VersionControlServer vcs = projectCollection.GetService<VersionControlServer>();
List<TeamProject> lstTeamProject = vcs.GetAllTeamProjects(true).ToList<TeamProject>();
foreach (TeamProject tp in lstTeamProject)
{
List<PolicyEnvelope> tc = tp.GetCheckinPolicies().ToList<PolicyEnvelope>();
var myPolicy = new MyCustomCheckinPolicy();
TeamProject teamProject = vcs.GetTeamProject(tp.Name);
foreach (PolicyType policyType in Workstation.Current.InstalledPolicyTypes)
{
if (policyType.Name == myPolicy.Type)
{
tc.Remove(new PolicyEnvelope(myPolicy, policyType));
teamProject.SetCheckinPolicies(tc.ToArray());
break;
}
}
}
}
catch (Exception ex)
{
throw new InstallException("My Error Message");
}
}
Because a custom checkin policy needs to be installed on all developer workstations that access the TFS Project, having it deregister itself from the TFS Project upon uninstall will actually remove the policy whenever any of these developers uninstalls it. Which isn't what you want I suspect.
There are ways to ensure the policy is distributed to all team members through the Team Foundation Power Tools, that way, should you need to ship an upgrade or a different policy, you'll be sure all members have it. Only do the deregistration manually through the UI.

Implementing Kerberos authentication with Javamail

There is an older thread that seems to be the only relevant discussion I have been able to find.
I am trying to implement Kerberos with Javamail (over IMAP) and I have gotten my self thoroughly confused on exactly what is to be done with mail.imap.sasl.mechanisms. Assume I give the value "GSS-API" but am kind of lost where to go from there. I notice that Javamail has an class IMAPSaslAuthernticator. It seems to me that this is what is needed but I can find precious little documentation on where or how to use it.
Any ideas?
NOTE: I wanted to post more code for my question, but according the site directions, full posts are only for answers. So, I have edited the code I originally posted question.
Below is the real meat. For now, once I pass this point I get the Message[] from the server and print the size to console.
SSL/TLS security is required so it is enabled below. In this example certificates are managed by a trusted keystore in Java.
private Folder folder;
private Session session;
private Store store;
public boolean connectToKerberosMail() {
if (folder != null && folder.isOpen()) {
return true;
}
Properties properties = new Properties();
properties.setProperty("mail.debug", "true");
properties.put("mail.imaps.connectiontimeout",600000);
properties.put("mail.imaps.timeout",601000);
properties.put("mail.imaps.fetchsize", 65000);
properties.put("mail.imaps.starttls.enable", "true");
properties.put("mail.imaps.starttls.required", "false");
properties.put("mail.imaps.sasl.enable","true");
properties.put("mail.imaps.sasl.mechanisms","GSSAPI");
properties.put("mail.imaps.sasl.authorizationid",<user>);
properties.put("mail.imaps.sasl.realm",<realm>);
System.setProperty( "sun.security.krb5.debug", "true");
System.setProperty( "java.security.krb5.realm",<realm>);
System.setProperty( "java.security.krb5.kdc", <ip-address>);
System.setProperty( "java.security.auth.login.config", "jaas.conf");
System.setProperty( "javax.security.auth.useSubjectCredsOnly", "false");
try {
session = Session.getInstance(properties);
} catch (Exception e) {
session = null;
return false;
}
session.setDebug(true);
URLName url = new URLName("imaps", <host>, <port>, "", <user>, <pass>);
store = new IMAPSSLStore(session, url);
try {
store.connect();
} catch (Exception e) {
e.printStackTrace();
store = null;
session = null;
return false;
}
return openFolder();
}
My jaas.conf file is as follows (the ticket cache was acquired from kinit):
com.sun.security.jgss.initiate {
com.sun.security.auth.module.Krb5LoginModule required
principal="<principal>"
ticketCache="<cache-path>"
doNotPrompt="true"
useTicketCache="true"
debug="true";
};
com.sun.security.jgss.accept {
com.sun.security.auth.module.Krb5LoginModule required
principal="<principal>"
ticketCache="<cache-path>"
doNotPrompt="true"
useTicketCache="true"
debug="true";
};
I recently posted here the output but noticed that some of my properties where designated "imap" instead of "imaps". So I am doing more testing before posintg output incase it changes.
In the mean time is what I have above correct? From what I understand I have to enable imap for the imap connection, startTLS for the TLS/SSL, and sasl for kerberos. But maybe something is overriding the another?
While not 100% the way there yet i made some discoveries. LOGIN was happening with the protocol in the NamedURL was "imap". I changed it to "imaps".
However, it look like javamail takes the protocol and host uses them to contruct the principal. protocol/host#realm? so I was applying to principal imaps/host#REALM which didnt exist so failed on a non-matching pricipals error.
So, we added this new principal to the servers and got past this.
But authentication is still failing. In the kerberos log I was approved and sent a ticket for accessing the mail. But I do not see it in my ticket cache (using klist) only the first ticket for accessing kerberos (I got from using kinit).
It seems that I say this in every response. I don't know how to get the word out....
You almost certainly want to change Session.getDefaultInstance() to Session.getInstance(), although that's probably not the source of your problems.
Anyway, what does the protocol trace show when you run your program? (emailSession.setDebug(true);)
I don't know enough about Kerberos, and especially how Kerberos works as a SASL mechanism, but aren't you going to have to specify some sort of password? Or can it get the appropriate Kerberos ticket without asking you to prove who you are?

TFS API BranchObjectCreated Event does not fire

I have some code to automate the creation of build definitions in TFS.
Now I'd like to have this code invoked whenever a branch is created.
Looking at the API, I see that there is a BranchObjectCreatedEvent in Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer.
So I've added some code to a console app to handle the event.
private static void MonitorBranchCreated()
{
try
{
TfsTeamProjectCollection tfs = InitialiseTfs();
var vcs = tfs.GetService<VersionControlServer>();
var projects = vcs.GetAllTeamProjects(true);
foreach (var project in projects)
{
project.VersionControlServer.BranchObjectCreated += BranchObjectCreated;
}
Console.WriteLine("Subscribed to TFS BranchObjectCreated Event - Awaiting Notification...");
Console.ReadLine();
}
catch (Exception exception)
{
DisplayError(exception);
}
}
private static void BranchObjectCreated(object sender, BranchObjectCreatedEventArgs e)
{
// Create the Build
}
The trouble is that the event never fires when I create a branch from Source Control Explorer in Visual Studio.
The MSDN documentation is limited and I can't find any other examples of usage so I'm hoping somebody might be able to tell me if this is the correct approach.
If so, why might the event not be firing? If not, is there another way I can hook into TFS so that I can handle events related to creation of branches?
When you hook up events to the client API, you only get events that were created by that client. If you were to hook up a BranchObjectCreated listener, then call VersionControlServer.CreateBranch(), then your branch object created listener would be called.
If you want to listen to events on the server (such as when somebody else creates a branch, or you create a branch from a different client), then you need to tie into the server's project alert system.
You can install the Alerts Explorer in the Team Foundation Server Power Tools that will allow you to configure fine-grained alerts on projects that will send you email or call a web method. At this point, you can create a new build that references this new branch.

Resources