klactose
klactose

Reputation: 1222

Getting 403 (Forbidden) when attempting to POST file to Google Cloud Storage from service account

I'm wondering if anyone can tell me the proper syntax & formatting for a service account to send a POST Object to bucket request? I'm attempting it programmatically using the HttpComponents library. I manage to get a token from my GoogleCredential, but every time I construct the POST request, I get:

HTTP/1.1 403 Forbidden

<?xml version='1.0' encoding='UTF-8'?><Error><Code>AccessDenied</Code><Message>Access denied.</Message><Details>bucket-name</Details></Error>

The Google documentation that describes the request methods, mentions posting using html forms, but I'm hoping that wasn't suggesting the ONLY way to get the job done. I know that HttpComponents has a way to explicitly create form data by using UrlEncodedFormEntity, but it doesn't support multipart data. Which is why I went with using the MultipartEntity class. My code is below:

MultipartEntity entity = new MultipartEntity( HttpMultipartMode.BROWSER_COMPATIBLE );
String token = credential.getAccessToken();
entity.addPart("Authorization", new StringBody("OAuth " + token));
String date = formatDate(new Date());
entity.addPart("Date", new StringBody(date));
entity.addPart("Content-Type", new StringBody("multipart/form-data"));
entity.addPart("bucket", new StringBody(bucket));
entity.addPart("key", new StringBody("fileName"));
entity.addPart("success_action_redirect", new StringBody("/storage"));
File uploadFile = new File("pathToFile");
FileBody fileBody = new FileBody(uploadFile, "text/xml");
entity.addPart("file", fileBody);
httppost.setEntity(entity);
System.out.println("Posting URI = "+httppost.toString());
HttpResponse response = client.execute(httppost);
HttpEntity resp_entity = response.getEntity();

As I mentioned, I am able to get an actual token, so I'm pretty sure the problem is in how I've formed the request as opposed to not being properly authenticated.

Keep in mind:

  1. This is being performed by a service account.
  2. Which means that it does have Read/Write access

Thanks for reading, and I appreciate any help!

Upvotes: 2

Views: 2759

Answers (1)

fejta
fejta

Reputation: 3121

It looks like you are conflating PUT and POST methods, given that you are using an OAuth access token that only works with PUT and form fields which only work with POST.

The easiest way to upload an object will be to use something like the following:

HttpPut put = new HttpPut("https://storage.googleapis.com/" + BUCKET + "/" + OBJECT);
put.addHeader("Authorization", "Bearer " + credential.getAccessToken());
put.setEntity(new StringEntity("object data"));
client.execute(put);

It is also possible, but more complicated than just using a PUT, to create a signed HTML form that users can use to upload objects using POST. Your form requires a signed policy document similar to the following:

PolicyDocument = {"expiration": "2010-06-16T11:11:11Z",
 "conditions": [
   ["starts-with", "$key", "" ],
   {"acl": "bucket-owner-read" },
   {"bucket": "travel-maps"},
   {"success_action_redirect": "http://www.example.com/success_notification.html" },
   ["eq", "$Content-Type", "image/jpeg" ],
   ["content-length-range", 0, 1000000]
  ]
}
Policy = Base_64_Encoding_Of(PolicyDocument)
MessageDigest = Sha256_With_RSA(SecretKey, Policy)
Signature = Base64_Encoding_Of(MessageDigest)

which results in the following HTML:

<form action="http://travel-maps.storage.googleapis.com" method="post" enctype="multipart/form-data">
<input type="text" name="key" value="">
<input type="hidden" name="bucket" value="travel-maps">
<input type="hidden" name="Content-Type" value="image/jpeg">
<input type="hidden" name="GoogleAccessId" value="[email protected]">
<input type="hidden" name="acl" value="bucket-owner-read">
<input type="hidden" name="success_action_redirect" value="http://www.example.com/success_notification.html">
<input type="hidden" name="policy" value="ajUJTm9jAHADNmF0aW9uIjogIjIwMTAtMDYtMTZUMTSAMPLEE6MTE6MTFaIiwNCSAMPLEiAgWyJzdGFydSAMPLEAiaHR0cDovL3maWNhSAMPLEIiB9LASAMPLEWN0aW9uX3JlZGlyZW">
<input type="hidden" name="signature" value="BSAMPLEaASAMPLE6SAMPLE+SAMPPLEqSAMPLEPSAMPLE+SAMPLEgSAMPLEzCPlgWREeF7oPGowkeKk7J4WApzkzxERdOQmAdrvshKSzUHg8Jqp1lw9tbiJfE2ExdOOIoJVmGLoDeAGnfzCd4fTsWcLbal9sFpqXsQI8IQi1493mw=">

<input name="file" type="file">
<input type="submit" value="Upload">
</form>

Note the policy and signatures fields as well as the other hidden fields, which match the conditions specified in the policy document. Specifically bucket == travel-maps, acl == bucket-owner-read, etc.

Upvotes: 2

Related Questions