How to sign url in .net for google cloud storage - c#

I want to know that how to generate signurl using google cloud storage classes in .net
I have created string as per the requirement
GET
1388534400
/bucket/objectname
but I now want to sign this url with p12 key and then want to make it url friendly
This library doesn't show specific function for it -> https://developers.google.com/resources/api-libraries/documentation/storage/v1/csharp/latest/annotated.html
So, basically I need .net alternate of Google_Signer_P12 class of php
$signer = new Google_Signer_P12(file_get_contents(__DIR__.'/'."final.p12"), "notasecret");
$signature = $signer->sign($to_sign);

Now there is a UrlSigner in the pre-release package Google.Cloud.Storage.V1 that can be used to to provide read-only access to existing objects:
// Create a signed URL which can be used to get a specific object for one hour.
UrlSigner urlSigner = UrlSigner.FromServiceAccountCredential(credential);
string url = urlSigner.Sign(
bucketName,
objectName,
TimeSpan.FromHours(1),
HttpMethod.Get);
Or write-only access to put specific object content into a bucket:
// Create a signed URL which allows the requester to PUT data with the text/plain content-type.
UrlSigner urlSigner = UrlSigner.FromServiceAccountCredential(credential);
var destination = "places/world.txt";
string url = urlSigner.Sign(
bucketName,
destination,
TimeSpan.FromHours(1),
HttpMethod.Put,
contentHeaders: new Dictionary<string, IEnumerable<string>> {
{ "Content-Type", new[] { "text/plain" } }
});
// Upload the content into the bucket using the signed URL.
string source = "world.txt";
ByteArrayContent content;
using (FileStream stream = File.OpenRead(source))
{
byte[] data = new byte[stream.Length];
stream.Read(data, 0, data.Length);
content = new ByteArrayContent(data)
{
Headers = { ContentType = new MediaTypeHeaderValue("text/plain") }
};
}
HttpResponseMessage response = await httpClient.PutAsync(url, content);

I know the question was for P12, but Google lead me here when I was looking to do this for the newer, preferred JSON method. I pieced this together with other samples and sites I found. Hope this help save some time.
public string GetSignedURL()
{
var myObj = "theObject";
var scopes = new string[] { "https://www.googleapis.com/auth/devstorage.read_write" };
var myBucket = "theBucket";
ServiceAccountCredential cred;
using ( var stream = new FileStream(#"\path to\private-key.json", FileMode.Open, FileAccess.Read) )
{
cred = GoogleCredential.FromStream(stream)
.CreateScoped(scopes)
.UnderlyingCredential as ServiceAccountCredential;
}
var urlSigner = UrlSigner.FromServiceAccountCredential(cred);
return urlSigner.Sign(myBucket, myObj, TimeSpan.FromHours(1), HttpMethod.Get);
}
A list of Scopes can be found here

The .NET client doesn't support signing URLs (it is an XML-only API), so you will need to either make a callout to a tool like gsutil or generate an RSA signature internal to your application (Signing and verifying signatures with RSA C#)

This is my google signer code, One can make it more dynamic as per their needs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Web;
using System.Security.Cryptography.X509Certificates;
namespace HHAFSGoogle
{
static class GoogleSigner
{
private static string hashAlgo = "SHA256";
public static string ServiceAccountEmail
{
get
{
return "XXXXXXXXXXXXX-YYYYYYYYYYYYYYYYYYYYYYYY#developer.gserviceaccount.com";
}
}
public static string GoogleSecreat
{
get
{
return "notasecret";
}
}
public static string GoogleBucketDir
{
get
{
return "MyBucketDirectory";
}
}
public static string GoogleBucketName
{
get
{
return "MyBucket";
}
}
public static string CertiFilelocation
{
get
{
return System.Web.HttpContext.Current.Server.MapPath("p12file.p12");
}
}
/// <summary>
/// Get URL signature
/// </summary>
/// <param name="base64EncryptedData"></param>
/// <param name="certiFilelocation"></param>
/// <returns></returns>
public static string GetSignature(string base64EncryptedData, string certiFilelocation)
{
X509Certificate2 certificate = new X509Certificate2(certiFilelocation, GoogleSecreat, X509KeyStorageFlags.Exportable);
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)certificate.PrivateKey;
RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider();
privateKey1.ImportParameters(csp.ExportParameters(true));
csp.ImportParameters(privateKey1.ExportParameters(true));
byte[] data = Encoding.UTF8.GetBytes(base64EncryptedData.Replace("\r", ""));
byte[] signature = privateKey1.SignData(data, hashAlgo);
bool isValid = privateKey1.VerifyData(data, hashAlgo, signature);
if (isValid)
{
return Convert.ToBase64String(signature);
}
else
{
return string.Empty;
}
}
/// <summary>
/// Get signed URL by Signature
/// </summary>
/// <param name="fileName"></param>
/// <param name="method"></param>
/// <param name="content_type"></param>
/// <param name="duration"></param>
/// <returns></returns>
public static string GetSignedURL(string fileName, string method = "GET", string content_type = "", int duration = 10)
{
TimeSpan span = (DateTime.UtcNow.AddMinutes(10) - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc));
var expires = Math.Round(span.TotalSeconds, 0);
// Encode filename, so URL characters like %20 for space could be handled properly in signature
fileName = HttpUtility.UrlPathEncode(fileName);
// Generate a string to sign
StringBuilder sbFileParam = new StringBuilder();
sbFileParam.AppendLine(method); //Could be GET, PUT, DELETE, POST
// /* Content-MD5 */ "\n" .
sbFileParam.AppendLine();
sbFileParam.AppendLine(content_type); // Type of content you would upload e.g. image/jpeg
sbFileParam.AppendLine(expires.ToString()); // Time when link should expire and shouldn't work longer
sbFileParam.Append("/" + GoogleBucketName + "/" + fileName);
var signature = System.Web.HttpContext.Current.Server.UrlEncode(GetSignature(sbFileParam.ToString(), CertiFilelocation));
return ("https://storage.googleapis.com/MyBucket/" + fileName +
"?response-content-disposition=attachment;&GoogleAccessId=" + ServiceAccountEmail +
"&Expires=" + expires + "&Signature=" + signature);
}
}
}
and to download file call above class to get signed url
GoogleSigner.GetSignedURL(bucketFileName)
and to upload file call above class to get signed url for upload url
GoogleSigner.GetSignedURL(fileName, "PUT", type);

Related

Google social login with HttpClient in .NET Core C# console app?

I developed an ASP.NET Core C# Web API Console App project for learning purposes. I want to integrate the Google External Login/Authentication for my Web API. As an exercise, I want to authenticate a user with google from a console application using HttpClient, retrieve a token, and then use this token with every request to my Web API endpoints. I can't find any useful tutorial(s) that explain how to do this, both in the console app and setting up the Web API. How can I accomplish this? Thanks.
There is a great sample by google for .net console app in this repo, take a look at the code
https://github.com/googlesamples/oauth-apps-for-windows/tree/master/OAuthConsoleApp
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace OAuthConsoleApp
{
class Program
{
const string AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth";
static async Task<int> Main(string[] args)
{
if (args.Length != 2)
{
Console.WriteLine("Required command line arguments: client-id client-secret");
return 1;
}
string clientId = args[0];
string clientSecret = args[1];
Console.WriteLine("+-----------------------+");
Console.WriteLine("| Sign in with Google |");
Console.WriteLine("+-----------------------+");
Console.WriteLine("");
Console.WriteLine("Press any key to sign in...");
Console.ReadKey();
Program p = new Program();
await p.DoOAuthAsync(clientId, clientSecret);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
return 0;
}
// ref http://stackoverflow.com/a/3978040
public static int GetRandomUnusedPort()
{
var listener = new TcpListener(IPAddress.Loopback, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
private async Task DoOAuthAsync(string clientId, string clientSecret)
{
// Generates state and PKCE values.
string state = GenerateRandomDataBase64url(32);
string codeVerifier = GenerateRandomDataBase64url(32);
string codeChallenge = Base64UrlEncodeNoPadding(Sha256Ascii(codeVerifier));
const string codeChallengeMethod = "S256";
// Creates a redirect URI using an available port on the loopback address.
string redirectUri = $"http://{IPAddress.Loopback}:{GetRandomUnusedPort()}/";
Log("redirect URI: " + redirectUri);
// Creates an HttpListener to listen for requests on that redirect URI.
var http = new HttpListener();
http.Prefixes.Add(redirectUri);
Log("Listening..");
http.Start();
// Creates the OAuth 2.0 authorization request.
string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}",
AuthorizationEndpoint,
Uri.EscapeDataString(redirectUri),
clientId,
state,
codeChallenge,
codeChallengeMethod);
// Opens request in the browser.
Process.Start(authorizationRequest);
// Waits for the OAuth authorization response.
var context = await http.GetContextAsync();
// Brings the Console to Focus.
BringConsoleToFront();
// Sends an HTTP response to the browser.
var response = context.Response;
string responseString = "<html><head><meta http-equiv='refresh' content='10;url=https://google.com'></head><body>Please return to the app.</body></html>";
byte[] buffer = Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
var responseOutput = response.OutputStream;
await responseOutput.WriteAsync(buffer, 0, buffer.Length);
responseOutput.Close();
http.Stop();
Log("HTTP server stopped.");
// Checks for errors.
string error = context.Request.QueryString.Get("error");
if (error is object)
{
Log($"OAuth authorization error: {error}.");
return;
}
if (context.Request.QueryString.Get("code") is null
|| context.Request.QueryString.Get("state") is null)
{
Log($"Malformed authorization response. {context.Request.QueryString}");
return;
}
// extracts the code
var code = context.Request.QueryString.Get("code");
var incomingState = context.Request.QueryString.Get("state");
// Compares the receieved state to the expected value, to ensure that
// this app made the request which resulted in authorization.
if (incomingState != state)
{
Log($"Received request with invalid state ({incomingState})");
return;
}
Log("Authorization code: " + code);
// Starts the code exchange at the Token Endpoint.
await ExchangeCodeForTokensAsync(code, codeVerifier, redirectUri, clientId, clientSecret);
}
async Task ExchangeCodeForTokensAsync(string code, string codeVerifier, string redirectUri, string clientId, string clientSecret)
{
Log("Exchanging code for tokens...");
// builds the request
string tokenRequestUri = "https://www.googleapis.com/oauth2/v4/token";
string tokenRequestBody = string.Format("code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&client_secret={4}&scope=&grant_type=authorization_code",
code,
Uri.EscapeDataString(redirectUri),
clientId,
codeVerifier,
clientSecret
);
// sends the request
HttpWebRequest tokenRequest = (HttpWebRequest)WebRequest.Create(tokenRequestUri);
tokenRequest.Method = "POST";
tokenRequest.ContentType = "application/x-www-form-urlencoded";
tokenRequest.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
byte[] tokenRequestBodyBytes = Encoding.ASCII.GetBytes(tokenRequestBody);
tokenRequest.ContentLength = tokenRequestBodyBytes.Length;
using (Stream requestStream = tokenRequest.GetRequestStream())
{
await requestStream.WriteAsync(tokenRequestBodyBytes, 0, tokenRequestBodyBytes.Length);
}
try
{
// gets the response
WebResponse tokenResponse = await tokenRequest.GetResponseAsync();
using (StreamReader reader = new StreamReader(tokenResponse.GetResponseStream()))
{
// reads response body
string responseText = await reader.ReadToEndAsync();
Console.WriteLine(responseText);
// converts to dictionary
Dictionary<string, string> tokenEndpointDecoded = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseText);
string accessToken = tokenEndpointDecoded["access_token"];
await RequestUserInfoAsync(accessToken);
}
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.ProtocolError)
{
var response = ex.Response as HttpWebResponse;
if (response != null)
{
Log("HTTP: " + response.StatusCode);
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
// reads response body
string responseText = await reader.ReadToEndAsync();
Log(responseText);
}
}
}
}
}
private async Task RequestUserInfoAsync(string accessToken)
{
Log("Making API Call to Userinfo...");
// builds the request
string userinfoRequestUri = "https://www.googleapis.com/oauth2/v3/userinfo";
// sends the request
HttpWebRequest userinfoRequest = (HttpWebRequest)WebRequest.Create(userinfoRequestUri);
userinfoRequest.Method = "GET";
userinfoRequest.Headers.Add(string.Format("Authorization: Bearer {0}", accessToken));
userinfoRequest.ContentType = "application/x-www-form-urlencoded";
userinfoRequest.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
// gets the response
WebResponse userinfoResponse = await userinfoRequest.GetResponseAsync();
using (StreamReader userinfoResponseReader = new StreamReader(userinfoResponse.GetResponseStream()))
{
// reads response body
string userinfoResponseText = await userinfoResponseReader.ReadToEndAsync();
Log(userinfoResponseText);
}
}
/// <summary>
/// Appends the given string to the on-screen log, and the debug console.
/// </summary>
/// <param name="output">String to be logged</param>
private void Log(string output)
{
Console.WriteLine(output);
}
/// <summary>
/// Returns URI-safe data with a given input length.
/// </summary>
/// <param name="length">Input length (nb. output will be longer)</param>
/// <returns></returns>
private static string GenerateRandomDataBase64url(uint length)
{
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] bytes = new byte[length];
rng.GetBytes(bytes);
return Base64UrlEncodeNoPadding(bytes);
}
/// <summary>
/// Returns the SHA256 hash of the input string, which is assumed to be ASCII.
/// </summary>
private static byte[] Sha256Ascii(string text)
{
byte[] bytes = Encoding.ASCII.GetBytes(text);
using (SHA256Managed sha256 = new SHA256Managed())
{
return sha256.ComputeHash(bytes);
}
}
/// <summary>
/// Base64url no-padding encodes the given input buffer.
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
private static string Base64UrlEncodeNoPadding(byte[] buffer)
{
string base64 = Convert.ToBase64String(buffer);
// Converts base64 to base64url.
base64 = base64.Replace("+", "-");
base64 = base64.Replace("/", "_");
// Strips padding.
base64 = base64.Replace("=", "");
return base64;
}
// Hack to bring the Console window to front.
// ref: http://stackoverflow.com/a/12066376
[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
public void BringConsoleToFront()
{
SetForegroundWindow(GetConsoleWindow());
}
}
}
It sounds like an interesting project. I've not done this before, but here's how I would approach it...
The default template in Visual Studio for an ASP .NET Core Web Application is already a console application. Just check the Output Type field in the Application tab of the project's Properties window and it will show you the value Console.
So I would start with that project template. In the project creation wizard, change the Authentication to Individual User Accounts and you'll get an ASP .NET Core Web Application pretty much ready to implement your external login logic.
Then rip out all the files and code related to web applications. Change the Startup class so that it looks like a Startup for a normal console project and boom! You should have a bare-bones console app with external logins!

C# version of the javascript function Cryptojs.HmacSHA384

I am trying to connect to an API where I need to authenticate by passing in the header a hashed string in my C# application. In the documentation from the provider though they only have an example of how to hash the string using JS functions in a react application, bellow is the screenshot of the code snippet found in said documentation.
I have already found an article here on stackoverflow on an alternative for the btoa function but I am completely stuck on the CryptoJS.HmacSHA384 function alternative. I have tried using the System.Security.Cryptography.HMACSHA384 class but can't figure out how to encode the strings I am passing to the method in order to obtain the same result, causing an Authorization denied when I try to connect to the API endpoint. Bellow is the code from the method I have written so far (that is not working):
public void myMethodToConnet(string user, string pass, string custId, string partId, string partKey, string ver, string commId)
{
int resetPasswordErrorCode;
_user = user;
_password = pass;
_rooftopId = custId;
_partnerId = partId;
_partnerKey = Encoding.UTF8.GetBytes(partKey);
_version = ver;
_communityId = commId;
_url = "url";
_token = GetToken().Result;
using (HMACSHA384 hmac = new HMACSHA384(_partnerKey))
{
_hmac = hmac.ComputeHash(Encoding.UTF8.GetBytes(_token));
_hash = BTOA($"{_user}:{Encoding.UTF8.GetString(_hmac)}:{_password}");
}
ActivateToken().Result;
}
private static string BTOA(string toEncode)
{
byte[] bytes = Encoding.GetEncoding(28591).GetBytes(toEncode);
string result = System.Convert.ToBase64String(bytes);
return result;
}
private async Task<int> ActivateToken()
{
CheckPasswordRequest request = new CheckPasswordRequest();
CheckPasswordResponse response = new CheckPasswordResponse();
StringContent content;
string jsonString;
string apiResponse;
int errorCode = 0;
using (HttpClient httpClient = new HttpClient())
{
request = new CheckPasswordRequest() { RooftopId = _rooftopId };
jsonString = JsonConvert.SerializeObject(request);
content = new StringContent(jsonString, Encoding.UTF8, "application/json");
httpClient.DefaultRequestHeaders.Add("Authorization", $"DataHub-Hash {_hash}");
using (var resp = await httpClient.PostAsync($"{_url}/CheckPassword", content))
{
apiResponse = await resp.Content.ReadAsStringAsync();
response = JsonConvert.DeserializeObject<CheckPasswordResponse>(apiResponse);
errorCode = response.ErrorCode;
}
}
return errorCode;
}
Thanks!
I finally figured it out by creating a simple HTML page where I ran the JS commands and printed out the output to than compare it to what the output in my C# application was. At the end the solution was that of converting the byte array output of the encryption algorithm to hex, to do so I created this helper method:
public static string ByteToString(byte[] input)
{
string output= "";
for (int i = 0; i < input.Length; i++)
{
output+= input[i].ToString("X2");
}
return (output);
}
by inserting the output string of this method between the user and password and running my BTOA helper method returned the string the API was expecting and managed to authenticate.

How to create and upload data to json file in s3?

Hi I am writing Lambda function in .Net core. My requirement is Post api will send employee data. When data is received I want to store it in S3 bucket. The approach I am following is whenever Api sends data to lambda, Empid is unique. Each time I want to create one json file and name of the file should be equal to emp id. I am trying to write my function as below.
public async Task<string> FunctionHandler(Employee input, ILambdaContext context)
{
string bucketname = "someunquebucket";
var client = new AmazonS3Client();
bool doesBucketExists = await AmazonS3Util.DoesS3BucketExistV2Async(client,bucketname);
if(!doesBucketExists)
{
var request = new PutBucketRequest
{
BucketName = "someunquebucket",
};
var response = await client.PutBucketAsync(request);
}
using (var stream = new MemoryStream(bin))
{
var request = new PutObjectRequest
{
BucketName = bucketname,
InputStream = stream,
ContentType = "application/json",
Key = input.emp_id
};
var response = await client.PutObjectAsync(request).ConfigureAwait(false);
}
}
In the above code, PutObjectRequest is used to write data to s3 bucket. I am adding few parameters like bucketname etc. In the function I am receiving Employee data. Now I want to create json file with emp id. I found above code but I am not sure what to pass in place of bin. Can someone help me to execute the above function. Any help would be appreciated. Thanks
I am new to .Net Core, and came across your post while trying to solve a similar problem.
Here is how I was able to upload a json response string to my s3 Bucket (Hope it helps)
String timeStamp = DateTime.Now.ToString("yyyyMMddHHmmssfff");
byte[] byteArray = Encoding.ASCII.GetBytes(jsonString);
var seekableStream = new MemoryStream(byteArray);
seekableStream.Position = 0;
var putRequest = new PutObjectRequest
{
BucketName = this.BucketName,
Key = timeStamp+"_"+reg + ".json",
InputStream = seekableStream
};
try
{
var response2 = await this.S3Client.PutObjectAsync(putRequest);}
Hope you sorted your issue.
This is the example code from AWS SDK Code Examples.
https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/welcome.html
https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/dotnetv3/S3
https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/dotnetv3/S3/S3_Basics/S3Bucket.cs#L45
/// <summary>
/// Shows how to upload a file from the local computer to an Amazon S3
/// bucket.
/// </summary>
/// <param name="client">An initialized Amazon S3 client object.</param>
/// <param name="bucketName">The Amazon S3 bucket to which the object
/// will be uploaded.</param>
/// <param name="objectName">The object to upload.</param>
/// <param name="filePath">The path, including file name, of the object
/// on the local computer to upload.</param>
/// <returns>A boolean value indicating the success or failure of the
/// upload procedure.</returns>
public static async Task<bool> UploadFileAsync(
IAmazonS3 client,
string bucketName,
string objectName,
string filePath)
{
var request = new PutObjectRequest
{
BucketName = bucketName,
Key = objectName,
FilePath = filePath,
};
var response = await client.PutObjectAsync(request);
if (response.HttpStatusCode == System.Net.HttpStatusCode.OK)
{
Console.WriteLine($"Successfully uploaded {objectName} to {bucketName}.");
return true;
}
else
{
Console.WriteLine($"Could not upload {objectName} to {bucketName}.");
return false;
}
}
This is a modified service I wrote to upload json strings as json files both with sync and async methods:
public class AwsS3Service
{
private readonly IAmazonS3 _client;
private readonly string _bucketName;
private readonly string _keyPrefix;
/// <param name="bucketName">The Amazon S3 bucket to which the object
/// will be uploaded.</param>
public AwsS3Service(string accessKey, string secretKey, string bucketName, string keyPrefix)
{
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
_client = new AmazonS3Client(credentials);
_bucketName=bucketName;
_keyPrefix=keyPrefix;
}
public bool UploadJson(string objectName, string json, int migrationId, long orgId)
{
return Task.Run(() => UploadJsonAsync(objectName, json, migrationId, orgId)).GetAwaiter().GetResult();
}
public async Task<bool> UploadJsonAsync(string objectName, string json, int migrationId, long orgId)
{
var request = new PutObjectRequest
{
BucketName = $"{_bucketName}",
Key = $"{_keyPrefix}{migrationId}/{orgId}/{objectName}",
InputStream = new MemoryStream(Encoding.UTF8.GetBytes(json)),
};
var response = await _client.PutObjectAsync(request);
if (response.HttpStatusCode == System.Net.HttpStatusCode.OK)
{
return true;
}
else
{
return false;
}
}
}

WOPI Host implementation, trying to render doc in iframe asp.net mvc

I'm trying to get Wopi host implementation in an ASP.NET MVC application.
Using this project
https://code.msdn.microsoft.com/office/Building-an-Office-Web-f98650d6
I don't get any calls hitting my API Controler
Discovery URL
<action name="view"
ext="docx"
default="true"
urlsrc="http://word-edit.officeapps.live.com/wv/wordviewerframe.aspx?<ui=UI_LLCC&><rs=DC_LLCC&><showpagestats=PERFSTATS&>" />
URL generated by my application
http://word-edit.officeapps.live.com/we/wordeditorframe.aspx?WOPISrc=http%3a%2f%2flocalhost%3a32876%2fapi%2fwopi%2ffiles%2ftest.docx&access_token=XskWxXK0Nro%3dhwYoiCFehrYAx85XQduYQHYQE9EEPC6EVgqaMiCp4%2bg%3d
I am using Local Host for testing purpose
Controller Route
[RoutePrefix("api/wopi")]
public class FilesController : ApiController
[Route("files/{name}/")]
public CheckFileInfo GetFileInfo(string name, string access_token)
{
Validate(name, access_token);
var fileInfo = _fileHelper.GetFileInfo(name);
bool updateEnabled = false;
if (bool.TryParse(WebConfigurationManager.AppSettings["updateEnabled"].ToString(), out updateEnabled))
{
fileInfo.SupportsUpdate = updateEnabled;
fileInfo.UserCanWrite = updateEnabled;
fileInfo.SupportsLocks = updateEnabled;
}
return fileInfo;
}
// GET api/<controller>/5
/// <summary>
/// Get a single file contents
/// </summary>
/// <param name="name">filename</param>
/// <returns>a file stream</returns>
[Route("files/{name}/contents")]
public HttpResponseMessage Get(string name, string access_token)
{
try
{
Validate(name, access_token);
var file = HostingEnvironment.MapPath("~/App_Data/" + name);
var responseMessage = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(file, FileMode.Open, FileAccess.Read);
responseMessage.Content = new StreamContent(stream);
responseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
return responseMessage;
}
catch (Exception ex)
{
var errorResponseMessage = new HttpResponseMessage(HttpStatusCode.InternalServerError);
var stream = new MemoryStream(UTF8Encoding.Default.GetBytes(ex.Message ?? ""));
errorResponseMessage.Content = new StreamContent(stream);
return errorResponseMessage;
}
}
It is not hitting to the API URL
WOPISrc cannot be 'localhost', it must be a link that office application server can access,
While you use office online application, then you need to provide a public ip or domain name of your wopi server instead of 'localhost'

WebAPI and Authorization Basic

I created a WebAPI but now I want to secure it with Basic Authorization.
// POST the data to the API
using (var client = new WebClient())
{
client.Headers.Add("Content-Type", "application/json");
client.Headers.Add(HttpRequestHeader.Authorization, "Basic" + Convert.ToBase64String(Encoding.ASCII.GetBytes(credentials)));
string json = JsonConvert.SerializeObject(ex);
string content = client.UploadString("http://myURL/v1/endpoint", json);
}
Below, how I post the data. Now, I would like to create a function that I can add to my controller or my Application_Start(). It will check:
if the request.Headers.Authorization is != null
if the request.Headers.Authorization.Scheme is != "Basic"
if there are some parameters
get the parameter and decode it to create a pair (SecretId/SecretKey)
call a service to check in the DB if there is a client with this pair
create an identity with IPrincipal
The thing is I don't know the best way is to create a customAttribute or a filter or something else. There is plenty of different way to do this but I would like to understand the difference.
Create the below-mentioned Filter in your project and use it at top of your web API method as :
**[BasicAuth]**
/// <summary>
/// Basic Authentication Filter Class
/// </summary>
public class BasicAuthAttribute : ActionFilterAttribute
{
/// <summary>
/// Called when [action executing].
/// </summary>
/// <param name="filterContext">The filter context.</param>
public override void OnActionExecuting(HttpActionContext filterContext)
{
try
{
if (filterContext.Request.Headers.Authorization == null)
{
// Client authentication failed due to invalid request.
filterContext.Response = new System.Net.Http.HttpResponseMessage()
{
StatusCode = HttpStatusCode.Unauthorized,
Content = new StringContent("{\"error\":\"invalid_client\"}", Encoding.UTF8, "application/json")
};
filterContext.Response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic", "realm=xxxx"));
}
else if (filterContext.Request.Headers.Authorization.Scheme != "Basic" ||
string.IsNullOrEmpty(filterContext.Request.Headers.Authorization.Parameter))
{
// Client authentication failed due to invalid request.
filterContext.Response = new System.Net.Http.HttpResponseMessage()
{
StatusCode = HttpStatusCode.BadRequest,
Content = new StringContent("{\"error\":\"invalid_request\"}", Encoding.UTF8, "application/json")
};
}
else
{
var authToken = filterContext.Request.Headers.Authorization.Parameter;
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string usernamePassword = encoding.GetString(Convert.FromBase64String(authToken));
int seperatorIndex = usernamePassword.IndexOf(':');
string clientId = usernamePassword.Substring(0, seperatorIndex);
string clientSecret = usernamePassword.Substring(seperatorIndex + 1);
if (!ValidateApiKey(clientId, clientSecret))
{
// Client authentication failed due to invalid credentials
filterContext.Response = new System.Net.Http.HttpResponseMessage()
{
StatusCode = HttpStatusCode.Unauthorized,
Content = new StringContent("{\"error\":\"invalid_client\"}", Encoding.UTF8, "application/json")
};
}
// Successfully finished HTTP basic authentication
}
}
catch (Exception ex)
{
// Client authentication failed due to internal server error
filterContext.Response = new System.Net.Http.HttpResponseMessage()
{
StatusCode = HttpStatusCode.BadRequest,
Content = new StringContent("{\"error\":\"invalid_request\"}", Encoding.UTF8, "application/json")
};
}
}
/// <summary>
/// Validates the API key.
/// </summary>
/// <param name="recievedKey">The recieved key.</param>
/// <returns></returns>
private bool ValidateApiKey(string clientId, string clientSecret)
{
if (your condition satisfies)
{
return true;
}
return false;
}
}
I found few interesting articles about handlers/filter and attribute. I don't want to override [Authorize] so I will probably do an Authentication Filter.
Below some good links:
filter with attribute
filter
handler
#Nkosi: Cheers to confirm. I'm going to change the code a little bit because I don't want to use an Attribute but rather an filter that I put in the WebApiConfig

Categories