super_settings 0.0.0.rc1 → 0.0.1.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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