diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 35dba88..e514764 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,11 +13,11 @@ on: jobs: test: name: Test Ruby ${{ matrix.RUBY_VERSION }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - RUBY_VERSION: ["2.2", "2.6"] + RUBY_VERSION: ["2.6", "3.3"] steps: - name: Check out code uses: actions/checkout@v4 @@ -28,8 +28,11 @@ jobs: ruby-version: ${{ matrix.RUBY_VERSION }} bundler-cache: true + - name: Run Lint + run: bundle exec rubocop + - name: Run Tests - run: bundle exec rake + run: bundle exec rspec results: if: ${{ always() }} diff --git a/.rspec b/.rspec index b3eb8b4..5255835 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,3 @@ --color ---format documentation \ No newline at end of file +--format documentation +--require spec_helper \ No newline at end of file diff --git a/.rubocop-https---raw-githubusercontent-com-aptible-dryer-lint-main--rubocop-base-yml b/.rubocop-https---raw-githubusercontent-com-aptible-dryer-lint-main--rubocop-base-yml new file mode 100644 index 0000000..ff17794 --- /dev/null +++ b/.rubocop-https---raw-githubusercontent-com-aptible-dryer-lint-main--rubocop-base-yml @@ -0,0 +1,353 @@ +require: + - rubocop-rspec + +Gemspec/DeprecatedAttributeAssignment: + Enabled: true + +Gemspec/RequireMFA: + Enabled: true + +Layout/LineContinuationLeadingSpace: + Enabled: true + +Layout/LineContinuationSpacing: + Enabled: true + +Layout/LineEndStringConcatenationIndentation: + Enabled: true + +Layout/SpaceBeforeBrackets: + Enabled: true + +Lint/AmbiguousAssignment: + Enabled: true + +Lint/AmbiguousOperatorPrecedence: + Enabled: true + +Lint/AmbiguousRange: + Enabled: true + +Lint/ConstantOverwrittenInRescue: + Enabled: true + +Lint/DeprecatedConstants: + Enabled: true + +Lint/DuplicateBranch: + Enabled: true + +Lint/DuplicateMagicComment: + Enabled: true + +Lint/DuplicateRegexpCharacterClassElement: + Enabled: true + +Lint/EmptyBlock: + Enabled: false + +Lint/EmptyClass: + Enabled: true + +Lint/EmptyInPattern: + Enabled: true + +Lint/IncompatibleIoSelectWithFiberScheduler: + Enabled: true + +Lint/LambdaWithoutLiteralBlock: + Enabled: true + +Lint/NoReturnInBeginEndBlocks: + Enabled: true + +Lint/NonAtomicFileOperation: + Enabled: true + +Lint/NumberedParameterAssignment: + Enabled: true + +Lint/OrAssignmentToConstant: + Enabled: true + +Lint/RedundantDirGlobSort: + Enabled: true + +Lint/RefinementImportMethods: + Enabled: true + +Lint/RequireRangeParentheses: + Enabled: true + +Lint/RequireRelativeSelfPath: + Enabled: true + +Lint/SymbolConversion: + Enabled: true + +Lint/ToEnumArguments: + Enabled: true + +Lint/TripleQuotes: + Enabled: true + +Lint/UnexpectedBlockArity: + Enabled: true + +Lint/UnmodifiedReduceAccumulator: + Enabled: true + +Lint/UselessRuby2Keywords: + Enabled: true + +Security/CompoundHash: + Enabled: true + +Security/IoMethods: + Enabled: true + +Style/ArgumentsForwarding: + Enabled: true + +Style/CollectionCompact: + Enabled: true + +Style/DocumentDynamicEvalDefinition: + Enabled: true + +Style/EmptyHeredoc: + Enabled: true + +Style/EndlessMethod: + Enabled: true + +Style/EnvHome: + Enabled: true + +Style/FetchEnvVar: + Enabled: true + +Style/FileRead: + Enabled: true + +Style/FileWrite: + Enabled: true + +# TODO - remove this when this has been released on rubocop +Style/GuardClause: + Enabled: false + +Style/HashConversion: + Enabled: true + +Style/HashExcept: + Enabled: true + +Style/IfWithBooleanLiteralBranches: + Enabled: true + +Style/InPatternThen: + Enabled: true + +Style/MagicCommentFormat: + Enabled: true + +Style/MapCompactWithConditionalBlock: + Enabled: true + +Style/MapToHash: + Enabled: true + +Style/MultilineInPatternThen: + Enabled: true + +Style/NegatedIfElseCondition: + Enabled: true + +Style/NestedFileDirname: + Enabled: true + +Style/NilLambda: + Enabled: true + +Style/NumberedParameters: + Enabled: true + +Style/NumberedParametersLimit: + Enabled: true + +Style/NumericLiterals: + Enabled: false + +Style/ObjectThen: + Enabled: true + +Style/OpenStructUse: + Enabled: true + +Style/OperatorMethodCall: + Enabled: true + +Style/QuotedSymbols: + Enabled: true + +Style/RedundantArgument: + Enabled: true + +Style/RedundantEach: + Enabled: true + +Style/RedundantInitialize: + Enabled: true + +Style/RedundantSelfAssignmentBranch: + Enabled: true + +Style/RedundantStringEscape: + Enabled: true + +Style/SelectByRegexp: + Enabled: true + +Style/StringChars: + Enabled: true + +Style/SwapValues: + Enabled: true + +RSpec/BeEq: + Enabled: true + +RSpec/BeNil: + Enabled: true + +RSpec/ChangeByZero: + Enabled: true + +RSpec/ClassCheck: + Enabled: true + +RSpec/ExcessiveDocstringSpacing: + Enabled: true + +RSpec/IdenticalEqualityAssertion: + Enabled: true + +RSpec/SortMetadata: + Enabled: true + +RSpec/SubjectDeclaration: + Enabled: true + +RSpec/StubbedMock: + Enabled: false + +RSpec/VerifiedDoubleReference: + Enabled: true + +############################################################################### +## Customizations ## +############################################################################### + +# Try to keep a balance between the fact that some people are +# working from laptops and the fact that unnecessarily short +# lines make code hard to read. +Layout/LineLength: + Enabled: true + Max: 120 + +# TODO: We have enough places where this isn't used that it would +# be hard to switch, but maybe we should enable in the future? +RSpec/NamedSubject: + Enabled: false + +# Sometimes our tests are only confirming that +# an exception isn't raised. +RSpec/NoExpectationExample: + Enabled: false + +# These sometimes make tests look nicer. +RSpec/NestedGroups: + Enabled: false + +RSpec/RepeatedExampleGroupBody: + Enabled: false + +RSpec/MultipleMemoizedHelpers: + Max: 20 + +RSpec/SubjectStub: + Enabled: false + +RSpec/MessageSpies: + Enabled: false + +RSpec/ExpectInHook: + Enabled: false + +RSpec/MultipleExpectations: + Enabled: false + +RSpec/ExampleLength: + Enabled: false + +# These aren't effective enough at calculating the right +# kinds of complexity. +Metrics/CyclomaticComplexity: + Enabled: false + +Metrics/AbcSize: + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false + +# Favor explicit over implicit. +Metrics/ParameterLists: + Max: 20 + +Style/HashSyntax: + Enabled: false + +Naming/BlockForwarding: + Enabled: true + EnforcedStyle: explicit + +# This probably isn't right to disable, but at the same time, there +# are places where we're using (valid) boilerplate code that adds up. +Metrics/MethodLength: + Enabled: false + +# This probably isn't best to change either, but some modules and corresponding +# helpers will have a fair number of lines and this seems arbitrarily limiting +Metrics/ModuleLength: + Enabled: false + +# We may want to re-enable this in the future with a reasonable value, +# but right now this is overly prescriptive, especially in scenarios +# where we have a lot of boilerplate code. +Metrics/ClassLength: + Enabled: false + +# This is fine being a judgement call. +Metrics/BlockLength: + Enabled: false + +# This is overly prescriptive with minimal gain. +RSpec/ContextWording: + Enabled: false + +# This is allowing something implicit, which isn't ideal, +# but it's also something we heavily leverage. +RSpec/LetSetup: + Enabled: false + +# This doesn't feel great to add, but unfortunately it's making +# incorrect assumptions about some of our function names +# which causes a lot of false positives. +RSpec/PredicateMatcher: + Enabled: false + +# There are valid reasons to do this. +RSpec/InstanceVariable: + Enabled: false diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..d2ed9f3 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,16 @@ +inherit_from: + - https://raw.githubusercontent.com/aptible/dryer-lint/main/.rubocop.base.yml + - .rubocop_ignore.yml + +Style/GuardClause: + Enabled: false + +AllCops: + TargetRubyVersion: 3.1 + NewCops: disable + Include: + - !ruby/regexp /\.rb$/ + - !ruby/regexp /Gemfile$/ + - !ruby/regexp /\.gemspec$/ + - !ruby/regexp /\.rake$/ + - !ruby/regexp /Rakefile$/ diff --git a/.rubocop_ignore.yml b/.rubocop_ignore.yml new file mode 100644 index 0000000..64e474c --- /dev/null +++ b/.rubocop_ignore.yml @@ -0,0 +1,11 @@ +Naming/FileName: + Exclude: + - 'lib/sidekiq-field-encryptor.rb' + +RSpec/MultipleDescribes: + Exclude: + - 'spec/sidekiq-field-encryptor/encryptor_spec.rb' + +Gemspec/RequiredRubyVersion: + Exclude: + - 'sidekiq-field-encryptor.gemspec' \ No newline at end of file diff --git a/Gemfile b/Gemfile index 2fcefe1..2dea6c3 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + source 'https://rubygems.org' # Specify your gem's dependencies in sidekiq-field-encryptor.gemspec diff --git a/README.md b/README.md index d059622..cb13bad 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,11 @@ decrypt the values inside the client before the job is executed. 1. Fork the project. 1. Commit your changes, with specs. -1. Ensure that your code passes specs (`rake spec`) and meets Aptible's Ruby style guide (`rake rubocop`). +1. Ensure that your code passes specs (`bundle exec rspec`) and meets Aptible's Ruby style guide (`bundle exec rubocop`). 1. Create a new pull request on GitHub. ## Copyright and License MIT License, see [LICENSE](LICENSE.md) for details. -Copyright (c) 2019 [Aptible](https://www.aptible.com), Blake Pettersson, and contributors. +Copyright (c) 2024 [Aptible](https://www.aptible.com), Blake Pettersson, and contributors. diff --git a/Rakefile b/Rakefile index 1a4d040..7398a90 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,3 @@ -require 'bundler/gem_tasks' +# frozen_string_literal: true -require 'aptible/tasks' -Aptible::Tasks.load_tasks +require 'bundler/gem_tasks' diff --git a/lib/sidekiq-field-encryptor.rb b/lib/sidekiq-field-encryptor.rb index 52f3206..7c61f39 100644 --- a/lib/sidekiq-field-encryptor.rb +++ b/lib/sidekiq-field-encryptor.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + require 'sidekiq-field-encryptor/version' require 'sidekiq-field-encryptor/encryptor' diff --git a/lib/sidekiq-field-encryptor/encryptor.rb b/lib/sidekiq-field-encryptor/encryptor.rb index 7fcb01c..60c4a10 100644 --- a/lib/sidekiq-field-encryptor/encryptor.rb +++ b/lib/sidekiq-field-encryptor/encryptor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'base64' require 'encryptor' require 'json' @@ -22,9 +24,10 @@ # Will encrypt the values {'x' => 1} and 'b' when storing the job in Redis and # decrypt the values inside the client before the job is executed. module SidekiqFieldEncryptor - SERIALIZE_JSON = 'json'.freeze - SERIALIZE_MARHSALL = 'marshal'.freeze + SERIALIZE_JSON = 'json' + SERIALIZE_MARHSALL = 'marshal' + # Shared methods between client and base class Base def initialize(options = {}) @encryption_key = options[:encryption_key] @@ -68,7 +71,7 @@ def deserialize(method, value) def encrypt(value) plaintext = serialize(value) - iv = OpenSSL::Cipher::Cipher.new(@encryption_algorithm).random_iv + iv = OpenSSL::Cipher.new(@encryption_algorithm).random_iv args = { key: @encryption_key, iv: iv, algorithm: @encryption_algorithm } ciphertext = ::Encryptor.encrypt(plaintext, **args) [ @@ -100,15 +103,16 @@ def process_message(message) if to_encrypt == true message['args'][arg_index] = yield(raw_value) elsif to_encrypt.is_a?(Array) && raw_value.is_a?(Hash) - message['args'][arg_index] = Hash[raw_value.map do |key, value| + message['args'][arg_index] = raw_value.to_h do |key, value| value = yield(value) if to_encrypt.member?(key.to_s) [key, value] - end] + end end end end end + # Used when encrypting fields class Client < Base def call(_, message, _, _) process_message(message) { |value| encrypt(value) } @@ -116,6 +120,7 @@ def call(_, message, _, _) end end + # Used when decrypting fields class Server < Base def call(_, message, _) process_message(message) { |value| decrypt(value) } diff --git a/lib/sidekiq-field-encryptor/version.rb b/lib/sidekiq-field-encryptor/version.rb index 7d5e227..7c2efef 100644 --- a/lib/sidekiq-field-encryptor/version.rb +++ b/lib/sidekiq-field-encryptor/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SidekiqFieldEncryptor - VERSION = '0.2.0'.freeze + VERSION = '0.3.0' end diff --git a/sidekiq-field-encryptor.gemspec b/sidekiq-field-encryptor.gemspec index 56e532a..2e4e3d8 100644 --- a/sidekiq-field-encryptor.gemspec +++ b/sidekiq-field-encryptor.gemspec @@ -1,6 +1,6 @@ -# encoding: utf-8 +# frozen_string_literal: true -lib = File.expand_path('../lib', __FILE__) +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'English' @@ -17,13 +17,13 @@ Gem::Specification.new do |spec| spec.license = 'MIT' spec.files = `git ls-files`.split($RS) - spec.test_files = spec.files.grep(%r{^spec/}) spec.require_paths = ['lib'] spec.add_dependency 'encryptor' - spec.add_development_dependency 'aptible-tasks' spec.add_development_dependency 'bundler' spec.add_development_dependency 'rake' - spec.add_development_dependency 'rspec' + spec.add_development_dependency 'rspec', '~> 3.12' + spec.add_development_dependency 'rubocop-rspec' + spec.metadata['rubygems_mfa_required'] = 'true' end diff --git a/spec/sidekiq-field-encryptor/encryptor_spec.rb b/spec/sidekiq-field-encryptor/encryptor_spec.rb index 9361bae..98d7b2e 100644 --- a/spec/sidekiq-field-encryptor/encryptor_spec.rb +++ b/spec/sidekiq-field-encryptor/encryptor_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + require 'spec_helper' describe SidekiqFieldEncryptor::Client do - let(:key) { OpenSSL::Cipher::Cipher.new('aes-256-cbc').random_key } + let(:key) { OpenSSL::Cipher.new('aes-256-cbc').random_key } let(:message) do { 'class' => 'FooJob', 'args' => [1, 2, { 'a' => 'A', 'b' => 'B' }] } end @@ -10,8 +12,9 @@ it "doesn't fail when encryption isn't attempted" do subject.call('FooJob', message, nil, nil) {} end + it 'fails when encryption is attempted' do - client = SidekiqFieldEncryptor::Client.new( + client = described_class.new( encrypted_fields: { 'FooJob' => { 1 => true } } ) expect { client.call('FooJob', message, nil, nil) {} } @@ -21,7 +24,7 @@ describe 'with an encryption key' do subject do - SidekiqFieldEncryptor::Client.new( + described_class.new( encryption_key: key, encrypted_fields: { 'FooJob' => { 1 => true, 2 => %w[b d] } } ) @@ -37,16 +40,16 @@ end it 'supports setting the encryption algorithm' do - key = OpenSSL::Cipher::Cipher.new('aes-128-cbc').random_key + key = OpenSSL::Cipher.new('aes-128-cbc').random_key fields = { 'FooJob' => { 1 => true, 2 => %w[b d] } } - ko = SidekiqFieldEncryptor::Client.new( + ko = described_class.new( encryption_key: key, encryption_algorithm: 'aes-256-cbc', encrypted_fields: fields ) - ok = SidekiqFieldEncryptor::Client.new( + ok = described_class.new( encryption_key: key, encryption_algorithm: 'aes-128-cbc', encrypted_fields: fields @@ -60,7 +63,7 @@ end describe SidekiqFieldEncryptor::Server do - let(:key) { OpenSSL::Cipher::Cipher.new('aes-256-cbc').random_key } + let(:key) { OpenSSL::Cipher.new('aes-256-cbc').random_key } let(:message) do { 'class' => 'FooJob', 'args' => [1, 2, { 'a' => 'A', 'b' => 'B' }] } end @@ -69,8 +72,9 @@ it "doesn't fail when decryption isn't attempted" do subject.call('FooJob', message, nil) {} end + it 'fails when decryption is attempted' do - server = SidekiqFieldEncryptor::Server.new( + server = described_class.new( encrypted_fields: { 'FooJob' => { 1 => true } } ) expect { server.call('FooJob', message, nil) {} } @@ -80,7 +84,7 @@ describe 'with an encryption key' do subject do - SidekiqFieldEncryptor::Server.new( + described_class.new( encryption_key: key, encrypted_fields: { 'FooJob' => { 1 => true, 2 => %w[b d] } } ) @@ -95,16 +99,16 @@ end it 'supports setting the encryption algorithm' do - key = OpenSSL::Cipher::Cipher.new('aes-128-cbc').random_key + key = OpenSSL::Cipher.new('aes-128-cbc').random_key fields = { 'FooJob' => { 1 => true } } - ko = SidekiqFieldEncryptor::Server.new( + ko = described_class.new( encryption_key: key, encryption_algorithm: 'aes-256-cbc', encrypted_fields: fields ) - ok = SidekiqFieldEncryptor::Server.new( + ok = described_class.new( encryption_key: key, encryption_algorithm: 'aes-128-cbc', encrypted_fields: fields @@ -119,7 +123,7 @@ end it 'fails if the serialization methods are different' do - r = SidekiqFieldEncryptor::Server.new( + r = described_class.new( encryption_key: key, encrypted_fields: { 'FooJob' => { 1 => true } }, serialization_method: SidekiqFieldEncryptor::SERIALIZE_JSON @@ -137,7 +141,7 @@ end it 'allows compat serialization' do - r = SidekiqFieldEncryptor::Server.new( + r = described_class.new( encryption_key: key, encrypted_fields: { 'FooJob' => { 1 => true } }, serialization_method: SidekiqFieldEncryptor::SERIALIZE_JSON, diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6c94ea7..70cc94c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.dirname(__FILE__))