<commit_before>require 'optparse'
require 'fileutils'
module SymmetricEncryption
  class CLI
    attr_reader :key_path, :app_name, :encrypt, :config_file_path,
                :decrypt, :random_password, :new_keys, :generate, :environment,
                :keystore, :re_encrypt, :version, :output_file_name, :compress,
                :environments, :cipher_name, :rolling_deploy, :rotate_keys, :prompt, :show_version,
                :cleanup_keys, :activate_key, :migrate

    KEYSTORES = [:heroku, :environment, :file]

    def self.run!(argv)
      new(argv).run!
    end

    def initialize(argv)
      @version          = current_version
      @environment      = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
      @config_file_path = File.expand_path(ENV['SYMMETRIC_ENCRYPTION_CONFIG'] || 'config/symmetric-encryption.yml')
      @app_name         = 'symmetric-encryption'
      @key_path         = '/etc/symmetric-encryption'
      @cipher_name      = 'aes-256-cbc'
      @rolling_deploy   = false
      @prompt           = false
      @show_version     = false
      @keystore         = :file

      if argv.empty?
        puts parser
        exit -10
      end
      parser.parse!(argv)
    end

    def run!
      raise(ArgumentError, 'Cannot cleanup keys and rotate keys at the same time') if cleanup_keys && rotate_keys

      if show_version
        puts "Symmetric Encryption v#{VERSION}"
        puts "OpenSSL v#{OpenSSL::VERSION}"
        puts "Environment: #{environment}"
      elsif encrypt
        load_config
        prompt ? encrypt_string : encrypt_file(encrypt)
      elsif decrypt
        load_config
        prompt ? decrypt_string : decrypt_file(decrypt)
      elsif random_password
        load_config
        gen_random_password(random_password)
      elsif migrate
        run_migrate
      elsif re_encrypt
        load_config
        SymmetricEncryption::Utils::ReEncryptFiles.new(version: version).process_directory(re_encrypt)
      elsif activate_key
        run_activate_key
      elsif rotate_keys
        run_rotate_keys
      elsif cleanup_keys
        run_cleanup_keys
      elsif generate
        generate_new_config
      else
        puts parser
      end
    end

    def parser
      @parser ||= OptionParser.new do |opts|
        opts.banner = <<BANNER
Symmetric Encryption v#{VERSION}

  For more information, see: https://rocketjob.github.io/symmetric-encryption/

  Note: 
    It is recommended to backup the current configuration file, or place it in version control before running
    the configuration manipulation commands below.

symmetric-encryption [options]
BANNER

        opts.on '-e', '--encrypt [FILE_NAME]', 'Encrypt a file, or read from stdin if no file name is supplied.' do |file_name|
          @encrypt = file_name || STDIN
        end

        opts.on '-d', '--decrypt [FILE_NAME]', 'Decrypt a file, or read from stdin if no file name is supplied.' do |file_name|
          @decrypt = file_name || STDIN
        end

        opts.on '-o', '--output FILE_NAME', 'Write encrypted or decrypted file to this file, otherwise output goes to stdout.' do |file_name|
          @output_file_name = file_name
        end

        opts.on '-P', '--prompt', 'When encrypting or decrypting, prompt for a string encrypt or decrypt.' do
          @prompt = true
        end

        opts.on '-z', '--compress', 'Compress encrypted output file.' do
          @compress = true
        end

        opts.on '-E', '--env ENVIRONMENT', "Environment to use in the config file. Default: RACK_ENV || RAILS_ENV || 'development'" do |environment|
          @environment = environment
        end

        opts.on '-c', '--config CONFIG_FILE_PATH', 'File name & path to the Symmetric Encryption configuration file. Default: config/symmetric-encryption.yml or Env var: `SYMMETRIC_ENCRYPTION_CONFIG`' do |path|
          @config_file_path = path
        end

        opts.on '-m', '--migrate', 'Migrate configuration file to new format.' do
          @migrate = true
        end

        opts.on '-r', '--re-encrypt [PATTERN]', 'ReEncrypt all files matching the pattern. Default:  "**/*.{yml,rb}"' do |pattern|
          @re_encrypt = pattern || "**/*.{yml,rb}"
        end

        opts.on '-n', '--new-password [SIZE]', 'Generate a new random password using only characters that are URL-safe base64. Default size is 22.' do |size|
          @random_password = (size || 22).to_i
        end

        opts.on '-g', '--generate', 'Generate a new configuration file and encryption keys for every environment.' do |config|
          @generate = config
        end

        opts.on '-s', '--keystore heroku|environment|file', 'Generate a new configuration file and encryption keys for every environment.' do |keystore|
          @keystore = (keystore || 'file').downcase.to_sym
        end

        opts.on '-K', '--key-path KEY_PATH', 'Output path in which to write generated key files. Default: /etc/symmetric-encryption' do |path|
          @key_path = path
        end

        opts.on '-a', '--app-name NAME', 'Application name to use when generating a new configuration. Default: symmetric-encryption' do |name|
          @app_name = name
        end

        opts.on '-S', '--environments ENVIRONMENTS', "Comma separated list of environments for which to generate the config file. Default: development,test,release,production" do |environments|
          @environments = environments.split(',').collect(&:strip).collect(&:to_sym)
        end

        opts.on '-C', '--cipher-name NAME', "Name of the cipher to use when generating a new config file, or when rotating keys. Default: aes-256-cbc" do |name|
          @cipher_name = name
        end

        opts.on '-R', '--rotate-keys', 'Generates a new encryption key version, encryption key files, and updates the configuration file.' do
          @rotate_keys = true
        end

        opts.on '-D', '--rolling-deploy', 'During key rotation, support a rolling deploy by placing the new key second in the list so that it is not activated yet.' do
          @rolling_deploy = true
        end

        opts.on '-A', '--activate-key', 'Activates the key by moving the key with the highest version to the top.' do
          @activate_key = true
        end

        opts.on '-X', '--cleanup-keys', 'Removes all encryption keys, except the one with the highest version from the configuration file.' do
          @cleanup_keys = true
        end

        opts.on '-V', '--key-version NUMBER', "Encryption key version to use when encrypting or re-encrypting. Default: (Current global version)." do |number|
          @version = number.to_i
        end

        opts.on '-L', '--ciphers', 'List available OpenSSL ciphers.' do
          puts "OpenSSL v#{OpenSSL::VERSION}. Available Ciphers:"
          puts OpenSSL::Cipher.ciphers.join("\n")
          exit
        end

        opts.on '-v', '--version', 'Display Symmetric Encryption version.' do
          @show_version = true
        end

        opts.on('-h', '--help', 'Prints this help.') do
          puts opts
          exit
        end

      end
    end

    private

    def load_config
      Config.load!(file_name: config_file_path, env: environment)
    end

    def generate_new_config
      config_file_does_not_exist!
      environments ||= %i(development test release production)
      cfg          =
        if keystore == :file
          SymmetricEncryption::Keystore::File.new_config(<commit_msg>Fixes #81 use instance environments<commit_after>require 'optparse'
require 'fileutils'
module SymmetricEncryption
  class CLI
    attr_reader :key_path, :app_name, :encrypt, :config_file_path,
                :decrypt, :random_password, :new_keys, :generate, :environment,
                :keystore, :re_encrypt, :version, :output_file_name, :compress,
                :environments, :cipher_name, :rolling_deploy, :rotate_keys, :prompt, :show_version,
                :cleanup_keys, :activate_key, :migrate

    KEYSTORES = [:heroku, :environment, :file]

    def self.run!(argv)
      new(argv).run!
    end

    def initialize(argv)
      @version          = current_version
      @environment      = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
      @config_file_path = File.expand_path(ENV['SYMMETRIC_ENCRYPTION_CONFIG'] || 'config/symmetric-encryption.yml')
      @app_name         = 'symmetric-encryption'
      @key_path         = '/etc/symmetric-encryption'
      @cipher_name      = 'aes-256-cbc'
      @rolling_deploy   = false
      @prompt           = false
      @show_version     = false
      @keystore         = :file

      if argv.empty?
        puts parser
        exit -10
      end
      parser.parse!(argv)
    end

    def run!
      raise(ArgumentError, 'Cannot cleanup keys and rotate keys at the same time') if cleanup_keys && rotate_keys

      if show_version
        puts "Symmetric Encryption v#{VERSION}"
        puts "OpenSSL v#{OpenSSL::VERSION}"
        puts "Environment: #{environment}"
      elsif encrypt
        load_config
        prompt ? encrypt_string : encrypt_file(encrypt)
      elsif decrypt
        load_config
        prompt ? decrypt_string : decrypt_file(decrypt)
      elsif random_password
        load_config
        gen_random_password(random_password)
      elsif migrate
        run_migrate
      elsif re_encrypt
        load_config
        SymmetricEncryption::Utils::ReEncryptFiles.new(version: version).process_directory(re_encrypt)
      elsif activate_key
        run_activate_key
      elsif rotate_keys
        run_rotate_keys
      elsif cleanup_keys
        run_cleanup_keys
      elsif generate
        generate_new_config
      else
        puts parser
      end
    end

    def parser
      @parser ||= OptionParser.new do |opts|
        opts.banner = <<BANNER
Symmetric Encryption v#{VERSION}

  For more information, see: https://rocketjob.github.io/symmetric-encryption/

  Note: 
    It is recommended to backup the current configuration file, or place it in version control before running
    the configuration manipulation commands below.

symmetric-encryption [options]
BANNER

        opts.on '-e', '--encrypt [FILE_NAME]', 'Encrypt a file, or read from stdin if no file name is supplied.' do |file_name|
          @encrypt = file_name || STDIN
        end

        opts.on '-d', '--decrypt [FILE_NAME]', 'Decrypt a file, or read from stdin if no file name is supplied.' do |file_name|
          @decrypt = file_name || STDIN
        end

        opts.on '-o', '--output FILE_NAME', 'Write encrypted or decrypted file to this file, otherwise output goes to stdout.' do |file_name|
          @output_file_name = file_name
        end

        opts.on '-P', '--prompt', 'When encrypting or decrypting, prompt for a string encrypt or decrypt.' do
          @prompt = true
        end

        opts.on '-z', '--compress', 'Compress encrypted output file.' do
          @compress = true
        end

        opts.on '-E', '--env ENVIRONMENT', "Environment to use in the config file. Default: RACK_ENV || RAILS_ENV || 'development'" do |environment|
          @environment = environment
        end

        opts.on '-c', '--config CONFIG_FILE_PATH', 'File name & path to the Symmetric Encryption configuration file. Default: config/symmetric-encryption.yml or Env var: `SYMMETRIC_ENCRYPTION_CONFIG`' do |path|
          @config_file_path = path
        end

        opts.on '-m', '--migrate', 'Migrate configuration file to new format.' do
          @migrate = true
        end

        opts.on '-r', '--re-encrypt [PATTERN]', 'ReEncrypt all files matching the pattern. Default:  "**/*.{yml,rb}"' do |pattern|
          @re_encrypt = pattern || "**/*.{yml,rb}"
        end

        opts.on '-n', '--new-password [SIZE]', 'Generate a new random password using only characters that are URL-safe base64. Default size is 22.' do |size|
          @random_password = (size || 22).to_i
        end

        opts.on '-g', '--generate', 'Generate a new configuration file and encryption keys for every environment.' do |config|
          @generate = config
        end

        opts.on '-s', '--keystore heroku|environment|file', 'Generate a new configuration file and encryption keys for every environment.' do |keystore|
          @keystore = (keystore || 'file').downcase.to_sym
        end

        opts.on '-K', '--key-path KEY_PATH', 'Output path in which to write generated key files. Default: /etc/symmetric-encryption' do |path|
          @key_path = path
        end

        opts.on '-a', '--app-name NAME', 'Application name to use when generating a new configuration. Default: symmetric-encryption' do |name|
          @app_name = name
        end

        opts.on '-S', '--environments ENVIRONMENTS', "Comma separated list of environments for which to generate the config file. Default: development,test,release,production" do |environments|
          @environments = environments.split(',').collect(&:strip).collect(&:to_sym)
        end

        opts.on '-C', '--cipher-name NAME', "Name of the cipher to use when generating a new config file, or when rotating keys. Default: aes-256-cbc" do |name|
          @cipher_name = name
        end

        opts.on '-R', '--rotate-keys', 'Generates a new encryption key version, encryption key files, and updates the configuration file.' do
          @rotate_keys = true
        end

        opts.on '-D', '--rolling-deploy', 'During key rotation, support a rolling deploy by placing the new key second in the list so that it is not activated yet.' do
          @rolling_deploy = true
        end

        opts.on '-A', '--activate-key', 'Activates the key by moving the key with the highest version to the top.' do
          @activate_key = true
        end

        opts.on '-X', '--cleanup-keys', 'Removes all encryption keys, except the one with the highest version from the configuration file.' do
          @cleanup_keys = true
        end

        opts.on '-V', '--key-version NUMBER', "Encryption key version to use when encrypting or re-encrypting. Default: (Current global version)." do |number|
          @version = number.to_i
        end

        opts.on '-L', '--ciphers', 'List available OpenSSL ciphers.' do
          puts "OpenSSL v#{OpenSSL::VERSION}. Available Ciphers:"
          puts OpenSSL::Cipher.ciphers.join("\n")
          exit
        end

        opts.on '-v', '--version', 'Display Symmetric Encryption version.' do
          @show_version = true
        end

        opts.on('-h', '--help', 'Prints this help.') do
          puts opts
          exit
        end

      end
    end

    private

    def load_config
      Config.load!(file_name: config_file_path, env: environment)
    end

    def generate_new_config
      config_file_does_not_exist!
      self.environments ||= %i(development test release production)
      cfg          =
        if keystore == :file
          SymmetricEncryption::Keystore::File.new_config(
{'fraction_non_alphanumeric': 0.1022795641419881, 'fraction_numerical': 0.002005481649842904, 'mean_word_length': 2.3907524932003628, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6622771', 'n_tokens_mistral': 4413, 'n_tokens_neox': 4213, 'n_words': 1385}