darrrr 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +203 -0
- data/Rakefile +42 -0
- data/lib/darrrr.rb +9 -0
- data/lib/github/delegated_account_recovery.rb +178 -0
- data/lib/github/delegated_account_recovery/account_provider.rb +140 -0
- data/lib/github/delegated_account_recovery/constants.rb +19 -0
- data/lib/github/delegated_account_recovery/crypto_helper.rb +57 -0
- data/lib/github/delegated_account_recovery/cryptors/default/default_encryptor.rb +66 -0
- data/lib/github/delegated_account_recovery/cryptors/default/encrypted_data.rb +92 -0
- data/lib/github/delegated_account_recovery/cryptors/default/encrypted_data_io.rb +12 -0
- data/lib/github/delegated_account_recovery/provider.rb +125 -0
- data/lib/github/delegated_account_recovery/recovery_provider.rb +118 -0
- data/lib/github/delegated_account_recovery/recovery_token.rb +113 -0
- data/lib/github/delegated_account_recovery/serialization/recovery_token_reader.rb +22 -0
- data/lib/github/delegated_account_recovery/serialization/recovery_token_writer.rb +22 -0
- data/lib/github/delegated_account_recovery/version.rb +5 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b73c12978f19a57c566f6cdc1e3caf2409307b89
|
4
|
+
data.tar.gz: 84ea2347b3d30a53489cfafad46b7aa4d5d4dc13
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5c224e64fc58c14ee6dc13272274d74f8439d20d73781770b007d13db567dfed10760a739737bf07922e0ac8f04496cac578a4facb58409167329dd3626031f6
|
7
|
+
data.tar.gz: f2800490548b13ae9d5da99bcce76552f508b51975f2cf864427802ed895288c734eae0f30044c8ade4351fe41eacd5af8bf6f1b2b1a1f12e69426d38d3f1fb8
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 GitHub
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/github/darrrr.svg?branch=master)](https://travis-ci.org/github/darrrr) [![Code Climate](https://codeclimate.com/github/github/darrrr/badges/gpa.svg)](https://codeclimate.com/github/github/darrrr)
|
2
|
+
|
3
|
+
The Delegated Account Recovery Rigid Reusable Ruby (aka D.a.r.r.r.r. or "Darrrr") library is meant to be used as the fully-complete plumbing in your Rack application when implementing the [Delegated Account Recovery specification](https://github.com/facebook/DelegatedRecoverySpecification). This library is currently used for the implementation at [GitHub](https://githubengineering.com/recover-accounts-elsewhere/).
|
4
|
+
|
5
|
+
Along with a fully featured library, a proof of concept application is provided in this repo.
|
6
|
+
|
7
|
+
## Configuration
|
8
|
+
|
9
|
+
An account provider (e.g. GitHub) is someone who stores a token with someone else (a recovery provider e.g. Facebook) in order to grant access to an account.
|
10
|
+
|
11
|
+
In `config/initializers` or any location that is run during application setup, add a file:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
Darrrr.authority = "http://localhost:9292"
|
15
|
+
Darrrr.privacy_policy = "#{Darrrr.authority}/articles/github-privacy-statement/"
|
16
|
+
Darrrr.icon_152px = "#{Darrrr.authority}/icon.png"
|
17
|
+
|
18
|
+
# See script/setup for instructions on how to generate keys
|
19
|
+
Darrrr::AccountProvider.configure do |config|
|
20
|
+
config.signing_private_key = ENV["ACCOUNT_PROVIDER_PRIVATE_KEY"]
|
21
|
+
config.symmetric_key = ENV["TOKEN_DATA_AES_KEY"]
|
22
|
+
config.tokensign_pubkeys_secp256r1 = [ENV["ACCOUNT_PROVIDER_PUBLIC_KEY"]]
|
23
|
+
config.save_token_return = "#{Darrrr.authority}/account-provider/save-token-return"
|
24
|
+
config.recover_account_return = "#{Darrrr.authority}/account-provider/recover-account-return"
|
25
|
+
end
|
26
|
+
|
27
|
+
Darrrr::RecoveryProvider.configure do |config|
|
28
|
+
config.signing_private_key = ENV["RECOVERY_PROVIDER_PRIVATE_KEY"]
|
29
|
+
config.countersign_pubkeys_secp256r1 = [ENV["RECOVERY_PROVIDER_PUBLIC_KEY"]]
|
30
|
+
config.token_max_size = 8192
|
31
|
+
config.save_token = "#{Darrrr.authority}/recovery-provider/save-token"
|
32
|
+
config.recover_account = "#{Darrrr.authority}/recovery-provider/recover-account"
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
The delegated recovery spec depends on publicly available endpoints serving standard configs. These responses can be cached but are not by default. To configure your cache store, provide the reference:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
Darrrr.cache = Dalli::Client.new('localhost:11211', options)
|
40
|
+
```
|
41
|
+
|
42
|
+
The spec disallows `http` URIs for basic security, but sometimes we don't have this setup locally.
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Darrrr.allow_unsafe_urls = true
|
46
|
+
```
|
47
|
+
|
48
|
+
## Provider registration
|
49
|
+
|
50
|
+
In order to allow a site to act as a provider, it must be "registered" on boot to prevent unauthorized providers from managing tokens.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
# Only configure this if you are acting as a recovery provider
|
54
|
+
Darrrr.register_account_provider("https://github.com")
|
55
|
+
|
56
|
+
# Only configure this if you are acting as an account provider
|
57
|
+
Darrrr.register_recovery_provider("https://www.facebook.com")
|
58
|
+
```
|
59
|
+
|
60
|
+
## Custom crypto
|
61
|
+
|
62
|
+
Create a module that responds to `Module.sign`, `Module.verify`, `Module.decrypt`, and `Module.encrypt`. You can use the template below. I recommend leaving the `#verify` method as is unless you have a compelling reason to override it.
|
63
|
+
|
64
|
+
### Global config
|
65
|
+
|
66
|
+
Set `Darrrr.custom_encryptor = MyCustomEncryptor`
|
67
|
+
|
68
|
+
### On-demand
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
Darrrr.with_encryptor(MyCustomEncryptor) do
|
72
|
+
# perform DAR actions using MyCustomEncryptor as the crypto provider
|
73
|
+
token = Darrrr.this_account_provider.generate_recovery_token(data: "foo", audience: recovery_provider)
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
module MyCustomEncryptor
|
79
|
+
class << self
|
80
|
+
# Encrypts the data in an opaque way
|
81
|
+
#
|
82
|
+
# data: the secret to be encrypted
|
83
|
+
#
|
84
|
+
# returns a byte array representation of the data
|
85
|
+
def encrypt(data)
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
# Decrypts the data
|
90
|
+
#
|
91
|
+
# ciphertext: the byte array to be decrypted
|
92
|
+
#
|
93
|
+
# returns a string
|
94
|
+
def decrypt(ciphertext)
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
# payload: binary serialized recovery token (to_binary_s).
|
99
|
+
#
|
100
|
+
# key: the private EC key used to sign the token
|
101
|
+
#
|
102
|
+
# returns signature in ASN.1 DER r + s sequence
|
103
|
+
def sign(payload, key)
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
# payload: token in binary form
|
108
|
+
# signature: signature of the binary token
|
109
|
+
# key: the EC public key used to verify the signature
|
110
|
+
#
|
111
|
+
# returns true if signature validates the payload
|
112
|
+
def verify(payload, signature, key)
|
113
|
+
# typically, the default verify function should be used to ensure compatibility
|
114
|
+
Darrrr::DefaultEncryptor.verify(payload, signature, key)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
## Example implementation
|
121
|
+
|
122
|
+
I strongly suggest you read the specification, specifically section 3.1 (save-token) and 3.5 (recover account) as they contain the most dangerous operations.
|
123
|
+
|
124
|
+
**NOTE:** this is NOT meant to be a complete implementation, it is just the starting point. Crucial aspects such as authentication, audit logging, out of band notifications, and account provider persistence are not implemented.
|
125
|
+
|
126
|
+
* [Account Provider](controllers/account_provider_controller.rb) (save-token-return, recover-account-return)
|
127
|
+
* [Recovery Provider](controllers/recovery_provider_controller.rb) (save-token, recover-account)
|
128
|
+
* [Configuration endpoint](controllers/well_known_config_controller.rb) (`/.well-known/delegated-account-recovery/configuration`)
|
129
|
+
|
130
|
+
Specifically, the gem exposes the following APIs for manipulating tokens.
|
131
|
+
* Account Provider
|
132
|
+
* [Generating](https://github.com/github/darrrr/blob/faafda5b1773e077c9c10b55b46216f97d13cd3b/lib/github/delegated_account_recovery/account_provider.rb#L49) a token
|
133
|
+
* Signing ([`#seal`](https://github.com/github/darrrr/blob/faafda5b1773e077c9c10b55b46216f97d13cd3b/lib/github/delegated_account_recovery/crypto_helper.rb#L13)) a token
|
134
|
+
* Verifying ([`#unseal`](https://github.com/github/darrrr/blob/faafda5b1773e077c9c10b55b46216f97d13cd3b/lib/github/delegated_account_recovery/crypto_helper.rb#L30)) a countersigned token
|
135
|
+
* Recovery Provider
|
136
|
+
* Verifying ([`#unseal`](https://github.com/github/darrrr/blob/faafda5b1773e077c9c10b55b46216f97d13cd3b/lib/github/delegated_account_recovery/crypto_helper.rb#L30)) a token
|
137
|
+
* [Countersigning](https://github.com/github/darrrr/blob/faafda5b1773e077c9c10b55b46216f97d13cd3b/lib/github/delegated_account_recovery/recovery_provider.rb#L60) a token
|
138
|
+
|
139
|
+
### Development
|
140
|
+
|
141
|
+
Local development assumes a Mac OS environment with [homebrew](https://brew.sh/) available. Postgres and phantom JS will be installed.
|
142
|
+
|
143
|
+
Run `./script/bootstrap` then run `./script/server`
|
144
|
+
|
145
|
+
* Visit `http://localhost:9292/account-provider`
|
146
|
+
* (Optionally) Record the random number for verification
|
147
|
+
* Click "connect to http://localhost:9292"
|
148
|
+
* You'll see some debug information on the page.
|
149
|
+
* Click "setup recovery".
|
150
|
+
* If recovery setup was successful, click "Recovery Setup Successful"
|
151
|
+
* Click the "recover now?" link
|
152
|
+
* You'll see an intermediate page, where more debug information is presented. Click "recover token"
|
153
|
+
* You should be sent back to your host
|
154
|
+
* And see something like `Recovered data: <the secret from step 1>`
|
155
|
+
|
156
|
+
### Tests
|
157
|
+
|
158
|
+
Run `./script/test` to run all tests.
|
159
|
+
|
160
|
+
## Deploying to heroku
|
161
|
+
|
162
|
+
Use `heroku config:set` to set the environment variables listed in [script/setup](/script/setup). Additionally, run:
|
163
|
+
|
164
|
+
```
|
165
|
+
heroku config:set HOST_URL=$(heroku info -s | grep web_url | cut -d= -f2)
|
166
|
+
```
|
167
|
+
|
168
|
+
Push your app to heroku:
|
169
|
+
|
170
|
+
```
|
171
|
+
git push heroku <branch-name>:master
|
172
|
+
```
|
173
|
+
|
174
|
+
Migrate the database:
|
175
|
+
|
176
|
+
```
|
177
|
+
heroku run rake db:migrate
|
178
|
+
```
|
179
|
+
|
180
|
+
Use the app!
|
181
|
+
|
182
|
+
```
|
183
|
+
heroku restart
|
184
|
+
heroku open
|
185
|
+
```
|
186
|
+
|
187
|
+
## Roadmap
|
188
|
+
|
189
|
+
* Add support for `token-status` endpoints as defined by the spec
|
190
|
+
* Add async API as defined by the spec
|
191
|
+
* Implement token binding as part of the async API
|
192
|
+
|
193
|
+
## Don't want to run `./script` entries?
|
194
|
+
|
195
|
+
See `script/setup` for the environment variables that need to be set.
|
196
|
+
|
197
|
+
## Contributions
|
198
|
+
|
199
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
200
|
+
|
201
|
+
## License
|
202
|
+
|
203
|
+
`darrrr` is licensed under the [MIT license](LICENSE.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'net/http'
|
4
|
+
require 'net/https'
|
5
|
+
require 'date'
|
6
|
+
|
7
|
+
require_relative "app"
|
8
|
+
require_relative "lib/darrrr"
|
9
|
+
require "sinatra/activerecord/rake"
|
10
|
+
|
11
|
+
namespace :db do
|
12
|
+
task :load_config do
|
13
|
+
require "./app"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
unless ENV["RACK_ENV"] == "production"
|
19
|
+
require 'rspec/core/rake_task'
|
20
|
+
desc "Run RSpec"
|
21
|
+
RSpec::Core::RakeTask.new do |t|
|
22
|
+
t.verbose = false
|
23
|
+
t.rspec_opts = "--format progress"
|
24
|
+
end
|
25
|
+
|
26
|
+
task default: :spec
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
require 'rdoc/task'
|
31
|
+
rescue LoadError
|
32
|
+
require 'rdoc/rdoc'
|
33
|
+
require 'rake/rdoctask'
|
34
|
+
RDoc::Task = Rake::RDocTask
|
35
|
+
end
|
36
|
+
|
37
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
38
|
+
rdoc.rdoc_dir = 'rdoc'
|
39
|
+
rdoc.title = 'SecureHeaders'
|
40
|
+
rdoc.options << '--line-numbers'
|
41
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
42
|
+
end
|
data/lib/darrrr.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bindata"
|
4
|
+
require "openssl"
|
5
|
+
|
6
|
+
require_relative "delegated_account_recovery/constants"
|
7
|
+
require_relative "delegated_account_recovery/crypto_helper"
|
8
|
+
require_relative "delegated_account_recovery/recovery_token"
|
9
|
+
require_relative "delegated_account_recovery/provider"
|
10
|
+
require_relative "delegated_account_recovery/account_provider"
|
11
|
+
require_relative "delegated_account_recovery/recovery_provider"
|
12
|
+
require_relative "delegated_account_recovery/serialization/recovery_token_writer"
|
13
|
+
require_relative "delegated_account_recovery/serialization/recovery_token_reader"
|
14
|
+
require_relative "delegated_account_recovery/cryptors/default/default_encryptor"
|
15
|
+
require_relative "delegated_account_recovery/cryptors/default/encrypted_data"
|
16
|
+
require_relative "delegated_account_recovery/cryptors/default/encrypted_data_io"
|
17
|
+
|
18
|
+
module GitHub
|
19
|
+
module DelegatedAccountRecovery
|
20
|
+
# Represents a binary serialization error
|
21
|
+
class RecoveryTokenSerializationError < StandardError; end
|
22
|
+
|
23
|
+
# Represents invalid data within a valid token
|
24
|
+
# (e.g. wrong `version` number, invalid token `type`)
|
25
|
+
class TokenFormatError < StandardError; end
|
26
|
+
|
27
|
+
# Represents all crypto errors
|
28
|
+
# (e.g. invalid keys, invalid signature, decrypt failures)
|
29
|
+
class CryptoError < StandardError; end
|
30
|
+
|
31
|
+
# Represents providers supplying invalid configurations
|
32
|
+
# (e.g. non-https URLs, missing required fields, http errors)
|
33
|
+
class ProviderConfigError < StandardError; end
|
34
|
+
|
35
|
+
# Represents an invalid countersigned recovery token.
|
36
|
+
# (e.g. invalid signature, invalid nested token, unregistered provider, stale tokens)
|
37
|
+
class CountersignedTokenError < StandardError
|
38
|
+
attr_reader :key
|
39
|
+
def initialize(message, key)
|
40
|
+
super(message)
|
41
|
+
@key = key
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Represents an invalid recovery token.
|
46
|
+
# (e.g. invalid signature, unregistered provider, stale tokens)
|
47
|
+
class RecoveryTokenError < StandardError; end
|
48
|
+
|
49
|
+
# Represents a call to to `recovery_provider` or `account_provider` that
|
50
|
+
# has not been registered.
|
51
|
+
class UnknownProviderError < ArgumentError; end
|
52
|
+
|
53
|
+
include Constants
|
54
|
+
|
55
|
+
class << self
|
56
|
+
REQUIRED_CRYPTO_OPS = [:sign, :verify, :encrypt, :decrypt].freeze
|
57
|
+
# recovery provider data is only loaded (and cached) upon use.
|
58
|
+
attr_accessor :recovery_providers, :account_providers, :cache, :allow_unsafe_urls,
|
59
|
+
:privacy_policy, :icon_152px, :authority
|
60
|
+
|
61
|
+
# Find and load remote recovery provider configuration data.
|
62
|
+
#
|
63
|
+
# provider_origin: the origin that contains the config data in a well-known
|
64
|
+
# location.
|
65
|
+
def recovery_provider(provider_origin)
|
66
|
+
unless self.recovery_providers
|
67
|
+
raise "No recovery providers configured"
|
68
|
+
end
|
69
|
+
if self.recovery_providers.include?(provider_origin)
|
70
|
+
RecoveryProvider.new(provider_origin).load
|
71
|
+
else
|
72
|
+
raise UnknownProviderError, "Unknown recovery provider: #{provider_origin}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Permit an origin to act as a recovery provider.
|
77
|
+
#
|
78
|
+
# provider_origin: the origin to permit
|
79
|
+
def register_recovery_provider(provider_origin)
|
80
|
+
self.recovery_providers ||= []
|
81
|
+
self.recovery_providers << provider_origin
|
82
|
+
end
|
83
|
+
|
84
|
+
# Find and load remote account provider configuration data.
|
85
|
+
#
|
86
|
+
# provider_origin: the origin that contains the config data in a well-known
|
87
|
+
# location.
|
88
|
+
def account_provider(provider_origin)
|
89
|
+
unless self.account_providers
|
90
|
+
raise "No account providers configured"
|
91
|
+
end
|
92
|
+
if self.account_providers.include?(provider_origin)
|
93
|
+
AccountProvider.new(provider_origin).load
|
94
|
+
else
|
95
|
+
raise UnknownProviderError, "Unknown account provider: #{provider_origin}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Permit an origin to act as an account provider.
|
100
|
+
#
|
101
|
+
# account_origin: the origin to permit
|
102
|
+
def register_account_provider(account_origin)
|
103
|
+
self.account_providers ||= []
|
104
|
+
self.account_providers << account_origin
|
105
|
+
end
|
106
|
+
|
107
|
+
# Provide a reference to the account provider configuration for this web app
|
108
|
+
def this_account_provider
|
109
|
+
AccountProvider.this
|
110
|
+
end
|
111
|
+
|
112
|
+
# Provide a reference to the recovery provider configuration for this web app
|
113
|
+
def this_recovery_provider
|
114
|
+
RecoveryProvider.this
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns a hash of all configuration values, recovery and account provider.
|
118
|
+
def account_and_recovery_provider_config
|
119
|
+
provider_data = Darrrr.this_account_provider.try(:to_h) || {}
|
120
|
+
|
121
|
+
if Darrrr.this_recovery_provider
|
122
|
+
provider_data.merge!(recovery_provider_config) do |key, lhs, rhs|
|
123
|
+
unless lhs == rhs
|
124
|
+
raise ArgumentError, "inconsistent config value detected #{key}: #{lhs} != #{rhs}"
|
125
|
+
end
|
126
|
+
|
127
|
+
lhs
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
provider_data
|
132
|
+
end
|
133
|
+
|
134
|
+
# returns the account provider information in hash form
|
135
|
+
def account_provider_config
|
136
|
+
this_account_provider.to_h
|
137
|
+
end
|
138
|
+
|
139
|
+
# returns the account provider information in hash form
|
140
|
+
def recovery_provider_config
|
141
|
+
this_recovery_provider.to_h
|
142
|
+
end
|
143
|
+
|
144
|
+
def with_encryptor(encryptor)
|
145
|
+
raise ArgumentError, "A block must be supplied" unless block_given?
|
146
|
+
unless valid_encryptor?(encryptor)
|
147
|
+
raise ArgumentError, "custom encryption class must respond to all of #{REQUIRED_CRYPTO_OPS}"
|
148
|
+
end
|
149
|
+
|
150
|
+
Thread.current[:darrrr_encryptor] = encryptor
|
151
|
+
yield
|
152
|
+
ensure
|
153
|
+
Thread.current[:darrrr_encryptor] = nil
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns the crypto API to be used. A thread local instance overrides the
|
157
|
+
# globally configured value which overrides the default encryptor.
|
158
|
+
def encryptor
|
159
|
+
Thread.current[:darrrr_encryptor] || @encryptor || DefaultEncryptor
|
160
|
+
end
|
161
|
+
|
162
|
+
# Overrides the global `encryptor` API to use
|
163
|
+
#
|
164
|
+
# encryptor: a class/module that responds to all +REQUIRED_CRYPTO_OPS+.
|
165
|
+
def custom_encryptor=(encryptor)
|
166
|
+
if valid_encryptor?(encryptor)
|
167
|
+
@encryptor = encryptor
|
168
|
+
else
|
169
|
+
raise ArgumentError, "custom encryption class must respond to all of #{REQUIRED_CRYPTO_OPS}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
private def valid_encryptor?(encryptor)
|
174
|
+
REQUIRED_CRYPTO_OPS.all? {|m| encryptor.respond_to?(m)}
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|