All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vendasta.common.v1.GoogleCredentialsManager Maven / Gradle / Ivy

The newest version!
package com.vendasta.common.v1;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.Date;
import org.apache.commons.codec.binary.Base64;

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.UrlEncodedContent;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.json.JsonParser;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.json.webtoken.JsonWebSignature;
import com.google.api.client.json.webtoken.JsonWebToken;
import com.google.api.client.json.webtoken.JsonWebToken.Payload;
import com.google.api.client.util.Charsets;
import com.google.api.client.util.GenericData;
import com.google.auth.oauth2.ServiceAccountJwtAccessCredentials;
import com.google.common.io.CharStreams;

public class GoogleCredentialsManager implements CredentialsManager {
	private String serviceAccount;
	private String audience;
	private String scope;
	private String currentToken;
	private Date currentTokenExpiry;

	public GoogleCredentialsManager(InputStream serviceAccount, String audience, String scope) throws IOException {
		this.serviceAccount = CharStreams.toString(new InputStreamReader(serviceAccount, Charsets.UTF_8));
		this.audience = audience;
		this.scope = scope;
		this.currentTokenExpiry = null;
		this.currentToken = null;
	}

	/**
	 * Talks to the Google IAM service to obtain a JWT using a Service Account.
	 * The result is cached until invalidated or expired.
	 *
	 * @throws CredentialsException
	 *             when an error occurs when attempting to get an authorization
	 *             token.
	 * @return an id token
	 */
	public String getAuthorization() throws CredentialsException {
		Date currentTime = new Date();
		if (currentTokenExpiry != null && currentTime.after(currentTokenExpiry)) {
			refreshToken();
		}
		if (currentToken == null) {
			refreshToken();
		}
		return currentToken;
	}

	private void refreshToken() throws CredentialsException {
		URI audienceUri;
		String jwtAccess;
		try {
			jwtAccess = this.getJWTAccessFromServiceAccount();
			audienceUri = new URI(this.audience);
		} catch (URISyntaxException e) {
			throw new CredentialsException("Malformed URI prevented acquisition of a JWT", e);
		} catch (IOException e) {
			throw new CredentialsException("Something went wrong with reading credentials back", e);
		}

		GenericData tokenRequest = new GenericData();
		tokenRequest.set("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
		tokenRequest.set("assertion", jwtAccess);
		UrlEncodedContent content = new UrlEncodedContent(tokenRequest);

		HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory();
		HttpRequest request;
		try {
			request = requestFactory.buildPostRequest(new GenericUrl(audienceUri), content);
		} catch (IOException e) {
			throw new CredentialsException("Error building credentials request", e);
		}
		JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
		request.setParser(new JsonObjectParser(jsonFactory));

		HttpResponse response;
		try {
			response = request.execute();
		} catch (IOException e) {
			throw new CredentialsException("Error getting access token for service account", e);
		}

		try {
			
			// Extract the JWT token from the response
			GenericData responseData = response.parseAs(GenericData.class);
			String jwtToken = (String) responseData.get("id_token");

			// Decode the JWT to get the expiry
			String[] tokens = jwtToken.split("\\.");
			String claims = new String(this.Base64Decode(tokens[1]));
			Payload payload = new JsonWebToken.Payload();
			JsonParser parser = JacksonFactory.getDefaultInstance().createJsonParser(claims);
			parser.parse(payload);
			System.out.println(payload);
			// Save the expiry and the token
			currentToken = "Bearer " + jwtToken;
			currentTokenExpiry = new Date(payload.getExpirationTimeSeconds() * 1000);

		} catch (IOException e) {
			throw new CredentialsException("Error parsing JSON response from google Credentials Server", e);
		}
	}

	/**
	 * This is a helper function that uses the Service Account, audience, and
	 * scope to build a JWT used to request a token from Google's IAM service.
	 * 
	 * @throws IOException
	 *             Really, this should never happen because converting a string
	 *             to an InputStream and reading it shouldn't fail :/
	 * @throws URISyntaxException
	 *             Only happens if an invalid Audience URI is used.
	 */
	private String getJWTAccessFromServiceAccount() throws URISyntaxException, IOException {
		InputStream is = new ByteArrayInputStream(serviceAccount.getBytes(Charsets.UTF_8));
		ServiceAccountJwtAccessCredentials creds = ServiceAccountJwtAccessCredentials.fromStream(is);

		URI audienceUri = new URI(this.audience);

		JsonWebSignature.Header header = new JsonWebSignature.Header();
		header.setAlgorithm("RS256");
		header.setType("JWT");
		header.setKeyId(creds.getPrivateKeyId());

		JsonWebToken.Payload payload = new JsonWebToken.Payload();
		long currentTime = System.currentTimeMillis();
		// Both copies of the email are required
		payload.setIssuer(creds.getClientEmail());
		payload.setSubject(creds.getClientEmail());
		payload.setAudience(audienceUri.toString());
		payload.setIssuedAtTimeSeconds(currentTime / 1000);
		payload.setExpirationTimeSeconds(currentTime / 1000 + 3600);
		payload.put("scope", this.scope);

		JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();

		String jwtAccess;
		try {
			jwtAccess = JsonWebSignature.signUsingRsaSha256(creds.getPrivateKey(), jsonFactory, header, payload);
		} catch (GeneralSecurityException e) {
			throw new IOException("Error signing service account JWT access header with private key:", e);
		}

		return jwtAccess;
	}

	/**
	 * Used by clients to invalidate the current token for any reason other than
	 * expiry, which is handled by this CredentialManager.
	 */
	public void invalidateAuthorization() {
		currentToken = null;
	}
	
	private String Base64Decode(String encoded) throws UnsupportedEncodingException {
		byte[] decoded = Base64.decodeBase64(encoded.getBytes("UTF-8"));
		return new String(decoded, "UTF-8");
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy