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

com.vendasta.vax.VAXCredentials Maven / Gradle / Ivy

package com.vendasta.vax;

import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import io.grpc.*;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.bouncycastle.openssl.PEMException;

import java.io.*;
import java.security.KeyPair;
import java.security.Security;
import java.security.interfaces.ECPrivateKey;
import java.util.Date;
import java.util.concurrent.Executor;


public class VAXCredentials extends CallCredentials {
    private VAXCredentialsManager credentialsManager;
    private final Metadata.Key AUTHORIZATION = Metadata.Key.of("Authorization", Metadata.ASCII_STRING_MARSHALLER);

    VAXCredentials(String scope) throws SDKException {
        String serviceAccountPath = System.getenv("VENDASTA_APPLICATION_CREDENTIALS");
        if (serviceAccountPath == null) {
            throw new SDKException("VENDASTA_APPLICATION_CREDENTIALS env variable is not set.");
        }

        InputStream serviceAccount;
        try {
            serviceAccount = new FileInputStream(serviceAccountPath);
        } catch (FileNotFoundException e) {
            throw new SDKException("VENDASTA_APPLICATION_CREDENTIALS env variable file not found");
        }
        this.credentialsManager = new VAXCredentialsManager(scope, serviceAccount);
    }

    VAXCredentials(String scope, InputStream serviceAccount) throws SDKException {
        this.credentialsManager = new VAXCredentialsManager(scope, serviceAccount);
    }

    @Override
    public void applyRequestMetadata(RequestInfo requestInfo, Executor executor, MetadataApplier metadataApplier) {
        executor.execute(() -> {
            try {
                Metadata headers = new Metadata();
                headers.put(AUTHORIZATION, credentialsManager.getAuthorization());
                metadataApplier.apply(headers);
            } catch (Throwable e) {
                metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e));
            }
        });
    }

    @Override
    public void thisUsesUnstableApi() {}

    public String getAuthorizationToken() {
        return credentialsManager.getAuthorization();
    }


    private class VAXCredentialsManager {
        private Gson gson = new Gson();
        private String scope;
        private Credentials creds;
        private ECPrivateKey privateKey;
        private String currentToken;
        private Date currentTokenExpiry;

        VAXCredentialsManager(String scope, InputStream serviceAccount) throws SDKException {

            this.scope = scope;
            this.creds = gson.fromJson(new InputStreamReader(serviceAccount), Credentials.class);
            this.currentToken = null;
            this.currentTokenExpiry = null;

            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            StringReader reader = new StringReader(creds.privateKey);

            Object parsed;
            try {
                parsed = new org.bouncycastle.openssl.PEMParser(reader).readObject();
            } catch (IOException e) {
                throw new SDKException(e.getMessage());
            }

            KeyPair pair;
            try {
                pair = new org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter().getKeyPair((org.bouncycastle.openssl.PEMKeyPair) parsed);
            } catch (PEMException e) {
                throw new SDKException(e.getMessage());
            }
            this.privateKey = (ECPrivateKey) pair.getPrivate();
        }

        private String getAuthorization() throws CredentialsException {
            Date currentTime = new Date();
            if (currentTokenExpiry != null && currentTime.after(currentTokenExpiry)) {
                refreshToken();
            }

            if (currentToken == null) {
                refreshToken();
            }

            if (currentToken == null) {
                throw new CredentialsException("Could not refresh token");
            }

            return currentToken;
        }

        void invalidateAuthorization() {
            currentToken = null;
        }

        void refreshToken() throws CredentialsException {
            String jwtAccess;
            try {
                jwtAccess = buildJWT();
            } catch (Exception e) {
                throw new CredentialsException("Something went wrong with building the credentials", e);
            }

            HttpClient httpClient = HttpClientBuilder.create().build();

            try {
                HttpPost request = new HttpPost(creds.tokenURI);
                StringEntity params = new StringEntity("{\"token\":\"" + jwtAccess + "\"}");
                request.addHeader("content-type", "application/json");
                request.setEntity(params);
                HttpResponse response = httpClient.execute(request);
                String responseAsString = EntityUtils.toString(response.getEntity());
                GetTokenResponse tokenResponse = gson.fromJson(responseAsString, GetTokenResponse.class);
                currentToken = "Bearer " + tokenResponse.token;
                SignedJWT signedJWT = SignedJWT.parse(tokenResponse.token);
                currentTokenExpiry = signedJWT.getJWTClaimsSet().getExpirationTime();
            } catch (Exception e) {
                throw new CredentialsException("An error occurred while fetching the token", e);
            }
        }

        String buildJWT() throws CredentialsException {
            ECDSASigner signer = null;
            try {
                signer = new ECDSASigner(privateKey);
            } catch (JOSEException e) {
                throw new CredentialsException("Could not create ECDSASigner from private key", e);
            }
            JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                    .audience("vendasta.com")
                    .subject(this.creds.email)
                    .expirationTime(new Date(new Date().getTime() + 60 * 1000))
                    .claim("kid", this.creds.privateKeyID)
                    .build();

            SignedJWT signedJWT = new SignedJWT(
                    new JWSHeader(JWSAlgorithm.ES256),
                    claimsSet);

            try {
                signedJWT.sign(signer);
            } catch (JOSEException e) {
                throw new CredentialsException("Could not sign JWT", e);
            }

            return signedJWT.serialize();
        }

        class Credentials {
            @SerializedName("private_key_id")
            private String privateKeyID;
            @SerializedName("private_key")
            private String privateKey;
            @SerializedName("client_email")
            private String email;
            @SerializedName("token_uri")
            private String tokenURI;
        }

        class GetTokenResponse {
            private String token;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy