package org.activityinfo.server.endpoint.export; import com.google.appengine.api.appidentity.AppIdentityService; import com.google.appengine.api.appidentity.AppIdentityServiceFactory; import com.google.common.io.BaseEncoding; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Calendar; public class GcsAppIdentityServiceUrlSigner { private static final int EXPIRATION_TIME = 5; private static final String BASE_URL = "https://storage.googleapis.com"; private final AppIdentityService identityService = AppIdentityServiceFactory.getAppIdentityService(); public String getSignedUrl(final String httpVerb, final String path) throws Exception { final long expiration = expiration(); final String unsigned = stringToSign(expiration, path, httpVerb); final String signature = sign(unsigned); return new StringBuilder(BASE_URL) .append("/") .append(path) .append("?GoogleAccessId=") .append(clientId()) .append("&Expires=") .append(expiration) .append("&Signature=") .append(URLEncoder.encode(signature, "UTF-8")).toString(); } private static long expiration() { final long unitMil = 1000l; final Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MINUTE, EXPIRATION_TIME); return calendar.getTimeInMillis() / unitMil; } private String stringToSign(final long expiration, String path, String httpVerb) { final String contentType = ""; final String contentMD5 = ""; final String canonicalizedExtensionHeaders = ""; final String canonicalizedResource = "/" + path; return httpVerb + "\n" + contentMD5 + "\n" + contentType + "\n" + expiration + "\n" + canonicalizedExtensionHeaders + canonicalizedResource; } protected String sign(final String stringToSign) throws UnsupportedEncodingException { final AppIdentityService.SigningResult signingResult = identityService .signForApp(stringToSign.getBytes()); return BaseEncoding.base64().encode(signingResult.getSignature()); } protected String clientId() { return identityService.getServiceAccountName(); } }