// Path: src/main/java/com/google/fleetengine/auth/token/FleetEngineToken.java
// @AutoValue
// public abstract class FleetEngineToken {
//   /** Token creation timestamp. */
//   public abstract Date creationTimestamp();
// 
//   /** Token expiration timestamp. */
//   public abstract Date expirationTimestamp();
// 
//   /** Audience (aud claim) of the JWT. */
//   public abstract String audience();
// 
//   /** Signed and base64 encoded JWT. */
//   @Nullable
//   public abstract String jwt();
// 
//   /** Scope of the authorization token. */
//   public abstract FleetEngineTokenType tokenType();
// 
//   /** Custom authorization claims written to JWT. */
//   public abstract FleetEngineTokenClaims authorizationClaims();
// 
//   /** Provides a builder for {@link FleetEngineToken}. */
//   public static Builder builder() {
//     return new AutoValue_FleetEngineToken.Builder()
//         .setAudience(CommonConstants.DEFAULT_JWT_AUDIENCE);
//   }
// 
//   public abstract FleetEngineToken.Builder toBuilder();
// 
//   /** Builder class for FleetEngineToken. */
//   @AutoValue.Builder
//   public abstract static class Builder {
// 
//     /** Setter for token creation timestamp. */
//     public abstract Builder setCreationTimestamp(Date creationTimestamp);
// 
//     /** Setter for token expiration timestamp. */
//     public abstract Builder setExpirationTimestamp(Date expirationTimestamp);
// 
//     /** Setter for a signed JWT to be associated with the token object. */
//     public abstract Builder setJwt(String token);
// 
//     /** Setter for the token type defining the scope of the token. */
//     public abstract Builder setTokenType(FleetEngineTokenType type);
// 
//     /** Setter for token audience (aud claim). */
//     public abstract Builder setAudience(String audience);
// 
//     /** Sets custom authorization claims written to JWT. */
//     public abstract Builder setAuthorizationClaims(FleetEngineTokenClaims claims);
// 
//     /** Builds the token object. */
//     public abstract FleetEngineToken build();
//   }
// }
// 
// Path: src/main/java/com/google/fleetengine/auth/token/factory/signer/SigningTokenException.java
// public class SigningTokenException extends Exception {
//   /**
//    * Constructs a {@code SigningTokenException} with the specified detail message.
//    *
//    * @param message The detail message (which is saved for later retrieval by the {@link
//    *     #getMessage()} method)
//    */
//   public SigningTokenException(String message) {
//     this(message, null);
//   }
// 
//   /**
//    * Constructs a {@code SigningTokenException} with the specified detail message.
//    *
//    * @param message The detail message (which is saved for later retrieval by the {@link
//    *     #getMessage()} method)
//    * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method).
//    *     (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
//    */
//   public SigningTokenException(String message, Throwable cause) {
//     super(message, cause);
//   }
// }


import com.google.common.annotations.VisibleForTesting;
import com.google.fleetengine.auth.token.FleetEngineToken;
import com.google.fleetengine.auth.token.factory.signer.SigningTokenException;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
// Copyright 2021 Google LLC
//
// 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.fleetengine.auth.client;


/**
 * Intercepts an outgoing gRPC request and attaches a valid Fleet Engine JWT to the header.
 *
 * <p>Works with generated gRPC stubby classes:
 *
 * <pre>{@code channel = ManagedChannelBuilder.forTarget(fleetEngineAddress)
 *   .intercept(FleetEngineAuthClientInterceptor#create(fleetEngineTokenProvider))
 *   .build();
 * VehicleServiceBlockingStub stub = VehicleServiceGrpc.newBlockingStub(channel);}</pre>
 */
public class FleetEngineAuthClientInterceptor implements ClientInterceptor {

  /** Authorization header name. */
  private static final Metadata.Key<String> AUTHORIZATION_HEADER =
      Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);

  private final FleetEngineTokenProvider tokenProvider;

  /**
   * Creates a gRPC client interceptor that attaches tokens from {@code tokenProvider} to outgoing
   * gRPC requests.
   *
   * @param tokenProvider provides valid Fleet Engine JWTs for an outgoing gRPC request.
   */
  public static FleetEngineAuthClientInterceptor create(FleetEngineTokenProvider tokenProvider) {
    return new FleetEngineAuthClientInterceptor(tokenProvider);
  }

  /** Constructor. */
  private FleetEngineAuthClientInterceptor(FleetEngineTokenProvider tokenProvider) {
    this.tokenProvider = tokenProvider;
  }

  /**
   * Overrides {@link ClientInterceptor#interceptCall(MethodDescriptor, CallOptions, Channel)} and
   * attaches a Fleet Engine JWT to the header of the outgoing gRPC request.
   */
  @Override
  public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
      MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
    ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
    return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(call) {
      @Override
      public void start(Listener<RespT> responseListener, Metadata headers) {
        addAuthorizationHeader(headers);
        super.start(responseListener, headers);
      }
    };
  }

  /** Adds the signed base64 encode JWT to the header. */
  @VisibleForTesting
  void addAuthorizationHeader(Metadata headers) {
    try {
      FleetEngineToken token = tokenProvider.getSignedToken();
      headers.put(AUTHORIZATION_HEADER, String.format("Bearer %s", token.jwt()));
// Path: src/main/java/com/google/fleetengine/auth/token/FleetEngineToken.java
// @AutoValue
// public abstract class FleetEngineToken {
//   /** Token creation timestamp. */
//   public abstract Date creationTimestamp();
// 
//   /** Token expiration timestamp. */
//   public abstract Date expirationTimestamp();
// 
//   /** Audience (aud claim) of the JWT. */
//   public abstract String audience();
// 
//   /** Signed and base64 encoded JWT. */
//   @Nullable
//   public abstract String jwt();
// 
//   /** Scope of the authorization token. */
//   public abstract FleetEngineTokenType tokenType();
// 
//   /** Custom authorization claims written to JWT. */
//   public abstract FleetEngineTokenClaims authorizationClaims();
// 
//   /** Provides a builder for {@link FleetEngineToken}. */
//   public static Builder builder() {
//     return new AutoValue_FleetEngineToken.Builder()
//         .setAudience(CommonConstants.DEFAULT_JWT_AUDIENCE);
//   }
// 
//   public abstract FleetEngineToken.Builder toBuilder();
// 
//   /** Builder class for FleetEngineToken. */
//   @AutoValue.Builder
//   public abstract static class Builder {
// 
//     /** Setter for token creation timestamp. */
//     public abstract Builder setCreationTimestamp(Date creationTimestamp);
// 
//     /** Setter for token expiration timestamp. */
//     public abstract Builder setExpirationTimestamp(Date expirationTimestamp);
// 
//     /** Setter for a signed JWT to be associated with the token object. */
//     public abstract Builder setJwt(String token);
// 
//     /** Setter for the token type defining the scope of the token. */
//     public abstract Builder setTokenType(FleetEngineTokenType type);
// 
//     /** Setter for token audience (aud claim). */
//     public abstract Builder setAudience(String audience);
// 
//     /** Sets custom authorization claims written to JWT. */
//     public abstract Builder setAuthorizationClaims(FleetEngineTokenClaims claims);
// 
//     /** Builds the token object. */
//     public abstract FleetEngineToken build();
//   }
// }
// 
// Path: src/main/java/com/google/fleetengine/auth/token/factory/signer/SigningTokenException.java
// public class SigningTokenException extends Exception {
//   /**
//    * Constructs a {@code SigningTokenException} with the specified detail message.
//    *
//    * @param message The detail message (which is saved for later retrieval by the {@link
//    *     #getMessage()} method)
//    */
//   public SigningTokenException(String message) {
//     this(message, null);
//   }
// 
//   /**
//    * Constructs a {@code SigningTokenException} with the specified detail message.
//    *
//    * @param message The detail message (which is saved for later retrieval by the {@link
//    *     #getMessage()} method)
//    * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method).
//    *     (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
//    */
//   public SigningTokenException(String message, Throwable cause) {
//     super(message, cause);
//   }
// }


// Path: src/main/java/com/google/fleetengine/auth/client/FleetEngineAuthClientInterceptor.java
import com.google.common.annotations.VisibleForTesting;
import com.google.fleetengine.auth.token.FleetEngineToken;
import com.google.fleetengine.auth.token.factory.signer.SigningTokenException;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;

// Copyright 2021 Google LLC
//
// 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.fleetengine.auth.client;


/**
 * Intercepts an outgoing gRPC request and attaches a valid Fleet Engine JWT to the header.
 *
 * <p>Works with generated gRPC stubby classes:
 *
 * <pre>{@code channel = ManagedChannelBuilder.forTarget(fleetEngineAddress)
 *   .intercept(FleetEngineAuthClientInterceptor#create(fleetEngineTokenProvider))
 *   .build();
 * VehicleServiceBlockingStub stub = VehicleServiceGrpc.newBlockingStub(channel);}</pre>
 */
public class FleetEngineAuthClientInterceptor implements ClientInterceptor {

  /** Authorization header name. */
  private static final Metadata.Key<String> AUTHORIZATION_HEADER =
      Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);

  private final FleetEngineTokenProvider tokenProvider;

  /**
   * Creates a gRPC client interceptor that attaches tokens from {@code tokenProvider} to outgoing
   * gRPC requests.
   *
   * @param tokenProvider provides valid Fleet Engine JWTs for an outgoing gRPC request.
   */
  public static FleetEngineAuthClientInterceptor create(FleetEngineTokenProvider tokenProvider) {
    return new FleetEngineAuthClientInterceptor(tokenProvider);
  }

  /** Constructor. */
  private FleetEngineAuthClientInterceptor(FleetEngineTokenProvider tokenProvider) {
    this.tokenProvider = tokenProvider;
  }

  /**
   * Overrides {@link ClientInterceptor#interceptCall(MethodDescriptor, CallOptions, Channel)} and
   * attaches a Fleet Engine JWT to the header of the outgoing gRPC request.
   */
  @Override
  public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
      MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
    ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
    return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(call) {
      @Override
      public void start(Listener<RespT> responseListener, Metadata headers) {
        addAuthorizationHeader(headers);
        super.start(responseListener, headers);
      }
    };
  }

  /** Adds the signed base64 encode JWT to the header. */
  @VisibleForTesting
  void addAuthorizationHeader(Metadata headers) {
    try {
      FleetEngineToken token = tokenProvider.getSignedToken();
      headers.put(AUTHORIZATION_HEADER, String.format("Bearer %s", token.jwt()));
    } catch (SigningTokenException e) {