require 'spec_helper'
require 'fernet'
require 'json'

describe Fernet::Token, 'validation' do
  let(:secret) { 'odN/0Yu+Pwp3oIvvG8OiE5w4LsLrqfWYRb3knQtSyKI=' }
  it 'is invalid with a bad MAC signature' do
    generated = Fernet::Token.generate(secret: secret,
                                       message: 'hello')

    bogus_hmac = "1" * 32
    allow(Fernet::Encryption).to receive(:hmac_digest).and_return(bogus_hmac)

    token = Fernet::Token.new(generated.to_s, secret: secret)

    expect(token.valid?).to eq(false)
    expect(token.errors[:signature]).to include("does not match")
  end

  it 'is invalid if too old' do
    generated = Fernet::Token.generate(secret: secret,
                                       message: 'hello',
                                       now: Time.now - 61)
    token = Fernet::Token.new(generated.to_s, enforce_ttl: true,
                                              ttl: 60,
                                              secret: secret)
    expect(token.valid?).to eq(false)
    expect(token.errors[:issued_timestamp]).to include("is too far in the past: token expired")
  end

  it 'is invalid with a large clock skew' do
    generated = Fernet::Token.generate(secret:  secret,
                                       message: 'hello',
                                       now:     Time.at(Time.now.to_i + 61))
    token = Fernet::Token.new(generated.to_s, secret: secret)

    expect(token.valid?).to eq(false)
    expect(token.errors[:issued_timestamp]).to include("is too far in the future")
  end

  it 'is invalid with bad base64' do
    token = Fernet::Token.new('bad', secret: secret)

    expect(token.valid?).to eq(false)
    expect(token.errors[:token]).to include("invalid base64")
  end

  it 'is invalid with an unknown token version' do
    invalid1 = Fernet::Token.generate(message: 'message', version: 0x00, secret: secret)
    invalid2 = Fernet::Token.generate(message: 'message', version: 0x81, secret: secret)
    valid    = Fernet::Token.generate(message: 'message', secret: secret)

    [invalid1, invalid2].each do |token|
      expect(token.valid?).to eq(false)
      expect(token.errors[:version]).to include("is unknown")
    end
    expect(valid.valid?).to eq(true)
  end

  it 'is invalid with bad base64 encodings' do
    token = Fernet::Token.generate(message: 'message', secret: secret)
    invalid = Fernet::Token.new("\n#{token}", secret: secret)

    ["\n#{token}", "#{token} ", "#{token}+",
      token.to_s.gsub(/(.)$/, "1"),
      token.to_s.gsub(/(.)$/, "+"),
      token.to_s.gsub(/(.)$/, "\\"),
    ].each do |invalid_string|
      invalid = Fernet::Token.new(invalid_string, secret: secret)
      expect(invalid.valid?).to be(false)
    end
  end
end

describe Fernet::Token, 'message' do
  let(:secret) { 'odN/0Yu+Pwp3oIvvG8OiE5w4LsLrqfWYRb3knQtSyKI=' }
  it 'refuses to decrypt if invalid' do
    generated = Fernet::Token.generate(secret:  secret,
                                       message: 'hello',
                                       now:     Time.now + 61)
    token = Fernet::Token.new(generated.to_s, secret: secret)

    !token.valid? or raise "invalid token"

    expect {
      token.message
    }.to raise_error Fernet::Token::InvalidToken,
      /issued_timestamp is too far in the future/
  end

  it 'gives back the original message in plain text' do
    token = Fernet::Token.generate(secret: secret,
                                   message: 'hello')
    token.valid? or raise "invalid token"

    expect(token.message).to eq('hello')
  end

  it 'correctly handles an empty message' do
    token = Fernet::Token.generate(secret: secret,
                                   message: '')
    token.valid? or raise "invalid token"

    expect(token.message).to eq('')
  end
end
