Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
44af239
#2421 Ordered headers after sign. Otherwise signature does not match.
frantello Sep 9, 2017
37a2d32
#2421 Fixed some check style.
frantello Sep 9, 2017
a554578
#2421 Fixed some check style.
frantello Sep 9, 2017
aba2220
Merge branch 'master' of git@github.com:frantello/google-cloud-java.git
frantello Sep 10, 2017
d715bfe
Merge branch 'master' of git@github.com:frantello/google-cloud-java.git
frantello Sep 10, 2017
ee95362
Merge branch 'master' of git@github.com:frantello/google-cloud-java.git
frantello Sep 10, 2017
7862624
#2421 Fixed some check style.
frantello Sep 10, 2017
b25f54e
Merge branch 'master' of git@github.com:frantello/google-cloud-java.git
frantello Sep 10, 2017
fb62be6
#2421 Added license
frantello Sep 10, 2017
bd5f566
#2421 Used URI instead Path for storage resources
frantello Sep 10, 2017
54c03bd
Revert " #2421 Used URI instead Path for storage resources"
frantello Sep 10, 2017
49f45e1
Merge branch 'master' of git@github.com:frantello/google-cloud-java.git
frantello Sep 10, 2017
a0b6e2a
frantello Sep 10, 2017
0aec379
#2421 Fixed see URLs
frantello Sep 10, 2017
b030d79
Merge branch 'master' of git@github.com:frantello/google-cloud-java.git
frantello Sep 10, 2017
43a8a7d
#2422 Fixed typo
frantello Dec 31, 2017
7271dfd
#2422 Added checks to ensure that required fields (verb, resource,
frantello Dec 31, 2017
fdf91c4
#2422 Canonical extension headers serialization
frantello Jan 1, 2018
932d552
#2422 Refactoring underscores to camelcase
frantello Jan 1, 2018
d7f67c2
Fixed URL inline
frantello Jan 23, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.storage;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Canonical extension header serializer.
*
* @see <a href=
* "https://cloud.google.com/storage/docs/access-control/signed-urls#about-canonical-extension-headers">
* Canonical Extension Headers</a>
*/
public class CanonicalExtensionHeadersSerializer {

private static final char HEADER_SEPARATOR = ':';

public StringBuilder serialize(Map<String, String> canonicalizedExtensionHeaders) {

StringBuilder serializedHeaders = new StringBuilder();

if (canonicalizedExtensionHeaders == null ||
canonicalizedExtensionHeaders.isEmpty()) {

return serializedHeaders;
}

// Make all custom header names lowercase.
Map<String, String> lowercaseHeaders = new HashMap<>();
for (String headerName : new ArrayList<>(canonicalizedExtensionHeaders.keySet())) {

String lowercaseHeaderName = headerName.toLowerCase();

// If present, remove the x-goog-encryption-key and x-goog-encryption-key-sha256 headers.
if ("x-goog-encryption-key".equals(lowercaseHeaderName) ||
"x-goog-encryption-key-sha256".equals(lowercaseHeaderName)) {

continue;
}

lowercaseHeaders.put(lowercaseHeaderName, canonicalizedExtensionHeaders.get(headerName));
}

// Sort all custom headers by header name using a lexicographical sort by code point value.
List<String> sortedHeaderNames = new ArrayList<>(lowercaseHeaders.keySet());
Collections.sort(sortedHeaderNames);

for (String headerName : sortedHeaderNames) {
serializedHeaders
.append(headerName).append(HEADER_SEPARATOR)
.append(lowercaseHeaders.get(headerName)
// Remove any whitespace around the colon that appears after the header name.
.trim()
// Replace any folding whitespace or newlines (CRLF or LF) with a single space.
.replaceAll("[\\s]{2,}"," ")
.replaceAll("(\\t|\\r?\\n)+", " "))
// Append a newline (U+000A) to each custom header.
.append(SignatureInfo.COMPONENT_SEPARATOR);
}

// Concatenate all custom headers
return serializedHeaders;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.cloud.storage;

import static com.google.common.base.Preconditions.checkArgument;
import java.net.URI;
import java.util.Map;

/**
* Signature Info holds payload components of the string that requires signing.
*
* @see <a href=
* "https://cloud.google.com/storage/docs/access-control/signed-urls#string-components">
* Components</a>
*/
public class SignatureInfo {

public static final char COMPONENT_SEPARATOR = '\n';

private final HttpMethod httpVerb;
private final String contentMd5;
private final String contentType;
private final long expiration;
private final Map<String, String> canonicalizedExtensionHeaders;
private final URI canonicalizedResource;

private SignatureInfo(Builder builder) {
this.httpVerb = builder.httpVerb;
this.contentMd5 = builder.contentMd5;
this.contentType = builder.contentType;
this.expiration = builder.expiration;
this.canonicalizedExtensionHeaders = builder.canonicalizedExtensionHeaders;
this.canonicalizedResource = builder.canonicalizedResource;
}

/**
* Constructs payload to be signed.
*
* @return paylod to sign
* @see <a href="https://cloud.google.com/storage/docs/access-control#Signed-URLs">Signed URLs</a>
*/
public String constructUnsignedPayload() {
StringBuilder payload = new StringBuilder();

payload.append(httpVerb.name()).append(COMPONENT_SEPARATOR);
if (contentMd5 != null) {
payload.append(contentMd5);
}
payload.append(COMPONENT_SEPARATOR);

if (contentType != null) {
payload.append(contentType);
}
payload.append(COMPONENT_SEPARATOR);

payload.append(expiration).append(COMPONENT_SEPARATOR);

if (canonicalizedExtensionHeaders != null) {
payload.append(new CanonicalExtensionHeadersSerializer()
.serialize(canonicalizedExtensionHeaders));
}

payload.append(canonicalizedResource);

return payload.toString();
}

public HttpMethod getHttpVerb() {
return httpVerb;
}

public String getContentMd5() {
return contentMd5;
}

public String getContentType() {
return contentType;
}

public long getExpiration() {
return expiration;
}

public Map<String, String> getCanonicalizedExtensionHeaders() {
return canonicalizedExtensionHeaders;
}

public URI getCanonicalizedResource() {
return canonicalizedResource;
}

public static final class Builder {

private final HttpMethod httpVerb;
private String contentMd5;
private String contentType;
private final long expiration;
private Map<String, String> canonicalizedExtensionHeaders;
private final URI canonicalizedResource;

/**
* Constructs builder.
*
* @param httpVerb the HTTP method
* @param expiration the EPOX expiration date
* @param canonicalizedResource the resource URI
* @throws IllegalArgumentException if required field is not provided.
*/
public Builder(HttpMethod httpVerb, long expiration, URI canonicalizedResource) {
this.httpVerb = httpVerb;
this.expiration = expiration;
this.canonicalizedResource = canonicalizedResource;
}

public Builder(SignatureInfo signatureInfo) {
this.httpVerb = signatureInfo.httpVerb;
this.contentMd5 = signatureInfo.contentMd5;
this.contentType = signatureInfo.contentType;
this.expiration = signatureInfo.expiration;
this.canonicalizedExtensionHeaders = signatureInfo.canonicalizedExtensionHeaders;
this.canonicalizedResource = signatureInfo.canonicalizedResource;
}

public Builder setContentMd5(String contentMd5) {
this.contentMd5 = contentMd5;

return this;
}

public Builder setContentType(String contentType) {
this.contentType = contentType;

return this;
}

public Builder setCanonicalizedExtensionHeaders(
Map<String, String> canonicalizedExtensionHeaders) {
this.canonicalizedExtensionHeaders = canonicalizedExtensionHeaders;

return this;
}

/**
* Creates an {@code SignatureInfo} object from this builder.
*/
public SignatureInfo build() {
checkArgument(httpVerb != null, "Required HTTP method");
checkArgument(canonicalizedResource != null, "Required canonicalized resource");
checkArgument(expiration >= 0, "Expiration must be greater than or equal to zero");

return new SignatureInfo(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -854,7 +855,7 @@ class SignUrlOption implements Serializable {
private final Object value;

enum Option {
HTTP_METHOD, CONTENT_TYPE, MD5, SERVICE_ACCOUNT_CRED
HTTP_METHOD, CONTENT_TYPE, MD5, EXT_HEADERS, SERVICE_ACCOUNT_CRED
}

private SignUrlOption(Option option, Object value) {
Expand All @@ -874,7 +875,7 @@ Object getValue() {
* The HTTP method to be used with the signed URL.
*/
public static SignUrlOption httpMethod(HttpMethod httpMethod) {
return new SignUrlOption(Option.HTTP_METHOD, httpMethod.name());
return new SignUrlOption(Option.HTTP_METHOD, httpMethod);
}

/**
Expand All @@ -892,6 +893,16 @@ public static SignUrlOption withContentType() {
public static SignUrlOption withMd5() {
return new SignUrlOption(Option.MD5, true);
}

/**
* Use it if signature should include the blob's canonicalized extended headers.
* When used, users of the signed URL should include the canonicalized extended headers with
* their request.
* @see <a href="https://cloud.google.com/storage/docs/xml-api/reference-headers"></a>
*/
public static SignUrlOption withExtHeaders(Map<String, String> extHeaders) {
return new SignUrlOption(Option.EXT_HEADERS, extHeaders);
}

/**
* Provides a service account signer to sign the URL. If not provided an attempt will be made to
Expand Down
Loading