super_settings 0.0.0.rc1 → 0.0.1.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,11 +2,12 @@
2
2
 
3
3
  require "json"
4
4
 
5
- # :nocov:
6
5
  module SuperSettings
7
6
  module Storage
8
7
  # Implementation of the SuperSettings::Storage model for running unit tests.
9
8
  class TestStorage
9
+ # :nocov:
10
+
10
11
  include Storage
11
12
 
12
13
  attr_reader :key, :raw_value, :description, :value_type, :updated_at, :created_at
@@ -127,14 +128,6 @@ module SuperSettings
127
128
  !!(defined?(@persisted) && @persisted)
128
129
  end
129
130
 
130
- protected
131
-
132
- def redact_history!
133
- self.class.history(key).each do |item|
134
- item[:value] = nil
135
- end
136
- end
137
-
138
131
  private
139
132
 
140
133
  def set_persisted!
@@ -153,6 +146,6 @@ module SuperSettings
153
146
  }
154
147
  end
155
148
  end
149
+ # :nocov:
156
150
  end
157
151
  end
158
- # :nocov:
@@ -2,7 +2,7 @@
2
2
 
3
3
  module SuperSettings
4
4
  # Abstraction over how a setting is stored and retrieved from the storage engine. Models
5
- # must implement the methods module in this module that raise `NotImplementedError`.
5
+ # must implement the methods module in this module that raise NotImplementedError.
6
6
  module Storage
7
7
  class RecordInvalid < StandardError
8
8
  end
@@ -14,6 +14,7 @@ module SuperSettings
14
14
 
15
15
  module ClassMethods
16
16
  # Storage classes must implent this method to return all settings included deleted ones.
17
+ #
17
18
  # @return [Array<SuperSetting::Setting::Storage>]
18
19
  def all
19
20
  # :nocov:
@@ -22,6 +23,7 @@ module SuperSettings
22
23
  end
23
24
 
24
25
  # Return all non-deleted settings.
26
+ #
25
27
  # @return [Array<SuperSetting::Setting::Storage>]
26
28
  def active
27
29
  all.reject(&:deleted?)
@@ -29,6 +31,7 @@ module SuperSettings
29
31
 
30
32
  # Storage classes must implement this method to return all settings updates since the
31
33
  # specified timestamp.
34
+ #
32
35
  # @return [Array<SuperSetting::Setting::Storage>]
33
36
  def updated_since(timestamp)
34
37
  # :nocov:
@@ -37,6 +40,7 @@ module SuperSettings
37
40
  end
38
41
 
39
42
  # Storage classes must implement this method to return a settings by it's key.
43
+ #
40
44
  # @return [SuperSetting::Setting::Storage]
41
45
  def find_by_key(key)
42
46
  # :nocov:
@@ -46,6 +50,7 @@ module SuperSettings
46
50
 
47
51
  # Storage classes must implement this method to return most recent time that any
48
52
  # setting was updated.
53
+ #
49
54
  # @return [Time]
50
55
  def last_updated_at
51
56
  # :nocov:
@@ -54,18 +59,24 @@ module SuperSettings
54
59
  end
55
60
 
56
61
  # Implementing classes can override this method to setup a thread safe connection within a block.
62
+ #
63
+ # @return [void]
57
64
  def with_connection(&block)
58
65
  yield
59
66
  end
60
67
 
61
68
  # Implementing classes can override this method to wrap an operation in an atomic transaction.
69
+ #
70
+ # @return [void]
62
71
  def transaction(&block)
63
72
  yield
64
73
  end
65
74
 
66
- # @return [Boolean] true if it's safe to load setting asynchronously in a background thread.
75
+ # Return true if it's safe to load setting asynchronously in a background thread.
76
+ #
77
+ # @return [Boolean]
67
78
  def load_asynchronous?
68
- !!(defined?(@load_asynchronous) && !@load_asynchronous.nil? ? @load_asynchronous : default_load_asynchronous?)
79
+ !!((defined?(@load_asynchronous) && !@load_asynchronous.nil?) ? @load_asynchronous : default_load_asynchronous?)
69
80
  end
70
81
 
71
82
  # Set to true to force loading setting asynchronously in a background thread.
@@ -80,7 +91,9 @@ module SuperSettings
80
91
  end
81
92
  end
82
93
 
83
- # @return [String] the key for the setting
94
+ # The key for the setting
95
+ #
96
+ # @return [String]
84
97
  def key
85
98
  # :nocov:
86
99
  raise NotImplementedError
@@ -88,6 +101,7 @@ module SuperSettings
88
101
  end
89
102
 
90
103
  # Set the key for the setting.
104
+ #
91
105
  # @param val [String]
92
106
  # @return [void]
93
107
  def key=(val)
@@ -96,7 +110,9 @@ module SuperSettings
96
110
  # :nocov:
97
111
  end
98
112
 
99
- # @return [String] the raw value for the setting before it is type cast.
113
+ # The raw value for the setting before it is type cast.
114
+ #
115
+ # @return [String]
100
116
  def raw_value
101
117
  # :nocov:
102
118
  raise NotImplementedError
@@ -104,6 +120,7 @@ module SuperSettings
104
120
  end
105
121
 
106
122
  # Set the raw value for the setting.
123
+ #
107
124
  # @param val [String]
108
125
  # @return [void]
109
126
  def raw_value=(val)
@@ -112,7 +129,9 @@ module SuperSettings
112
129
  # :nocov:
113
130
  end
114
131
 
115
- # @return [String] the value type for the setting
132
+ # The value type for the setting.
133
+ #
134
+ # @return [String]
116
135
  def value_type
117
136
  # :nocov:
118
137
  raise NotImplementedError
@@ -120,7 +139,8 @@ module SuperSettings
120
139
  end
121
140
 
122
141
  # Set the value type for the setting.
123
- # @param val [String] one of string, integer, float, boolean, datetime, array, or secret
142
+ #
143
+ # @param val [String] one of string, integer, float, boolean, datetime, or array
124
144
  # @return [void]
125
145
  def value_type=(val)
126
146
  # :nocov:
@@ -128,7 +148,8 @@ module SuperSettings
128
148
  # :nocov:
129
149
  end
130
150
 
131
- # @return [String] the description for the setting
151
+ # The description for the setting.
152
+ # @return [String]
132
153
  def description
133
154
  # :nocov:
134
155
  raise NotImplementedError
@@ -136,6 +157,7 @@ module SuperSettings
136
157
  end
137
158
 
138
159
  # Set the description for the setting.
160
+
139
161
  # @param val [String]
140
162
  # @return [void]
141
163
  def description=(val)
@@ -144,7 +166,9 @@ module SuperSettings
144
166
  # :nocov:
145
167
  end
146
168
 
147
- # @return [Boolean] true if the setting marked as deleted
169
+ # Return true if the setting marked as deleted.
170
+ #
171
+ # @return [Boolean]
148
172
  def deleted?
149
173
  # :nocov:
150
174
  raise NotImplementedError
@@ -153,6 +177,7 @@ module SuperSettings
153
177
 
154
178
  # Set the deleted flag for the setting. Settings should not actually be deleted since
155
179
  # the record is needed to keep the local cache up to date.
180
+ #
156
181
  # @param val [Boolean]
157
182
  # @return [void]
158
183
  def deleted=(val)
@@ -161,7 +186,9 @@ module SuperSettings
161
186
  # :nocov:
162
187
  end
163
188
 
164
- # @return [Time] the time the setting was last updated
189
+ # Return the time the setting was last updated
190
+ #
191
+ # @return [Time]
165
192
  def updated_at
166
193
  # :nocov:
167
194
  raise NotImplementedError
@@ -169,6 +196,7 @@ module SuperSettings
169
196
  end
170
197
 
171
198
  # Set the last updated time for the setting.
199
+ #
172
200
  # @param val [Time]
173
201
  # @return [void]
174
202
  def updated_at=(val)
@@ -177,7 +205,9 @@ module SuperSettings
177
205
  # :nocov:
178
206
  end
179
207
 
180
- # @return [Time] the time the setting was created
208
+ # Return the time the setting was created.
209
+ #
210
+ # @return [Time]
181
211
  def created_at
182
212
  # :nocov:
183
213
  raise NotImplementedError
@@ -185,6 +215,7 @@ module SuperSettings
185
215
  end
186
216
 
187
217
  # Set the created time for the setting.
218
+ #
188
219
  # @param val [Time]
189
220
  # @return [void]
190
221
  def created_at=(val)
@@ -195,6 +226,7 @@ module SuperSettings
195
226
 
196
227
  # Return array of history items reflecting changes made to the setting over time. Items
197
228
  # should be returned in reverse chronological order so that the most recent changes are first.
229
+ #
198
230
  # @return [Array<SuperSettings::History>]
199
231
  def history(limit: nil, offset: 0)
200
232
  # :nocov:
@@ -202,7 +234,9 @@ module SuperSettings
202
234
  # :nocov:
203
235
  end
204
236
 
205
- # Create a history item for the setting
237
+ # Create a history item for the setting.
238
+ #
239
+ # @return [void]
206
240
  def create_history(changed_by:, created_at:, value: nil, deleted: false)
207
241
  # :nocov:
208
242
  raise NotImplementedError
@@ -210,6 +244,7 @@ module SuperSettings
210
244
  end
211
245
 
212
246
  # Persist the record to storage.
247
+ #
213
248
  # @return [void]
214
249
  def save!
215
250
  # :nocov:
@@ -217,7 +252,8 @@ module SuperSettings
217
252
  # :nocov:
218
253
  end
219
254
 
220
- # @return [Boolean] true if the record has been stored.
255
+ # Return true if the record has been stored.
256
+ # @return [Boolean]
221
257
  def persisted?
222
258
  # :nocov:
223
259
  raise NotImplementedError
@@ -227,17 +263,6 @@ module SuperSettings
227
263
  def ==(other)
228
264
  other.is_a?(self.class) && other.key == key
229
265
  end
230
-
231
- protected
232
-
233
- # Remove the value stored on history records if the setting is changed to a secret since
234
- # these are not stored encrypted in the database. Implementing classes must redefine this
235
- # method.
236
- def redact_history!
237
- # :nocov:
238
- raise NotImplementedError
239
- # :nocov:
240
- end
241
266
  end
242
267
  end
243
268
 
@@ -1,14 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "secret_keys"
4
-
5
3
  require_relative "super_settings/application"
6
4
  require_relative "super_settings/coerce"
7
5
  require_relative "super_settings/configuration"
8
6
  require_relative "super_settings/local_cache"
9
- require_relative "super_settings/encryption"
10
7
  require_relative "super_settings/rest_api"
11
- require_relative "super_settings/rack_middleware"
8
+ require_relative "super_settings/rack_application"
12
9
  require_relative "super_settings/controller_actions"
13
10
  require_relative "super_settings/attributes"
14
11
  require_relative "super_settings/setting"
@@ -65,6 +62,15 @@ module SuperSettings
65
62
  Coerce.boolean(val.nil? ? default : val)
66
63
  end
67
64
 
65
+ # Return true if a setting cast as a boolean evaluates to false.
66
+ #
67
+ # @param key [String, Symbol]
68
+ # @param default [Boolean] value to return if the setting value is nil
69
+ # @return [Boolean]
70
+ def disabled?(key, default = true)
71
+ !enabled?(key, !default)
72
+ end
73
+
68
74
  # Get a setting value cast to a Time.
69
75
  #
70
76
  # @param key [String, Symbol]
@@ -91,13 +97,15 @@ module SuperSettings
91
97
  # store into a structured data store. It uses a delimiter to define how keys are nested which
92
98
  # defaults to a dot.
93
99
  #
94
- # If, for example, you have three keys in you settings "A.B1.C1 = 1", "A.B1.C2 = 2", and "A.B2.C3 = 3", the
100
+ # If, for example, you have three keys in you settings +A.B1.C1 = 1+, +A.B1.C2 = 2+, and +A.B2.C3 = 3+, the
95
101
  # nested structure will be:
96
102
  #
97
- # `{"A" => {"B1" => {"C1" => 1, "C2" => 2}, "B2" => {"C3" => 3}}}`
103
+ # +{"A" => {"B1" => {"C1" => 1, "C2" => 2}, "B2" => {"C3" => 3}}}+
104
+ #
105
+ # This whole hash would be returned if you called +hash+ without any key. If you called it with the
106
+ # key "A.B1", it would return
98
107
  #
99
- # This whole hash would be returned if you called `hash` without any key. If you called it with the
100
- # key "A.B1", it would return `{"C1" => 1, "C2" => 2}`.
108
+ # +{"C1" => 1, "C2" => 2}+
101
109
  #
102
110
  # @param key [String, Symbol] the prefix patter to fetch keys for; default to returning all settings
103
111
  # @param default [Hash] value to return if the setting value is nil
@@ -146,6 +154,8 @@ module SuperSettings
146
154
  end
147
155
 
148
156
  # Load the settings from the database into the in memory cache.
157
+ #
158
+ # @return [void]
149
159
  def load_settings
150
160
  local_cache.load_settings
151
161
  local_cache.wait_for_load
@@ -153,6 +163,8 @@ module SuperSettings
153
163
  end
154
164
 
155
165
  # Force refresh the settings in the in memory cache to be in sync with the database.
166
+ #
167
+ # @return [void]
156
168
  def refresh_settings
157
169
  local_cache.refresh
158
170
  nil
@@ -160,6 +172,8 @@ module SuperSettings
160
172
 
161
173
  # Reset the in memory cache. The cache will be automatically reloaded the next time
162
174
  # you access a setting.
175
+ #
176
+ # @return [void]
163
177
  def clear_cache
164
178
  local_cache.reset
165
179
  nil
@@ -176,7 +190,8 @@ module SuperSettings
176
190
  # object. You should use this method to configure the gem from an Rails initializer since
177
191
  # it will handle ensuring all the appropriate frameworks are loaded first.
178
192
  #
179
- # yieldparam config [SuperSettings::Configuration]
193
+ # @yieldparam config [SuperSettings::Configuration]
194
+ # @return [void]
180
195
  def configure(&block)
181
196
  Configuration.instance.defer(&block)
182
197
  unless defined?(Rails::Engine)
@@ -188,21 +203,19 @@ module SuperSettings
188
203
  # This setting aids in performance since it throttles the number of times the database is queried
189
204
  # for changes. However, changes made to the settings in the databae will take up to the number of
190
205
  # seconds in the refresh interval to be updated in the cache.
206
+ #
207
+ # @return [void]
191
208
  def refresh_interval=(value)
192
209
  local_cache.refresh_interval = value
193
210
  end
194
211
 
195
- # Set the secret used to encrypt secret settings in the database.
196
- #
197
- # If you need to roll your secret, you can pass in an array of values. The first one
198
- # specified will be used to encrypt values, but all of the keys will be tried when
199
- # decrypting a value already stored in the database.
200
- #
201
- # @param value [String, Array]
202
- def secret=(value)
203
- Encryption.secret = value
204
- load_settings if loaded?
205
- end
212
+ # URL for authenticating access to the application. This would normally be some kind of
213
+ # login page. Browsers will be redirected here if they are denied access to the web UI.
214
+ attr_accessor :authentication_url
215
+
216
+ # Javascript to inject into the settings application HTML page. This can be used, for example,
217
+ # to set authorization credentials stored client side to access the settings API.
218
+ attr_accessor :web_ui_javascript
206
219
 
207
220
  private
208
221
 
@@ -16,10 +16,10 @@ Gem::Specification.new do |spec|
16
16
  Gemfile
17
17
  Gemfile.lock
18
18
  Rakefile
19
+ assets/
19
20
  bin/
20
21
  gemfiles/
21
22
  spec/
22
- web_ui.png
23
23
  ]
24
24
  spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
25
25
  `git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
@@ -27,8 +27,6 @@ Gem::Specification.new do |spec|
27
27
 
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- spec.add_dependency "secret_keys", ">= 1.0"
31
-
32
30
  spec.add_development_dependency "bundler"
33
31
 
34
32
  spec.required_ruby_version = ">= 2.5"
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: super_settings
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0.rc1
4
+ version: 0.0.1.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-24 00:00:00.000000000 Z
11
+ date: 2023-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: secret_keys
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '1.0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '1.0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: bundler
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -71,11 +57,10 @@ files:
71
57
  - lib/super_settings/coerce.rb
72
58
  - lib/super_settings/configuration.rb
73
59
  - lib/super_settings/controller_actions.rb
74
- - lib/super_settings/encryption.rb
75
60
  - lib/super_settings/engine.rb
76
61
  - lib/super_settings/history_item.rb
77
62
  - lib/super_settings/local_cache.rb
78
- - lib/super_settings/rack_middleware.rb
63
+ - lib/super_settings/rack_application.rb
79
64
  - lib/super_settings/rest_api.rb
80
65
  - lib/super_settings/setting.rb
81
66
  - lib/super_settings/storage.rb
@@ -84,7 +69,6 @@ files:
84
69
  - lib/super_settings/storage/redis_storage.rb
85
70
  - lib/super_settings/storage/test_storage.rb
86
71
  - lib/super_settings/version.rb
87
- - lib/tasks/super_settings.rake
88
72
  - super_settings.gemspec
89
73
  homepage: https://github.com/bdurand/super_settings
90
74
  licenses:
@@ -105,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
89
  - !ruby/object:Gem::Version
106
90
  version: 1.3.1
107
91
  requirements: []
108
- rubygems_version: 3.2.23
92
+ rubygems_version: 3.4.5
109
93
  signing_key:
110
94
  specification_version: 4
111
95
  summary: Fast access runtime settings for a Rails application with an included UI
@@ -1,76 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SuperSettings
4
- module Encryption
5
- SALT = "0c54a781"
6
- private_constant :SALT
7
-
8
- # Error thrown when the secret is invalid
9
- class InvalidSecretError < StandardError
10
- def initialize
11
- super("Cannot decrypt. Invalid secret provided.")
12
- end
13
- end
14
-
15
- class << self
16
- # Set the secret key used for encrypting secret values. If this is not set,
17
- # the value will be loaded from the `SUPER_SETTINGS_SECRET` environment
18
- # variable. If that value is not set, arguments will not be encrypted.
19
- #
20
- # You can set multiple secrets by passing an array if you need to roll your secrets.
21
- # The left most value in the array will be used as the encryption secret, but
22
- # all the values will be tried when decrypting. That way if you have existing keys
23
- # that were encrypted with a different secret, you can still make it available
24
- # when decrypting. If you are using the environment variable, separate the keys
25
- # with spaces.
26
- #
27
- # @param value [String] One or more secrets to use for encrypting arguments.
28
- # @return [void]
29
- def secret=(value)
30
- @encryptors = make_encryptors(value)
31
- end
32
-
33
- # Encrypt a value for use with secret settings.
34
- # @api private
35
- def encrypt(value)
36
- return nil if Coerce.blank?(value)
37
- encryptor = encryptors.first
38
- return value if encryptor.nil?
39
- encryptor.encrypt(value)
40
- end
41
-
42
- # Decrypt a value for use with secret settings.
43
- # @api private
44
- def decrypt(value)
45
- return nil if Coerce.blank?(value)
46
- return value if encryptors.empty? || encryptors == [nil]
47
- encryptors.each do |encryptor|
48
- begin
49
- return encryptor.decrypt(value) if encryptor
50
- rescue OpenSSL::Cipher::CipherError
51
- # Not the right key, try the next one
52
- end
53
- end
54
- raise InvalidSecretError
55
- end
56
-
57
- # @return [Boolean] true if the value is encrypted in the storage engine.
58
- def encrypted?(value)
59
- SecretKeys::Encryptor.encrypted?(value)
60
- end
61
-
62
- private
63
-
64
- def encryptors
65
- if !defined?(@encryptors) || @encryptors.empty?
66
- @encryptors = make_encryptors(ENV["SUPER_SETTINGS_SECRET"].to_s.split)
67
- end
68
- @encryptors
69
- end
70
-
71
- def make_encryptors(secrets)
72
- Array(secrets).map { |val| val.nil? ? nil : SecretKeys::Encryptor.from_password(val, SALT) }
73
- end
74
- end
75
- end
76
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- namespace :super_settings do
4
- desc "Encrypt settings marked as secret" do
5
- SuperSettings::Setting.where(value_type: "secret").each do |setting|
6
- setting.raw_value_will_change! if setting.value
7
- end
8
- end
9
- end