// Copyright 2020 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.crypto.tink.jwt;

import com.google.errorprone.annotations.Immutable;
import java.time.Instant;
import java.util.List;
import java.util.Set;

/**
 * A decoded and verified <a href="https://tools.ietf.org/html/rfc7519">JSON Web Token</a> (JWT).
 *
 * <p>A new instance of this class is returned as the result of a sucessfully verification of a
 * MACed or signed compact JWT.
 *
 * <p>It gives read-only access all payload claims and a subset of the headers. It does not contain
 * any headers that depend on the key, such as "alg" or "kid". These headers are checked when the
 * signature is verified and should not be read by the user. This ensures that the key can be
 * changed without any changes to the user code.
 */
@Immutable
public final class VerifiedJwt {

  private final RawJwt rawJwt;

  VerifiedJwt(RawJwt rawJwt) {
    this.rawJwt = rawJwt;
  }

  /**
   * Returns the {@code typ} header value. Throws a JwtInvalidException if header is not present.
   */
  public String getTypeHeader() throws JwtInvalidException {
    return this.rawJwt.getTypeHeader();
  }

  /**
   * Returns true iff the {@code typ} header is present.
   */
  public boolean hasTypeHeader() {
    return this.rawJwt.hasTypeHeader();
  }

  /**
   * Returns the {@code iss} claim that identifies the principal that issued the JWT. Throws a
   * JwtInvalidException if no such claim is present.
   */
  public String getIssuer() throws JwtInvalidException {
    return this.rawJwt.getIssuer();
  }

  /**
   * Returns true iff the {@code iss} claim is present.
   */
  public boolean hasIssuer() {
    return this.rawJwt.hasIssuer();
  }

  /**
   * Returns the {@code sub} claim identifying the principal that is the subject of the JWT. Throws
   * a JwtInvalidException if no such claim is present.
   */
  public String getSubject() throws JwtInvalidException {
    return this.rawJwt.getSubject();
  }

  /**
   * Returns true iff the {@code sub} claim is present.
   */
  public boolean hasSubject() {
    return this.rawJwt.hasSubject();
  }

  /**
   * Returns the {@code aud} claim identifying the principals that are the audience of the JWT.
   * Throws a JwtInvalidException if no such claim is present.
   */
  public List<String> getAudiences() throws JwtInvalidException {
    return this.rawJwt.getAudiences();
  }

  /**
   * Returns true iff the {@code aud} claim is present.
   */
  public boolean hasAudiences() {
    return this.rawJwt.hasAudiences();
  }

  /**
   * Returns the {@code jti} claim that provides a unique identifier for the JWT. Throws a
   * JwtInvalidException if no such claim is present.
   */
  public String getJwtId() throws JwtInvalidException {
    return this.rawJwt.getJwtId();
  }

  /**
   * Returns true iff the {@code jti} claim is present.
   */
  public boolean hasJwtId() {
    return this.rawJwt.hasJwtId();
  }

  /**
   * Returns the expiration time claim {@code exp} that identifies the instant on or after which the
   * token MUST NOT be accepted for processing. Throws a JwtInvalidException if no such claim is
   * present.
   *
   * <p>This API requires {@link java.time.Instant} which is unavailable on Android until API level
   * 26. To use it on older Android devices, enable API desugaring as shown in
   * https://developer.android.com/studio/write/java8-support#library-desugaring.
   */
  public Instant getExpiration() throws JwtInvalidException  {
    return this.rawJwt.getExpiration();
  }

  /**
   * Returns true iff the {@code exp} claim is present.
   */
  public boolean hasExpiration() {
    return this.rawJwt.hasExpiration();
  }

  /**
   * Returns the not before claim {@code nbf} that identifies the instant before which the token
   * MUST NOT be accepted for processing. Throws a JwtInvalidException if no such claim is
   * present.
   *
   * <p>This API requires {@link java.time.Instant} which is unavailable on Android until API level
   * 26. To use it on older Android devices, enable API desugaring as shown in
   * https://developer.android.com/studio/write/java8-support#library-desugaring.
   */
  public Instant getNotBefore() throws JwtInvalidException {
    return this.rawJwt.getNotBefore();
  }

  /**
   * Returns true iff the {@code nbf} claim is present.
   */
  public boolean hasNotBefore() {
    return this.rawJwt.hasNotBefore();
  }

  /**
   * Returns the issued at time claim {@code iat} that identifies the instant at which the JWT was
   * issued. Throws a JwtInvalidException if no such claim is present.
   *
   * <p>This API requires {@link java.time.Instant} which is unavailable on Android until API level
   * 26. To use it on older Android devices, enable API desugaring as shown in
   * https://developer.android.com/studio/write/java8-support#library-desugaring.
   */
  public Instant getIssuedAt() throws JwtInvalidException {
    return this.rawJwt.getIssuedAt();
  }

  /**
   * Returns true iff the {@code iat} claim is present.
   */
  public boolean hasIssuedAt() {
    return this.rawJwt.hasIssuedAt();
  }

  /**
   * Returns the non-registered claim of name {@code name} and type Boolean. Throws a
   * JwtInvalidException if no such claim is present or the claim has another type.
   */
  public Boolean getBooleanClaim(String name) throws JwtInvalidException {
    return this.rawJwt.getBooleanClaim(name);
  }

  /**
   * Returns the non-registered claim of name {@code name} and type Number. Throws a
   * JwtInvalidException if no such claim is present or the claim has another type.
   */
  public Double getNumberClaim(String name) throws JwtInvalidException {
    return this.rawJwt.getNumberClaim(name);
  }

  /**
   * Returns the non-registered claim of name {@code name} and type String. Throws a
   * JwtInvalidException if no such claim is present or the claim has another type.
   */
  public String getStringClaim(String name) throws JwtInvalidException {
    return this.rawJwt.getStringClaim(name);
  }

  /** Returns true iff there is a non-registered claim of name {@code name} and type NULL. */
  public boolean isNullClaim(String name) {
    return this.rawJwt.isNullClaim(name);
  }

  /**
   * Returns the non-registered claim of name {@code name} and type JSON Object encoded in a string.
   * Throws a JwtInvalidException if no such claim is present or the claim has another type.
   */
  public String getJsonObjectClaim(String name) throws JwtInvalidException {
    return this.rawJwt.getJsonObjectClaim(name);
  }

  /**
   * Returns the non-registered claim of name {@code name} and type JSON Array encoded in a string.
   * Throws a JwtInvalidException if no such claim is present or the claim has another type.
   */
  public String getJsonArrayClaim(String name) throws JwtInvalidException {
    return this.rawJwt.getJsonArrayClaim(name);
  }

  /**
   * Returns true iff a non-registered claim of name {@code name} and type boolean is present.
   */
  public boolean hasBooleanClaim(String name) {
    return this.rawJwt.hasBooleanClaim(name);
  }

  /**
   * Returns true iff a non-registered claim of name {@code name} and type number is present.
   */
  public boolean hasNumberClaim(String name) {
    return this.rawJwt.hasNumberClaim(name);
  }

  /**
   * Returns true iff a non-registered claim of name {@code name} and type string is present.
   */
  public boolean hasStringClaim(String name) {
    return this.rawJwt.hasStringClaim(name);
  }

  /**
   * Returns true iff a non-registered claim of name {@code name} and type JsonObject is present.
   */
  public boolean hasJsonObjectClaim(String name) {
    return this.rawJwt.hasJsonObjectClaim(name);
  }

  /**
   * Returns true iff a non-registered claim of name {@code name} and type JsonArray is present.
   */
  public boolean hasJsonArrayClaim(String name) {
    return this.rawJwt.hasJsonArrayClaim(name);
  }

  /**
   * Returns all non-registered claim names.
   */
  public Set<String> customClaimNames() {
    return this.rawJwt.customClaimNames();
  }

  /**
   * Returns a brief description of a VerifiedJwt object. The exact details of the representation
   * are unspecified and subject to change.
   */
  @Override
  public String toString() {
    return "verified{" + this.rawJwt + "}";
  }
}
