# frozen_string_literal: true

# Copyright 2020 Google LLC
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
#     * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

require "googleauth/id_tokens/errors"
require "googleauth/id_tokens/key_sources"
require "googleauth/id_tokens/verifier"

module Google
  module Auth
    ##
    # ## Verifying Google ID tokens
    #
    # This module verifies ID tokens issued by Google. This can be used to
    # authenticate signed-in users using OpenID Connect. See
    # https://developers.google.com/identity/sign-in/web/backend-auth for more
    # information.
    #
    # ### Basic usage
    #
    # To verify an ID token issued by Google accounts:
    #
    #     payload = Google::Auth::IDTokens.verify_oidc the_token,
    #                                                  aud: "my-app-client-id"
    #
    # If verification succeeds, you will receive the token's payload as a hash.
    # If verification fails, an exception (normally a subclass of
    # {Google::Auth::IDTokens::VerificationError}) will be raised.
    #
    # To verify an ID token issued by the Google identity-aware proxy (IAP):
    #
    #     payload = Google::Auth::IDTokens.verify_iap the_token,
    #                                                 aud: "my-app-client-id"
    #
    # These methods will automatically download and cache the Google public
    # keys necessary to verify these tokens. They will also automatically
    # verify the issuer (`iss`) field for their respective types of ID tokens.
    #
    # ### Advanced usage
    #
    # If you want to provide your own public keys, either by pointing at a
    # custom URI or by providing the key data directly, use the Verifier class
    # and pass in a key source.
    #
    # To point to a custom URI that returns a JWK set:
    #
    #     source = Google::Auth::IDTokens::JwkHttpKeySource.new "https://example.com/jwk"
    #     verifier = Google::Auth::IDTokens::Verifier.new key_source: source
    #     payload = verifier.verify the_token, aud: "my-app-client-id"
    #
    # To provide key data directly:
    #
    #     jwk_data = {
    #       keys: [
    #         {
    #           alg: "ES256",
    #           crv: "P-256",
    #           kid: "LYyP2g",
    #           kty: "EC",
    #           use: "sig",
    #           x: "SlXFFkJ3JxMsXyXNrqzE3ozl_0913PmNbccLLWfeQFU",
    #           y: "GLSahrZfBErmMUcHP0MGaeVnJdBwquhrhQ8eP05NfCI"
    #         }
    #       ]
    #     }
    #     source = Google::Auth::IDTokens::StaticKeySource.from_jwk_set jwk_data
    #     verifier = Google::Auth::IDTokens::Verifier key_source: source
    #     payload = verifier.verify the_token, aud: "my-app-client-id"
    #
    module IDTokens
      ##
      # A list of issuers expected for Google OIDC-issued tokens.
      #
      # @return [Array<String>]
      #
      OIDC_ISSUERS = ["accounts.google.com", "https://accounts.google.com"].freeze

      ##
      # A list of issuers expected for Google IAP-issued tokens.
      #
      # @return [Array<String>]
      #
      IAP_ISSUERS = ["https://cloud.google.com/iap"].freeze

      ##
      # The URL for Google OAuth2 V3 public certs
      #
      # @return [String]
      #
      OAUTH2_V3_CERTS_URL = "https://www.googleapis.com/oauth2/v3/certs"

      ##
      # The URL for Google IAP public keys
      #
      # @return [String]
      #
      IAP_JWK_URL = "https://www.gstatic.com/iap/verify/public_key-jwk"

      class << self
        ##
        # The key source providing public keys that can be used to verify
        # ID tokens issued by Google OIDC.
        #
        # @return [Google::Auth::IDTokens::JwkHttpKeySource]
        #
        def oidc_key_source
          @oidc_key_source ||= JwkHttpKeySource.new OAUTH2_V3_CERTS_URL
        end

        ##
        # The key source providing public keys that can be used to verify
        # ID tokens issued by Google IAP.
        #
        # @return [Google::Auth::IDTokens::JwkHttpKeySource]
        #
        def iap_key_source
          @iap_key_source ||= JwkHttpKeySource.new IAP_JWK_URL
        end

        ##
        # Reset all convenience key sources. Used for testing.
        # @private
        #
        def forget_sources!
          @oidc_key_source = @iap_key_source = nil
          self
        end

        ##
        # A convenience method that verifies a token allegedly issued by Google
        # OIDC.
        #
        # @param token [String] The ID token to verify
        # @param aud [String,Array<String>,nil] The expected audience. At least
        #     one `aud` field in the token must match at least one of the
        #     provided audiences, or the verification will fail with
        #     {Google::Auth::IDToken::AudienceMismatchError}. If `nil` (the
        #     default), no audience checking is performed.
        # @param azp [String,Array<String>,nil] The expected authorized party
        #     (azp). At least one `azp` field in the token must match at least
        #     one of the provided values, or the verification will fail with
        #     {Google::Auth::IDToken::AuthorizedPartyMismatchError}. If `nil`
        #     (the default), no azp checking is performed.
        # @param aud [String,Array<String>,nil] The expected audience. At least
        #     one `iss` field in the token must match at least one of the
        #     provided issuers, or the verification will fail with
        #     {Google::Auth::IDToken::IssuerMismatchError}. If `nil`, no issuer
        #     checking is performed. Default is to check against {OIDC_ISSUERS}.
        #
        # @return [Hash] The decoded token payload.
        # @raise [KeySourceError] if the key source failed to obtain public keys
        # @raise [VerificationError] if the token verification failed.
        #     Additional data may be available in the error subclass and message.
        #
        def verify_oidc token,
                        aud: nil,
                        azp: nil,
                        iss: OIDC_ISSUERS

          verifier = Verifier.new key_source: oidc_key_source,
                                  aud:        aud,
                                  azp:        azp,
                                  iss:        iss
          verifier.verify token
        end

        ##
        # A convenience method that verifies a token allegedly issued by Google
        # IAP.
        #
        # @param token [String] The ID token to verify
        # @param aud [String,Array<String>,nil] The expected audience. At least
        #     one `aud` field in the token must match at least one of the
        #     provided audiences, or the verification will fail with
        #     {Google::Auth::IDToken::AudienceMismatchError}. If `nil` (the
        #     default), no audience checking is performed.
        # @param azp [String,Array<String>,nil] The expected authorized party
        #     (azp). At least one `azp` field in the token must match at least
        #     one of the provided values, or the verification will fail with
        #     {Google::Auth::IDToken::AuthorizedPartyMismatchError}. If `nil`
        #     (the default), no azp checking is performed.
        # @param aud [String,Array<String>,nil] The expected audience. At least
        #     one `iss` field in the token must match at least one of the
        #     provided issuers, or the verification will fail with
        #     {Google::Auth::IDToken::IssuerMismatchError}. If `nil`, no issuer
        #     checking is performed. Default is to check against {IAP_ISSUERS}.
        #
        # @return [Hash] The decoded token payload.
        # @raise [KeySourceError] if the key source failed to obtain public keys
        # @raise [VerificationError] if the token verification failed.
        #     Additional data may be available in the error subclass and message.
        #
        def verify_iap token,
                       aud: nil,
                       azp: nil,
                       iss: IAP_ISSUERS

          verifier = Verifier.new key_source: iap_key_source,
                                  aud:        aud,
                                  azp:        azp,
                                  iss:        iss
          verifier.verify token
        end
      end
    end
  end
end
