How to set User Agent string in MSGraph .net SDK - user-agent

I'm reading customer email accounts using the MSGraph SDK v4.27.0 in C#. It works fine but one customer insists on using allowlists for EWS access to email. That grants access to apps that supply a User Agent string but how do I include it in the MSGraph header using the SDK calls?
The code is taken from the MS example
var scopes = new[] { "User.Read","Mail.ReadWrite","Mail.ReadWrite.Shared" };
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var userName = strAccount;
var password = strPWD ;
var userNamePasswordCredential = new UsernamePasswordCredential(
userName, password, tenantId, theClientId, options);
var graphClient = new GraphServiceClient(userNamePasswordCredential, scopes);
try
{
rootFolder = await graphClient.Me.MailFolders["msgfolderroot"]
.Request()
.GetAsync();
}..

Found the answer from the GitHubs docs eventually although it did take some experimentation finding out that the header option name is "User-Agent"
You create an options list and add in the user agent option
List<Option> theOptions = new List<Option>();
theOptions.Add(new HeaderOption("User-Agent", "MyUserAgentName"));
Then every .Request() call has to have the options as a parameter e.g.
rootFolder = await graphClient.Me.MailFolders["msgfolderroot"]
.Request(theOptions)
.GetAsync();

Related

Using Network Creds in dotnet core app on a mac using HttpClient

Writing a dotnet core app. I need to log in with network credentials as the service (which happens to be a TFS on-prem server) uses those to authenticate. From my (and another team members') windows machine, the following code works:
Console.WriteLine("Type in your DOMAIN password:");
var pass = GetPassword(); //command line secure string magic from SO
var networkCredential = new NetworkCredential("USERNAME", pass, "DOMAINNAME");
string tfsDefaultCollection = "https://TFSURL/DefaultCollection";
string testUrl = $"{tfsDefaultCollection}/_apis/tfvc/changesets/1234/changes?api-version=2.2";
var httpClientHandler = new HttpClientHandler
{
Credentials = networkCredential
};
var client = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri(testUrl)
};
httpClientHandler.PreAuthenticate = true;
var test = client.GetAsync(testUrl).Result;
Console.WriteLine(test);
But it doesn't work from my mac. I get a 401 unauthorized. Both used the same, hardwired connection. AND this works on my mac:
curl --ntlm --user "DOMAINNAME\USERNAME" "https://TFSURL/DefaultCollection/_apis/tfvc/changesets/1234/changes?api-version=2.2"
So that rules out a connectivity question, I would think. Am I missing something I need to be doing on my mac? Can anybody point me to some documentation or way to troubleshoot what both of these requests are doing at the lowest level to see if there is a difference?
Well finally some google-foo got me there. There's a bug in dotnet core for linux/mac. This issue describes the fix:
https://github.com/dotnet/corefx/issues/25988#issuecomment-412534360
It has to do with the host machine you are connecting to uses both Kerberos and NTLM authentication methods.
Implemented below:
AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);
Console.WriteLine("Type in your DOMAIN password:");
var pass = GetPassword(); //command line secure string magic from SO
var networkCredential = new NetworkCredential("USERNAME", pass, "DOMAINNAME");
string tfsDefaultCollection = "https://TFSURL/DefaultCollection";
string testUrl = $"{tfsDefaultCollection}/_apis/tfvc/changesets/1234/changes?api-version=2.2";
var myCache = new CredentialCache
{
{
new Uri(testUrl), "NTLM",
networkCredential
}
};
var httpClientHandler = new HttpClientHandler
{
Credentials = myCache
};
var client = new HttpClient(httpClientHandler)
{
BaseAddress = new Uri(testUrl)
};
httpClientHandler.PreAuthenticate = true;
var test = client.GetAsync(testUrl).Result;
Console.WriteLine(test);
Thanks to #dmcgill50 for getting me on the right googling track.

Google AUTH API Application Type, how important is it?

I've been doing a lot tinkering around with the authentication stuff using the .NET libraries provided by Google.
We have both a desktop and web-app side, and what we want to achieve is to authenticate ONCE, either on the desktop or the web side, and store the refresh token, and reuse it both on the web side and the desktop side.
So the situation is like so, on the desktop side, when there's no saved existing AccessToken's and RefreshToken's, we will ask the user to authenticate via this code:
using (var stream = new FileStream("client_secrets_desktop.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(GoogleClientSecrets.Load(stream).Secrets,
new[] { GmailService.Scope.GmailReadonly, GmailService.Scope.GmailCompose },
"someemail#gmail.com", CancellationToken.None);
}
In this case the Client ID and Secret is of an Application type Installed Application.
On the web-application side, if there's also no refresh token yet then I'm using DotNetOpenAuth to trigger the authentication, here's the code snippet:
const string clientID = "someclientid";
const string clientSecret = "somesecret";
const string redirectUri = "http://localhost/Home/oauth2callback";
AuthorizationServerDescription server = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri("https://accounts.google.com/o/oauth2/auth"),
TokenEndpoint = new Uri("https://accounts.google.com/o/oauth2/token"),
ProtocolVersion = ProtocolVersion.V20
};
public ActionResult AuthenticateMe()
{
List<string> scope = new List<string>
{
GmailService.Scope.GmailCompose,
GmailService.Scope.GmailReadonly,
GmailService.Scope.GmailModify
};
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
// Here redirect to authorization site occurs
OutgoingWebResponse response = consumer.PrepareRequestUserAuthorization(
scope, new Uri(redirectUri));
response.Headers["Location"] += "&access_type=offline&approval_prompt=force";
return response.AsActionResult();
}
public void oauth2callback()
{
WebServerClient consumer = new WebServerClient(server, clientID, clientSecret);
consumer.ClientCredentialApplicator =
ClientCredentialApplicator.PostParameter(clientSecret);
IAuthorizationState grantedAccess = consumer.ProcessUserAuthorization(null);
string accessToken = grantedAccess.AccessToken;
}
Here is where I want to confirm my suspicions.
When there is a RefreshToken that exists, we use the following code snippet to call the Gmail API's
UserCredential uc = new UserCredential(flow, "someemail#gmail.com", new TokenResponse()
{
AccessToken = "lastaccesstoken",
TokenType = "Bearer",
RefreshToken = "supersecretrefreshtoken"
});
var refreshState = await uc.RefreshTokenAsync(CancellationToken.None);
var svc = new GmailService(new BaseClientService.Initializer()
{
HttpClientInitializer = uc,
ApplicationName = "Gmail Test",
});
Here's the thing I noticed is that, for me to be able to use the refresh token to refresh from either the desktop or the web side, the refresh token needs to be generated through the same client ID/secret combination. I've tested it and it seems like it's fine if we use Installed application as the application type for the Client ID for both the desktop and the web, but my question I guess is, these application type's for the client IDs, do they matter so much?
Am I doing anything wrong to do it this way?
Thanks in advance

Google calendar api CalendarList List return empty element

EDIT: The original poster asked this for C#, but the same problem occurs regardless of the library used, and its solution is language independent.
Using C# lib,
string service_account = "myaccount#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"pathtomy-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(service_account)
{
Scopes = new[] { CalendarService.Scope.Calendar }
}.FromCertificate(certificate));
// Create the service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "My project name",
});
var calendarList = service.CalendarList.List().Execute();
IList<CalendarListEntry> items = calendarList.Items;
items is empty. On https://developers.google.com/google-apps/calendar/v3/reference/calendarList/list, when I try the api, I get the good result.
I really don't understand why I don't have any result : seems like if the service_account do not have the same calendar as my gmail account linked to.
Any suggestion ?
Thanks.
The solution is to share the existing calendar with the service account
<paste-your-account-here>#developer.gserviceaccount.com
(You can find the account in question under the credentials tab in Google Developers Console, it's called 'EMAIL ADDRESS')

Migration of Google Apps Marketplace app to oAuth 2.0 with additional scopes

We have app using oauth 1.0 in old marketplace. We are in process of migrating to oauth 2.0 for new marketplace. We are using UpgradeableApp API to do migration for existing domains. I am following steps specified here : https://developers.google.com/apps-marketplace/v1migratev2
As mentioned in the prerequisites in the above link: The scopes for the new and old apps must be compatible. But our new app has some additional scopes. Is there any way to grant access to these additional scopes while doing migration.
Only domain's admin or users can approve additional scopes.
Domain's admin receives an email notification after upgrade.
In your oauth2.0 app you can detect if all scopes have been approved or not. If not, you can show the user appropriate message to contact domain admin to get scopes approved.
For this we should have same scope in both old as well as on new listing. I am also facing the same problem of migrating the old users to new one. Kindly check the below code how I am migrating from old to new Users but every time I am getting 401 UnAuthorized, May I know what I am missing for this.
String url = String.Format("https://www.googleapis.com/appsmarket/v2/upgradableApp/{0}/{1}/{2}", oldAppId, chromeListing, domain);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "PUT";
request.ContentType = "application/json";
request.Accept = "application/json";
request.ProtocolVersion = HttpVersion.Version11;
request.Credentials = CredentialCache.DefaultCredentials;
request.Headers.Add("Authorization", "OAuth");
Hashtable postObj = new Hashtable();
postObj["Consumer Key"] = oldClientId;
postObj["Consumer Key Secret"] = oldSecret;
String s1 = new JavaScriptSerializer().Serialize(postObj);
var bs = Encoding.UTF8.GetBytes(s1);
using (Stream reqStream = request.GetRequestStream())
{
reqStream.Write(bs, 0, bs.Length);
}
using (WebResponse response = request.GetResponse())
{
using (var sr = new StreamReader(response.GetResponseStream()))
{
result = sr.ReadToEnd();
sr.Close();
}
}

Windows.Web.Http.HttpClient + WEB API Windows Authentication

Im using Windows.Web.Http.HttpClient to connect to my WEB API. Application haven't prompted for userid and password, but recently i changed WEB API by moving AuthorizeAttribute filter from Action to Class level. Now my Windows store 8.1 application prompt for user id and password. Please let me know how to set HttpClient to not prompt the login and password. Can any1 suggest me do i need to add header to my httpcleint
using (Windows.Web.Http.HttpClient httpClient = new Windows.Web.Http.HttpClient())
{
// Add a user-agent header
var headers = httpClient.DefaultRequestHeaders;
// The safe way to check a header value from the user is the TryParseAdd method
// Since we know this header is okay, we use ParseAdd with will throw an exception
// with a bad value - http://msdn.microsoft.com/en-us/library/windows/apps/dn440594.aspx
headers.UserAgent.ParseAdd("ie");
headers.UserAgent.ParseAdd("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)");
using (var response = await httpClient.GetAsync(new Uri(url)))
I dont see a way to send Default credentials.
Disable UI dialogs using HttpBaseProtocolFilter.AllowUI. Try this:
Windows.Web.Http.Filters.HttpBaseProtocolFilter filter =
new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
filter.AllowUI = false;
HttpClient client = new HttpClient(filter);
Uri uri = new Uri("http://localhost/?basic=1");
var response = await client.GetAsync(uri);
System.Diagnostics.Debug.WriteLine(response);
Do you need credentials? Use HttpBaseProtocolFilter.ServerCredential. Try this:
Uri uri = new Uri("http://localhost?ntlm=1");
Windows.Web.Http.Filters.HttpBaseProtocolFilter filter =
new Windows.Web.Http.Filters.HttpBaseProtocolFilter();
filter.AllowUI = false;
// Set credentials that will be sent to the server.
filter.ServerCredential =
new Windows.Security.Credentials.PasswordCredential(
uri.ToString(),
"userName",
"abracadabra");
HttpClient client = new HttpClient(filter);
var response = await client.GetAsync(uri);
System.Diagnostics.Debug.WriteLine(response);
Do you need default Windows credentials (domain credentials)? Simply add the Enterprise Authentication capability to your Package.appxmanifest.
I tried to apply your solution but it doesn't work as expected, or maybe I don't understand what I'm supposed to do.
I need to user the Windows credentials and I have enabled the Enterprise Authentification capability on my UWP app.
I use the code that you suggest:
var filter = new HttpBaseProtocolFilter();
filter.AllowUI = false;
var client = new HttpClient(filter);
HttpResponseMessage response = new HttpResponseMessage();
response = await client.PostAsync(concUri, null);
But the response returns me a 401.3 error...
If I add the login/password to the ServerCredential, this works well:
var filter = new HttpBaseProtocolFilter();
filter.AllowUI = false;
filter.ServerCredential = new Windows.Security.Credentials.PasswordCredential(WebServiceConstants.WebServiceUrl.ToString(), "login", "password");
var client = new HttpClient(filter);
HttpResponseMessage response = new HttpResponseMessage();
response = await client.GetAsync(concUri);
But I don't see what is the role of the Enterprise Authentication capability in this case, if I need to pass the login and the password...

Resources