Using explicit credentials in a C# dialogflow application - c#

I'm creating a C# application that uses DialogFlow's detectIntent. I need help passing the Google Cloud credentials explicitly.
It works with the GOOGLE_APPLICATION_CREDENTIALS environment variable. However I want to pass the credentials explicitly. I need a C# version of the solution provided here.
I'm using the following quick-start provided with the documentation:
public static void DetectIntentFromTexts(string projectId,
string sessionId,
string[] texts,
string languageCode = "en-US")
{
var client = df.SessionsClient.Create();
foreach (var text in texts)
{
var response = client.DetectIntent(
session: new df.SessionName(projectId, sessionId),
queryInput: new df.QueryInput()
{
Text = new df.TextInput()
{
Text = text,
LanguageCode = languageCode
}
}
);
var queryResult = response.QueryResult;
Console.WriteLine($"Query text: {queryResult.QueryText}");
if (queryResult.Intent != null)
{
Console.WriteLine($"Intent detected: {queryResult.Intent.DisplayName}");
}
Console.WriteLine($"Intent confidence: {queryResult.IntentDetectionConfidence}");
Console.WriteLine($"Fulfillment text: {queryResult.FulfillmentText}");
Console.WriteLine();
}
}

Currently you need to create a gRPC channel directly, and pass that into the client:
GoogleCredential credential = GoogleCredential.FromFile("...");
ChannelCredentials channelCredentials = credential.ToChannelCredentials();
Channel channel = new Channel(SessionsClient.DefaultEndpoint, channelCredentials);
var client = df.SessionsClient.Create(channel);
Very soon, this will be a lot easier via a builder pattern:
var client = new SessionsClientBuilder
{
CredentialsPath = "path to file",
}.Build();
... or various other ways of specify the credential. I'm hoping that'll be out in the next couple of weeks.

Related

Problems with Google Cloud Platform authentication

we are experiencing problems with API authentication of our project in asp-net core 3.1. Specifically we have integrated the text-to-speech service provided by Google. Locally everything works correctly, but this does not happen when the web-app is online.
try
{
var path = "C://GoogleVoice//food-safety-trainer-47a9337eda0f.json";
var credential = GoogleCredential.FromFile(path);
var storage = StorageClient.Create(credential);
TextToSpeechClient client = TextToSpeechClient.Create();
var test = client.GrpcClient;
// The input can be provided as text or SSML.
SynthesisInput input = new SynthesisInput
{
Text = text
};
VoiceSelectionParams voiceSelection = new VoiceSelectionParams();
voiceSelection.LanguageCode = "it-IT";
voiceSelection.Name = "it-IT-Wavenet-A";
voiceSelection.SsmlGender = SsmlVoiceGender.Female;
// The audio configuration determines the output format and speaking rate.
AudioConfig audioConfig = new AudioConfig
{
AudioEncoding = AudioEncoding.Mp3
};
SynthesizeSpeechResponse response = client.SynthesizeSpeech(input, voiceSelection, audioConfig);
var result = _mp3Helper.SaveFile(response);
if (result.Item1 == "Success")
return Json(new { Result = true, Value = result.Item2 });
else
return Json(new { Result = false, Error = result.ToString() });
}
catch(Exception ex)
{
return Json(new { Result = false, Error = ex.Message.ToString() });
}
The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.
Assuming you want to use the same service account for both Speech and Storage, you need to specify the credentials for the text-to-speech client. Options:
Set the GOOGLE_APPLICATION_DEFAULT_CREDENTIALS environment variable to refer to the JSON file. Ideally, do that as part of deployment configuration rather than in your code, but you can set the environment variable in your code if you want to. At that point, you can remove any explicit loading/setting of the credential for the Storage client.
Specify the CredentialPath in TextToSpeechClientBuilder:
var client = new TextToSpeechClientBuilder { CredentialPath = path }.Build();
This will load a separate credential.
Specify the credential's token access method via the TokenAccessMethod property in TextToSpeechClientBuilder:
var client = new TextToSpeechClientBuilder
{
TokenAccessMethod = credential.GetAccessTokenForRequestAsync
}.Build();

Azure Blob how to properly create and consume a SAS token

Before I begin, allow me to say that I have scoured MSFTs docs, everything seems to imply that I need to manually handroll the GET request? Which seems wrong, given that the SDK handles that for us.
What I am experiencing from my Xamarin app is the following 403 error when I try to run my code to get a list of blobs.
<?xml version="1.0" encoding="utf-8"?><Error><Code>AuthenticationFailed</Code><Message>Server failed to authenticate the request. Make sure the value of the Authorization header is formed correctly including the signature.</Message></Error>
The way my workflow goes is as follows:
App Makes request to API to get Azure SAS token
API Responds using the following code to return the SAS token (blobServiceClient is defined using the default emulator storage connection string):
try
{
var client = blobServiceClient.GetBlobContainerClient(request.VenueId);
var permissions = BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List;
var sas = client.GenerateSasUri(permissions, DateTimeOffset.Now.AddHours(1));
var containerUri = "";
#if DEBUG
var temp = sas.AbsoluteUri;
var replaced = temp.Replace("http://127.0.0.1:10000/", "http://myngrokproxy.ngrok.io/");
containerUri = replaced;
#else
containerUri = sas.AbsoluteUri;
#endif
//var sas = containerUri + container.GetSharedAccessSignature(accessPolicy);
return new AzureSASResponse
{
SAS = containerUri
};
} catch (Exception e)
{
return null;
}
As you can see, the replace is there since localhost URL is meaningless for the emulator.
App side I try to consume it as follows:
try
{
var uri = new Uri(SAS);
var containerClient = new BlobContainerClient(uri);
var blobs = containerClient.GetBlobs().AsPages();
foreach(var blob in blobs)
{
Console.WriteLine(blob);
}
} catch (Exception e)
{
Console.WriteLine(e);
}
My resulting SAS token looks like so: "http://myngrokproxy.ngrok.io/devstoreaccount1/8dc9e4831d634629b386680ad7c9a324?sv=2020-08-04&se=2021-10-21T21%3A43%3A16Z&sr=c&sp=rl&sig=oncjUlSLMsOS3WbxUWqjXDp28WACYxxVqUElrK%2BYNlY%3D"
How can I go about A) setting the auth header on it, even the GET request that fails is the .GetBlobs method in the Xamarin app?
After much trial and error my ways to fix it were as follows:
Use latest version of azurite from Microsoft, I used the original old one (Arafuto/azurite)
change code to look as follows;
var sasBuilder = new BlobSasBuilder()
{
BlobContainerName = containerClient.Name,
Resource = "c",
StartsOn = DateTimeOffset.UtcNow.AddMinutes(-15),
ExpiresOn = DateTimeOffset.UtcNow.AddDays(7)
};
sasBuilder.SetPermissions(BlobSasPermissions.Read | BlobSasPermissions.List);
var client = blobServiceClient.GetBlobContainerClient(request.VenueId);
var permissions = BlobContainerSasPermissions.Read | BlobContainerSasPermissions.List;
var sas = client.GenerateSasUri(sasBuilder);
var containerUri = "";
#if DEBUG
var temp = sas.AbsoluteUri;
var replaced = temp.Replace("http://127.0.0.1:10000/", "http://myngrokproxy.ngrok.io/");
containerUri = replaced;
#else
containerUri = sas.AbsoluteUri;
#endif
return new AzureSASResponse
{
SAS = containerUri
};
The inspiration for the BlobSasBuilder came from this document: https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-user-delegation-sas-create-dotnet#get-a-user-delegation-sas-for-a-container

Google Cloud Platforms Text To Speech API "'Error creating credential from JSON. Unrecognized credential type ."

using (var stream = new FileStream("C:/textToSpeech/client_secret.json", FileMode.Open, FileAccess.Read))
{
var credential = GoogleCredential.FromStream(stream).CreateScoped(LoggingServiceV2Client.DefaultScopes);
var channel = new Grpc.Core.Channel(
LoggingServiceV2Client.DefaultEndpoint.ToString(),
credential.ToChannelCredentials());
var client = await TextToSpeechClient.CreateAsync(channel.ShutdownToken);
//var client = TextToSpeechClient.Create();
var input = new SynthesisInput
{
Text = "This is Demo of the Google Cloud Text-to-Speech API"
};
VoiceSelectionParams voiceSelection = new VoiceSelectionParams
{
LanguageCode = "en-US",
SsmlGender = SsmlVoiceGender.Female,
};
var audioConfig = new AudioConfig
{
AudioEncoding = AudioEncoding.Mp3
};
var response = client.SynthesizeSpeech(input, voiceSelection, audioConfig);
using (var output = File.Create("output.mp3"))
{
response.AudioContent.WriteTo(output);
}
Console.WriteLine("Audio content written to file output.mp3");
}
I want to convert my text to audio text in a form application but I get an error while translating. I signed up for Google Cloud Platform and created my JSON file by doing the necessary actions. I am getting the following error while authorizing the API.You can see the screenshot of the error I got here. I added the necessary things to the environment variables. I tried every solution I found on the internet, but I could not get rid of this error. Error => System.InvalidOperationException: 'Error creating credential from JSON. Unrecognized credential type .'

OAuth 2.0 Authorization for windows desktop application using HttpListener

I am writing a windows desktop application with External Authentication(Google, Facebook) in C#.
I'm using HttpListener to allow a user to get Barer token by External Authentication Service with ASP.NET Web API, but administrator privileges are required for that and I want run without admin mode.
My reference was Sample Desktop Application for Windows.
Is this the best practice for external authentication provider from C#? Or is there another way to do that?
This is my code to get Barer token by external provider:
public static async Task<string> RequestExternalAccessToken(string provider)
{
// Creates a redirect URI using an available port on the loopback address.
string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, GetRandomUnusedPort());
// Creates an HttpListener to listen for requests on that redirect URI.
var http = new HttpListener();
http.Prefixes.Add(redirectURI);
http.Start();
// Creates the OAuth 2.0 authorization request.
string authorizationRequest = Properties.Settings.Default.Server
+ "/api/Account/ExternalLogin?provider="
+ provider
+ "&response_type=token&client_id=desktop"
+ "&redirect_uri="
+ redirectURI + "?";
// Opens request in the browser.
System.Diagnostics.Process.Start(authorizationRequest);
// Waits for the OAuth authorization response.
var context = await http.GetContextAsync();
// Sends an HTTP response to the browser.
var response = context.Response;
string responseString = string.Format("<html><head></head><body></body></html>");
var buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
var responseOutput = response.OutputStream;
Task responseTask = responseOutput.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) =>
{
responseOutput.Close();
http.Stop();
Console.WriteLine("HTTP server stopped.");
});
// Checks for errors.
if (context.Request.QueryString.Get("access_token") == null)
{
throw new ApplicationException("Error connecting to server");
}
var externalToken = context.Request.QueryString.Get("access_token");
var path = "/api/Account/GetAccessToken";
var client = new RestClient(Properties.Settings.Default.Server + path);
RestRequest request = new RestRequest() { Method = Method.GET };
request.AddParameter("provider", provider);
request.AddParameter("AccessToken", externalToken);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
var clientResponse = client.Execute(request);
if (clientResponse.StatusCode == HttpStatusCode.OK)
{
var responseObject = JsonConvert.DeserializeObject<dynamic>(clientResponse.Content);
return responseObject.access_token;
}
else
{
throw new ApplicationException("Error connecting to server", clientResponse.ErrorException);
}
}
I don't know about Facebook, but usually (I am experienced with Google OAuth2 and Azure AD as well as Azure AD B2C), the authentication provider allows you to use a custom URI scheme for the authentication callback, something like badcompany://auth
To acquire an authentication token I ended up implementing the following scheme (All code is presented without warranty and not to be copied thoughtlessly.)
1. Register an URI-handler when the app is started
You can register an URI-Handler by creating a key in the HKEY_CURRENT_USER/Software/Classes (hence no admin privileges needed) key in the Windows registry
The name of the key equals the URI prefix, badcompany in our case
The key contains an empty string value named URL Protocol
The key contains a subkey DefaultIcon for the icon (actually I do not know whether this is necessary), I used the path of the current executable
There is a subkey shell/open/command, whose default value determines the path of the command to execute when the URI is tried to be opened, **please note*, that the "%1" is necessary to pass the URI to the executable
this.EnsureKeyExists(Registry.CurrentUser, "Software/Classes/badcompany", "URL:BadCo Applications");
this.SetValue(Registry.CurrentUser, "Software/Classes/badcompany", "URL Protocol", string.Empty);
this.EnsureKeyExists(Registry.CurrentUser, "Software/Classes/badcompany/DefaultIcon", $"{location},1");
this.EnsureKeyExists(Registry.CurrentUser, "Software/Classes/badcompany/shell/open/command", $"\"{location}\" \"%1\"");
// ...
private void SetValue(RegistryKey rootKey, string keys, string valueName, string value)
{
var key = this.EnsureKeyExists(rootKey, keys);
key.SetValue(valueName, value);
}
private RegistryKey EnsureKeyExists(RegistryKey rootKey, string keys, string defaultValue = null)
{
if (rootKey == null)
{
throw new Exception("Root key is (null)");
}
var currentKey = rootKey;
foreach (var key in keys.Split('/'))
{
currentKey = currentKey.OpenSubKey(key, RegistryKeyPermissionCheck.ReadWriteSubTree)
?? currentKey.CreateSubKey(key, RegistryKeyPermissionCheck.ReadWriteSubTree);
if (currentKey == null)
{
throw new Exception("Could not get or create key");
}
}
if (defaultValue != null)
{
currentKey.SetValue(string.Empty, defaultValue);
}
return currentKey;
}
2. Open a pipe for IPC
Since you'll have to pass messages from one instance of your program to another, you'll have to open a named pipe that can be used for that purpose.
I called this code in a loop in a background Task
private async Task<string> ReceiveTextFromPipe(CancellationToken cancellationToken)
{
string receivedText;
PipeSecurity ps = new PipeSecurity();
System.Security.Principal.SecurityIdentifier sid = new System.Security.Principal.SecurityIdentifier(System.Security.Principal.WellKnownSidType.WorldSid, null);
PipeAccessRule par = new PipeAccessRule(sid, PipeAccessRights.ReadWrite, System.Security.AccessControl.AccessControlType.Allow);
ps.AddAccessRule(par);
using (var pipeStream = new NamedPipeServerStream(this._pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.Asynchronous, 4096, 4096, ps))
{
await pipeStream.WaitForConnectionAsync(cancellationToken);
using (var streamReader = new StreamReader(pipeStream))
{
receivedText = await streamReader.ReadToEndAsync();
}
}
return receivedText;
}
3. Make sure that the application is started only once
This can be acquired using a Mutex.
internal class SingleInstanceChecker
{
private static Mutex Mutex { get; set; }
public static async Task EnsureIsSingleInstance(string id, Action onIsSingleInstance, Func<Task> onIsSecondaryInstance)
{
SingleInstanceChecker.Mutex = new Mutex(true, id, out var isOnlyInstance);
if (!isOnlyInstance)
{
await onIsSecondaryInstance();
Application.Current.Shutdown(0);
}
else
{
onIsSingleInstance();
}
}
}
When the mutex has been acquired by another instance, the application is not fully started, but
4. Handle being called with the authentication redirect URI
If it's the only (first) instance, it may handle the authentication redirect URI itself
Extract the token from the URI
Store the token (if necessary and/or wanted)
Use the token for requests
If it's a further instance
Pass the redirect URI to the first instance by using pipes
The first instance now performs the steps under 1.
Close the second instance
The URI is sent to the first instance with
using (var client = new NamedPipeClientStream(this._pipeName))
{
try
{
var millisecondsTimeout = 2000;
await client.ConnectAsync(millisecondsTimeout);
}
catch (Exception)
{
onSendFailed();
return;
}
if (!client.IsConnected)
{
onSendFailed();
}
using (StreamWriter writer = new StreamWriter(client))
{
writer.Write(stringToSend);
writer.Flush();
}
}
To add to Paul's excellent answer:
Identity Model Libraries are worth looking at - one of the things they'll do for you is Authorization Code Flow (PKCE) which is recommended for native apps
My preference is the same as Paul's - to use custom URI schemes - usability is better I think
Having said that, a loopback solution should work without admin rights for ports greater than 1024
If it helps there is some stuff on my blog about this - including a Nodejs / Electron sample you can run from here to see what a finished solution looks like.

Get IP of Azure VM using SDK

I have a VM powered on and running in azure. I know its name but want to retrieve its IP address programmatically using the new C# SDK and avoiding the REST API. How can I do this?
Try this:
string subId = "deadbeef-beef-beef-beef-beefbeefbeef";
string resourceGroup = "SORG01";
string vmName = "SORG01-BOX01";
using (var client = new ComputeManagementClient(credentials))
{
client.SubscriptionId = subId;
VirtualMachine vm = VirtualMachinesOperationsExtensions.Get(client.VirtualMachines, resourceGroup, vmName);
networkName = vm.NetworkProfile.NetworkInterfaces[0].Id.Split('/').Last();
}
using (var client = new NetworkManagementClient(credentials))
{
client.SubscriptionId = subId;
var network = NetworkInterfacesOperationsExtensions.Get(client.NetworkInterfaces, resourceGroup, vmName);
string ip = network.IpConfigurations[0].PrivateIPAddress;
}
To have these classes, you'll need to install from nuget:
Microsoft.Azure.Management.Compute
Microsoft.Azure.Management.Compute.Models
Microsoft.Azure.Management.Network
Note that you'll have to select "Include Prerelease" on the nuget search window in order to find these packages. credentials is a Microsoft.Rest.TokenCredentials object that you acquire in this manner:
var authContext = new AuthenticationContext("https://login.windows.net/{YourTenantId}");
var credential = new ClientCredential("{YourAppID}", "{YourAppSecret}");
var result = authContext.AcquireTokenAsync("https://management.core.windows.net/", credential);
result.Wait();
if (result.Result == null)
throw new AuthenticationException("Failed to obtain the JWT token");
credentials = new TokenCredentials(result.Result.AccessToken);
The easiest way to retrieve the public IP Address of Azure Virtual Machine is
{_VirtualMachineInstance}.GetPrimaryPublicIPAddress().IPAddress;
Very good explanation of this matter you can find here- Tom Sun answer:
Get Azure VM using resource manager deployment and rest api

Categories