// Copyright 2022 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 jwt

import (
	"encoding/base64"
	"fmt"
	"testing"
	"time"

	"github.com/tink-crypto/tink-go/v2/mac/subtle"
)

func TestNewMACwithNilMACFails(t *testing.T) {
	if _, err := newMACWithKID(nil, "", nil); err == nil {
		t.Errorf("NewMACWithKID(nil, '', nil) err = nil, want error")
	}
}

func createMACwithKID(customKID *string) (*macWithKID, error) {
	// https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.1.1
	key, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow")
	if err != nil {
		return nil, fmt.Errorf("failed parsing test key: %v", err)
	}
	mac, err := subtle.NewHMAC("SHA256", key, 32)
	if err != nil {
		return nil, err
	}
	return newMACWithKID(mac, "HS256", customKID)
}

func TestCreateAndValidateToken(t *testing.T) {
	m, err := createMACwithKID(nil)
	if err != nil {
		t.Fatalf("creating JWTMACwithKID primitive: %v", err)
	}
	rawOpts := &RawJWTOptions{
		TypeHeader:        refString("typeHeader"),
		JWTID:             refString("123"),
		WithoutExpiration: true,
	}
	rawJWT, err := NewRawJWT(rawOpts)
	if err != nil {
		t.Errorf("NewRawJWT() err = %v, want nil", err)
	}
	compact, err := m.ComputeMACAndEncodeWithKID(rawJWT, nil)
	if err != nil {
		t.Errorf("m.ComputeMACAndEncodeWithKID err = %v, want nil", err)
	}
	validatorOps := &ValidatorOpts{
		ExpectedTypeHeader:     refString("typeHeader"),
		AllowMissingExpiration: true,
	}
	validator, err := NewValidator(validatorOps)
	if err != nil {
		t.Errorf("NewValidator err = %v, want nil", err)
	}
	verifiedJWT, err := m.VerifyMACAndDecodeWithKID(compact, validator, nil)
	if err != nil {
		t.Errorf("m.VerifyMACAndDecodeWithKID() err = %v, want nil", err)
	}
	typeHeader, err := verifiedJWT.TypeHeader()
	if err != nil {
		t.Errorf("verifiedJWT.TypeHeader() err = %v, want nil", err)
	}
	if typeHeader != "typeHeader" {
		t.Errorf("verifiedJWT.TypeHeader() = %q, want 'typeHeader'", typeHeader)
	}
	jwtID, err := verifiedJWT.JWTID()
	if err != nil {
		t.Errorf("verifiedJWT.JWTID() err = %v, want nil", err)
	}
	if jwtID != "123" {
		t.Errorf("verifiedJWT.JWTID() = %q, want '123'", jwtID)
	}

	validatorOps = &ValidatorOpts{
		ExpectedTypeHeader:     refString("notTypeHeader"),
		AllowMissingExpiration: true,
	}
	validator, err = NewValidator(validatorOps)
	if err != nil {
		t.Errorf("NewValidator err = %v, want nil", err)
	}
	if _, err := m.VerifyMACAndDecodeWithKID(compact, validator, nil); err == nil {
		t.Errorf("m.VerifyMACAndDecodeWithKID() err = nil, want error")
	}
}

func TestCreateAndValidateTokenWithKID(t *testing.T) {
	m, err := createMACwithKID(nil)
	if err != nil {
		t.Fatalf("creating JWTMACwithKID primitive: %v", err)
	}
	rawOpts := &RawJWTOptions{
		TypeHeader:        refString("typeHeader"),
		JWTID:             refString("123"),
		WithoutExpiration: true,
	}
	rawJWT, err := NewRawJWT(rawOpts)
	if err != nil {
		t.Errorf("NewRawJWT() err = %v, want nil", err)
	}
	compact, err := m.ComputeMACAndEncodeWithKID(rawJWT, refString("kid-123"))
	if err != nil {
		t.Errorf("m.ComputeMACAndEncodeWithKID err = %v, want nil", err)
	}
	opts := &ValidatorOpts{
		ExpectedTypeHeader:     refString("typeHeader"),
		AllowMissingExpiration: true,
	}
	validator, err := NewValidator(opts)
	if err != nil {
		t.Fatalf("creating JWT validator, NewValidator: %v", err)
	}
	verifiedJWT, err := m.VerifyMACAndDecodeWithKID(compact, validator, refString("kid-123"))
	if err != nil {
		t.Errorf("m.VerifyMACAndDecodeWithKID(kid = kid-123) err = %v, want nil", err)
	}

	typeHeader, err := verifiedJWT.TypeHeader()
	if err != nil {
		t.Errorf("verifiedJWT.TypeHeader() err = %v, want nil", err)
	}
	if typeHeader != *rawOpts.TypeHeader {
		t.Errorf("verifiedJWT.TypeHeader() = %q, want %q", typeHeader, *rawOpts.TypeHeader)
	}
	jwtID, err := verifiedJWT.JWTID()
	if err != nil {
		t.Errorf("verifiedJWT.JWTID() err = %v, want nil", err)
	}
	if jwtID != *rawOpts.JWTID {
		t.Errorf("verifiedJWT.JWTID() = %q, want %q", jwtID, *rawOpts.JWTID)
	}

	if _, err := m.VerifyMACAndDecodeWithKID(compact, validator, nil); err != nil {
		t.Errorf("m.VerifyMACAndDecodeWithKID(kid = nil) err = %v, want nil", err)
	}
	if _, err := m.VerifyMACAndDecodeWithKID(compact, validator, refString("other-kid")); err == nil {
		t.Errorf("m.VerifyMACAndDecodeWithKID(kid = 'other-kid') err = nil, want error")
	}
}

func TestValidateFixedToken(t *testing.T) {
	// Key and Token are examples from: https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.1.1
	compact := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
	m, err := createMACwithKID(nil)
	if err != nil {
		t.Fatalf("creating JWTMACwithKID primitive: %v", err)
	}
	opts := &ValidatorOpts{
		ExpectedTypeHeader: refString("JWT"),
		ExpectedIssuer:     refString("joe"),
		FixedNow:           time.Unix(12345, 0),
	}
	pastValidator, err := NewValidator(opts)
	if err != nil {
		t.Fatalf("creating JWTValidator: %v", err)
	}
	// verification succeeds because token was valid valid on January 1, 1970 UTC.
	verifiedJWT, err := m.VerifyMACAndDecodeWithKID(compact, pastValidator, nil)
	if err != nil {
		t.Fatalf("m.VerifyMACAndDecodeWithKID() err = %v, want nil", err)
	}
	typeHeader, err := verifiedJWT.TypeHeader()
	if err != nil {
		t.Errorf("verifiedJWT.TypeHeader() err = %v, want nil", err)
	}
	if typeHeader != *opts.ExpectedTypeHeader {
		t.Errorf("verifiedJWT.TypeHeader() = %q, want %q", typeHeader, *opts.ExpectedTypeHeader)
	}
	issuer, err := verifiedJWT.Issuer()
	if err != nil {
		t.Errorf("verifiedJWT.Issuer() err = %v, want nil", err)
	}
	if issuer != *opts.ExpectedIssuer {
		t.Errorf("verifiedJWT.Issuer() = %q, want %q", issuer, *opts.ExpectedIssuer)
	}
	boolClaim, err := verifiedJWT.BooleanClaim("http://example.com/is_root")
	if err != nil {
		t.Errorf("verifiedJWT.BooleanClaim('http://example.com/is_root') err = %v, want nil", err)
	}
	if boolClaim != true {
		t.Errorf("verifiedJWT.BooleanClaim('http://example.com/is_root') = %q, want true", issuer)
	}

	// expired token fails verification
	opts.FixedNow = time.Now()
	presentValidator, err := NewValidator(opts)
	if err != nil {
		t.Fatalf("creating JWTValidator: %v", err)
	}
	_, err = m.VerifyMACAndDecodeWithKID(compact, presentValidator, nil)
	if err == nil {
		t.Fatalf("m.VerifyMACAndDecodeWithKID() with expired token err = nil, want error")
	}
	if !IsExpirationErr(err) {
		t.Fatalf("IsExpirationErr(err) for err = %q is false, want true", err)
	}

	// tampered token verification fails
	tamperedCompact := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXx"
	if _, err := m.VerifyMACAndDecodeWithKID(tamperedCompact, pastValidator, nil); err == nil {
		t.Fatalf("m.VerifyMACAndDecodeWithKID() with expired tampered token err = nil, want error")
	}
}

func TestInvalidTokens(t *testing.T) {
	m, err := createMACwithKID(nil)
	if err != nil {
		t.Fatalf("creating JWTMACwithKID primitive: %v", err)
	}
	validator, err := NewValidator(&ValidatorOpts{})
	if err != nil {
		t.Fatalf("creating JWTValidator: %v", err)
	}
	for _, compact := range []string{
		"eyJhbGciOiJIUzI1NiJ9.e30.abc.",
		"eyJhbGciOiJIUzI1NiJ9?.e30.abc",
		"eyJhbGciOiJIUzI1NiJ9.e30?.abc",
		"eyJhbGciOiJIUzI1NiJ9.e30.abc?",
		"eyJhbGciOiJIUzI1NiJ9.e30",
		"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOi&Jqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
	} {
		if _, err := m.VerifyMACAndDecodeWithKID(compact, validator, nil); err == nil {
			t.Errorf("m.VerifyMACAndDecodeWithKID(%q) err = nil, want error", compact)
		}
	}
}

func TestCustomKIDAndTinkPrefixKeyFail(t *testing.T) {
	m, err := createMACwithKID(refString("custom-kid"))
	if err != nil {
		t.Fatalf("creating JWTMACwithKID primitive: %v", err)
	}
	rawJWT, err := NewRawJWT(&RawJWTOptions{WithoutExpiration: true})
	if err != nil {
		t.Fatalf("NewRawJWT() err = %v, want nil", err)
	}
	if _, err := m.ComputeMACAndEncodeWithKID(rawJWT, refString("tink-kid")); err == nil {
		t.Errorf("specifying kid when primitive contains kid to ComputeMACAndEncodeWithKID() err = nil, want error")
	}
}
